function [G, G_ind, related, n_genes_KO, G_time] = buildGmatrix(model_name, model, separate_isoform, numWorkers, printLevel)
% Build the G matrix required for the calculation of genetic Minimal Cut
% Sets (gMCSs).
%
% USAGE:
%
%    [G, G_ind, related, n_genes_KO, G_time] = buildGmatrix(model_name, model_struct, separate_isoform, numWorkers, printLevel)
%
% INPUTS:
%    model_name:          Name of the metabolic model under study.
%    model_struct:        Metabolic model structure (COBRA Toolbox format).
%    separate_isoform:    Character used to discriminate different isoforms of a gene.
%
% OPTIONAL INPUTS:
%    numWorkers:        Maximum number of workers
%                       * 0 - maximum provided by the system (automatic)
%                       * 1 - sequential
%                       * 2+ - parallel
%    printLevel:        show the reactions created in models.
%                       * 0 - shows nothing
%                       * 1 - shows progress by reactions (default)
%                       * 2+ - shows everything (reaction and network generation)
%
% OUTPUTS:
%    G:             G matrix.
%    G_ind:         Gene knockouts associated with each row in the G matrix.
%    related:       Relationships among rows of the G matrix.
%    n_genes_KO:    Number of genes whose knockout is added by each row of the G matrix.
%    G_time:        Calculation times for each of the steps.
%
% EXAMPLE:
%
%    [G, G_ind, related, n_genes_KO, G_time] = buildGmatrix('Recon2.v04', modelR204, '.')
%
% .. Authors:
%       - Inigo Apaolaza, 16/11/2017, University of Navarra, TECNUN School of Engineering.
%       - Luis V. Valcarcel, 19/11/2017, University of Navarra, TECNUN School of Engineering.
%       - Francisco J. Planes, 20/11/2017, University of Navarra, TECNUN School of Engineering.
%       - Inigo Apaolaza, 10/04/2018, University of Navarra, TECNUN School of Engineering.

time_a = tic;
% Generate name for temporary folder
global CBTDIR
tmpFolderName = [CBTDIR filesep '.tmp'];
if ~exist(tmpFolderName,'dir')  % Create directories if needed
    mkdir(tmpFolderName)
end
if ~exist([tmpFolderName filesep 'rxn_level_gMCSs'],'dir')
    mkdir([tmpFolderName filesep 'rxn_level_gMCSs'])
end
if ~exist([tmpFolderName filesep 'rxn_level_gMCSs_by_rxn'],'dir')
    mkdir([tmpFolderName filesep 'rxn_level_gMCSs_by_rxn'])
end
if ~exist([tmpFolderName filesep 'rxn_level_models'],'dir')
    mkdir([tmpFolderName filesep 'rxn_level_models'])
end

% Analyze the GPR rules in order to set the strategy for the calculation of
% the knockouts for the G matrix
grRules = model.grRules;
rxnGeneMat = model.rxnGeneMat;
n_rxns = size(rxnGeneMat, 1);

rxns_0_genes = sum(rxnGeneMat, 2) == 0;
rxns_1_gene = sum(rxnGeneMat, 2) == 1;
rxns_or = cellfun(@strfind, grRules, repmat({'or'}, n_rxns, 1), 'UniformOutput', false);
rxns_or = ~cellfun(@isempty, rxns_or) & sum(rxnGeneMat, 2) > 1;
rxns_and = cellfun(@strfind, grRules, repmat({'and'}, n_rxns, 1), 'UniformOutput', false);
rxns_and = ~cellfun(@isempty, rxns_and) & sum(rxnGeneMat, 2) > 1;
rxns_only_or = rxns_or & ~rxns_and;
rxns_only_and = ~rxns_or & rxns_and;
rxns_or_and = rxns_or & rxns_and;

n_rxns_0_genes = sum(rxns_0_genes);
n_rxns_1_gene = sum(rxns_1_gene);
n_rxns_only_or = sum(rxns_only_or);
n_rxns_only_and = sum(rxns_only_and);
n_rxns_or_and = sum(rxns_or_and);
n_rxns_total = n_rxns_0_genes+n_rxns_1_gene+n_rxns_only_or+n_rxns_only_and+n_rxns_or_and;

summary_1 = {'n_rxns_0_genes', n_rxns_0_genes; 'n_rxns_1_gene', n_rxns_1_gene;
    'n_rxns_only_or', n_rxns_only_or; 'n_rxns_only_and', n_rxns_only_and;
    'n_rxns_or_and', n_rxns_or_and; 'n_rxns_total', n_rxns_total};
if printLevel >=1
    fprintf('\nG MATRIX - Summary\n')
    disp(summary_1)
end
ini_time = toc(time_a);

% Step 1 - Reactions with 1 gene
% clc
if printLevel >=1
    disp('G MATRIX - STEP 1');
end
time_b = tic;
act_rxnGeneMat = rxnGeneMat(rxns_1_gene, :);
not_delete_cols = sum(act_rxnGeneMat, 1) ~= 0;
act_rxnGeneMat = act_rxnGeneMat(:, not_delete_cols);
G_ind_1 = model.genes(not_delete_cols);
n_KO_1 = length(G_ind_1);
tmp_G_1 = act_rxnGeneMat';
G_1 = zeros(n_KO_1, n_rxns);
G_1(:, rxns_1_gene) = tmp_G_1;
G_time(1, 1) = toc(time_b);

% Step2 - Reactions with more than one gene and only OR rules
% clc
if printLevel >=1
    disp('G MATRIX - STEP 2');
end
time_c = tic;
act_rxnGeneMat = rxnGeneMat(rxns_only_or, :);
pos_rxns_only_or = find(rxns_only_or);
for i = 1:n_rxns_only_or
    G_ind_2{i, 1} = model.genes(logical(act_rxnGeneMat(i, :)))';
end
tmp_G_2 = eye(n_rxns_only_or);
G_2 = zeros(n_rxns_only_or, n_rxns);
G_2(:, rxns_only_or) = tmp_G_2;
G_time(2, 1) = toc(time_c);

% Reactions with more than one gene and only AND rules
% clc
if printLevel >=1
    disp('G MATRIX - STEP 3');
end
time_d = tic;
act_rxnGeneMat = rxnGeneMat(rxns_only_and, :);
not_delete_cols = sum(act_rxnGeneMat, 1) ~= 0;
act_rxnGeneMat = act_rxnGeneMat(:, not_delete_cols);
G_ind_3 = model.genes(not_delete_cols);
n_KO_3 = length(G_ind_3);
tmp_G_3 = act_rxnGeneMat';
G_3 = zeros(n_KO_3, n_rxns);
G_3(:, rxns_only_and) = tmp_G_3;
G_time(3, 1) = toc(time_d);

% Reactions with more than one gene and both OR and AND rules
time_e = tic;
search_filename = [tmpFolderName filesep 'rxn_level_models' filesep 'rxn_level_' model_name '_and_or.mat'];
if exist(search_filename,'file')
    load(search_filename);
else
    pos_rxns_or_and = find(rxns_or_and);
    [models_or_and, rxnNumGenes_or_and] = GPR2models(model, pos_rxns_or_and, separate_isoform, numWorkers, printLevel);
    save(search_filename, 'models_or_and', 'rxnNumGenes_or_and');
end

search_filename_2 = [tmpFolderName filesep 'rxn_level_gMCSs' filesep 'rxn_level_gMCSs_' model_name '.mat'];
if exist(search_filename_2,'file')
    load(search_filename_2);
else
    target_b = 1e-3;
    n_mcs = 100000000000;
    timelimit = 5*60;
    pos_rxns_or_and = find(rxns_or_and);
    if printLevel >=1
        disp('G MATRIX - STEP 4');
        showprogress(0);
    end
    for i = 1:n_rxns_or_and
        if printLevel >=1
%             disp([num2str(i),' of ', num2str(n_rxns_or_and)]);
            showprogress(i/n_rxns_or_and);
        end
        search_filename_3 = [tmpFolderName filesep 'rxn_level_gMCSs_by_rxn' filesep 'rxn_level_gMCSs_' model_name '_rxn' num2str(pos_rxns_or_and(i)) '.mat'];
        if exist(search_filename_3,'file')
            load(search_filename_3);
            mcs{i, 1} = act_mcs;
        else
            act_model = models_or_and{i};
            nbio = find(act_model.c);
            rxns = act_model.rxns;
            tmp = repmat({'DM_'}, length(rxns), 1);
            DM = cellfun(@strfind, rxns, tmp, 'UniformOutput', false);
            DM = ~cellfun(@isempty, DM);
            n_DM = sum(DM);
            DM = rxns(find(DM));
%             options.rxn_set = DM;
% %             options.timelimit = timelimit;
%             options.target_b = target_b;
%             options.printLevel = 0;
            max_len_mcs = length(DM);
            [act_mcs, act_mcs_time] = calculateMCS(act_model, n_mcs, max_len_mcs,...
                'rxn_set', DM,...
                'timelimit', timelimit,...
                'target_b', target_b,...
                'printLevel', 0);
            mcs{i, 1} = act_mcs;
            mcs_time{i, 1} = act_mcs_time;
            save(search_filename_3, 'act_mcs', 'act_mcs_time');
        end
    end
    save(search_filename_2);
end

k = 0;
for i = 1:n_rxns_or_and
    load([tmpFolderName filesep 'rxn_level_gMCSs_by_rxn' filesep 'rxn_level_gMCSs_' model_name '_rxn' num2str(pos_rxns_or_and(i)) '.mat']);
    n_act_mcs = length(act_mcs);

    for j = 1:n_act_mcs
        act_G_ind = act_mcs{j};
        if ~iscell(act_G_ind) && isnan(act_G_ind)
        else
            act_G_ind = cellfun(@strrep, act_G_ind, repmat({'DM_'}, length(act_G_ind), 1), repmat({''}, length(act_G_ind), 1), 'UniformOutput', false);
            act_G_ind = cellfun(@strtok, act_G_ind, repmat({separate_isoform}, length(act_G_ind), 1), 'UniformOutput', false);
            k = k+1;
            G_4(k, :) = zeros(1, n_rxns);
            G_4(k, pos_rxns_or_and(i)) = 1;
            G_ind_4{k, 1} = act_G_ind';
        end
    end
end
G_time(4, 1) = toc(time_e);

% Delete isoforms in order to work at the gene level
time_f = tic;
G_ind_1 = cellfun(@strtok, G_ind_1, repmat({separate_isoform}, length(G_ind_1), 1), 'UniformOutput', false);

n_KO_2 = length(G_ind_2);
for i = 1:n_KO_2
    act_G_ind_2 = G_ind_2{i};
    act_G_ind_2 = cellfun(@strtok, act_G_ind_2, repmat({separate_isoform}, 1, length(act_G_ind_2)), 'UniformOutput', false);
    G_ind_2{i} = unique(act_G_ind_2);
end
G_ind_3 = cellfun(@strtok, G_ind_3, repmat({separate_isoform}, length(G_ind_3), 1), 'UniformOutput', false);

% Delete repeats
if printLevel >=1
    disp('G MATRIX - Delete Repeats');
end
tmp_G = [];
try tmp_G = [tmp_G; G_1]; end
try tmp_G = [tmp_G; G_2]; end
try tmp_G = [tmp_G; G_3]; end
try tmp_G = [tmp_G; G_4]; end
tmp_G_ind = [];
try tmp_G_ind = [tmp_G_ind; G_ind_1]; end
try tmp_G_ind = [tmp_G_ind; G_ind_2]; end
try tmp_G_ind = [tmp_G_ind; G_ind_3]; end
try tmp_G_ind = [tmp_G_ind; G_ind_4]; end
n_tmp_G_ind = length(tmp_G_ind);

k = 0;
clear G G_ind
for i = 1:n_tmp_G_ind
    if i == 1
        k = k+1;
        G(k, :) = tmp_G(i, :);
        if ~iscell(tmp_G_ind{i})
            G_ind{k, 1} = tmp_G_ind(i);
        else
            G_ind{k, 1} = sort(tmp_G_ind{i});
        end
    else
        if ~iscell(tmp_G_ind{i})
            act_G_ind = tmp_G_ind(i);
        else
            act_G_ind = sort(tmp_G_ind{i});
        end

        pos_equal = cellfun(@isequal, G_ind, repmat({act_G_ind}, length(G_ind), 1));
        if sum(pos_equal) > 0
            G(pos_equal, :) = G(pos_equal, :) + tmp_G(i, :);
        else
            k = k+1;
            G(k, :) = tmp_G(i, :);
            G_ind{k, 1} = act_G_ind;
        end
    end
end

% Fill the G matrix with reactions which are knocked out by a given KO
% without being a gMCS
n_genes_KO = cellfun(@length, G_ind);
[n_genes_KO, ind] = sort(n_genes_KO, 'ascend');
G_ind = G_ind(ind);
G = G(ind, :);
n_G_ind = length(G_ind);
for i = 1:n_G_ind
    act_G_ind = G_ind{i};
    n_act_G_ind = length(act_G_ind);
    pos = find(n_genes_KO > n_act_G_ind);
    greater_G_ind = G_ind(pos);
    n_greater_G_ind = length(pos);
    for j = 1:n_greater_G_ind
        if sum(ismember(G_ind{pos(j)}, act_G_ind)) == n_act_G_ind
            G(pos(j), :) = G(pos(j), :) + G(i, :);
        end
    end
end
G = double(G>0);

% Check the interconnections between KOs
if printLevel >=1
    disp('G MATRIX - Check Relations');
end
k = 0;
n_genes_KO = cellfun(@length, G_ind);
for i = 2:n_G_ind
    act_G_ind = G_ind(i);
    n_act_G_ind = length(act_G_ind{:});
    pos = find(n_genes_KO < n_act_G_ind);
    if ~isempty(pos)
        pos = pos(end);
        for j = 1:pos
            act_G_ind_2 = G_ind(j);
            tmp = ismember(act_G_ind{:}, act_G_ind_2{:});
            n_act_G_ind_2 = length(act_G_ind_2{:});
            if sum(tmp) == n_act_G_ind_2
                k = k+1;
                related(k, 1) = i;
                related(k, 2) = j;
            end
        end
    end
end

if exist('related')
    un_related = unique(related(:, 1));
    n_un_related = length(un_related);
    for i = 1:n_un_related
        act_KO = un_related(i);
        ind = find(related(:, 1) == act_KO);
        act_related = related(ind, 2);
        all_genes = [G_ind{act_related}];
        un_all_genes = unique(all_genes);
        n_un_all_genes = length(un_all_genes);
        n_genes_KO(act_KO) = n_genes_KO(act_KO)-n_un_all_genes;
    end
else
    related = NaN;
end

final_filename = [pwd filesep 'G_' model_name '.mat'];
G_time(5, 1) = toc(time_f)+ini_time;
save(final_filename, 'G', 'G_ind', 'related', 'n_genes_KO', 'G_time');
if printLevel >=1
    disp('The G Matrix has been successfully calculated');
end
rmdir(tmpFolderName, 's');
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Copyright (C) 2010, John T. Ramshur, jramshur@gmail.com
%
% This file is part of HRVAS
%
% HRVAS 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.
%
% HRVAS 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 HRVAS.  If not, see <http://www.gnu.org/licenses/>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function batchHRV(opt)
%batchHRV: function that calculates hrv on all ibi files in selected
%           directory
%
%Inputs:
%
%Outputs:
%

%check inputs
% if (nargin < 1) || ~isstruct(opt)
%
% end

    %choose file containing HRV analysis options
    reply = input('Enter full file path of HRV analysis option file: ', 's');
    if isempty(reply) || exist(reply,'file')~=2
        error('Please choose valid file path!')
        return;
    end
    opt=load(reply);
    opt=opt.settings;

    %choose dir containing input files
    reply = input('Enter dir path containing IBI files: ', 's');
    if isempty(reply) || exist(reply,'dir')~=7
        error('Please choose valid input dir path!')
        return;
    end

    %check input directory
    fileList = dir(fullfile(reply, '*.ibi')); %get list of files
    fileList(any([fileList.isdir],1))=[]; %remove folders/dir from the list
    fnames = {fileList.name}; %get file names only from fileList structure
    %build array of full file paths
    fpaths=cell(length(fnames),1);
    for ff=1:length(fnames)
        fpaths{ff}=fullfile(reply,fnames{ff});
    end

    %display results of input dir
    disp([reply ' contains ' num2str(length(fpaths)) ' .ibi files.'])

    %choose output file name to save hrv reslts
    outPath = input('Enter file name for exported HRV data: ', 's');
    if isempty(outPath)
        error('Please choose valid input dir path!')
        return;
    end

    %Batch process HRV
    hrv=batchGetHRV(fpaths,opt);

    %Export/save HRV
    saveHRV(hrv,opt,fpaths,outPath)
    disp('batchHRV Done!')

end

function saveHRV(hrv,opt,fList,outPath)
       exportHRV(outPath,fList,hrv,opt)
end

function hrv=batchGetHRV(fList,opt)
    %Preallocate hrv array
    %hrv=repmat(struct('b',0,'a',0),1,length(fList));

    %display time remaining
    disp('Processing: 0% complete.')
    tic; avgElapsed=0;

    %get hrv for each file
    nFiles=length(fList);
    for f=1:nFiles
        hrv(f)=getHRV(fList{f},opt);

        %calculate remaining time for waitbar
        elapsedTime=toc; %total time elapsed since start
        %average time elapsed to process a single file
        avgElapsed=elapsedTime/f;
        %time remaining in processing (min)
        timeRem=ceil(avgElapsed*(nFiles-f)/60);
        bar=round(f/nFiles*100);
        disp(['Processing: ' num2str(bar) ...
            '% complete. (~' num2str(timeRem) ' min)'])
    end

end

function output=getHRV(f,settings)

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % LOAD IBI
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    nIBI=[]; dIBI=[];
    IBI=loadIBI(f,settings);

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %Preprocess Data
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    %build cell array of locate artifacts methods
    methods={}; methInput=[];
    if settings.ArtLocatePer
        methods=[methods,'percent'];
        methInput=[methInput,settings.ArtLocatePerVal];
    end
    if settings.ArtLocateSD
        methods=[methods,'sd'];
        methInput=[methInput,settings.ArtLocateSDVal];
    end
    if settings.ArtLocateMed
        methods=[methods,'median'];
        methInput=[methInput,settings.ArtLocateMedVal];
    end
    %determine which window/span to use
    if strcmpi(settings.ArtReplace,'mean')
        replaceWin=settings.ArtReplaceMeanVal;
    elseif strcmpi(settings.ArtReplace,'median')
        replaceWin=settings.ArtReplaceMedVal;
    else
        replaceWin=0;
    end

    %Note: We don't need to use all the input arguments,but we will let the
    %function handle all inputs
    [dIBI,nIBI,trend,art] = preProcessIBI(IBI, ...
        'locateMethod', methods, 'locateInput', methInput, ...
        'replaceMethod', settings.ArtReplace, ...
        'replaceInput',replaceWin, ...
        'detrendMethod', settings.Detrend, ...
        'smoothMethod', settings.SmoothMethod, ...
        'smoothSpan', settings.SmoothSpan, ...
        'smoothDegree', settings.SmoothDegree, ...
        'polyOrder', settings.PolyOrder, ...
        'waveletType', ...
        [settings.WaveletType num2str(settings.WaveletType2)], ...
        'waveletLevels', settings.WaveletLevels, ...
        'lambda', settings.PriorsLambda,...
        'resampleRate',settings.Interp,...
        'meanCorrection',true);

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %Calculate HRV
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    output.ibiinfo.count=size(IBI,1); % total # of ibi
    output.ibiinfo.outliers=sum(art); % number of outliers

    %Time-Domain (using non-detrented ibi)
    output.time=timeDomainHRV(nIBI,settings.SDNNi*60,settings.pNNx);
    %output.time.mean=round(mean(nIBI(:,2).*1000)*10)/10;
    %output.time.meanHR=round(mean(60./nIBI(:,2))*10)/10;

    %Freq-Domain
    output.freq=freqDomainHRV(dIBI,settings.VLF,settings.LF, ...
        settings.HF,settings.AROrder,settings.WinWidth, ...
        settings.WinOverlap,settings.Points,settings.Interp);

    %Nonlinear (using non-detrented ibi)
    output.nl=nonlinearHRV(nIBI,settings.m,settings.r, ...
        settings.n1,settings.n2,settings.breakpoint);
    %Poincare
    output.poincare=poincareHRV(nIBI);

    %Time-Freq
    output.tf=timeFreqHRV(dIBI,nIBI,settings.VLF,settings.LF, ...
        settings.HF,settings.AROrder, settings.tfWinSize, ...
        settings.tfOverlap,settings.Points,settings.Interp, ...
        {'ar','lomb','wavelet'});
    %%%%%%%%%%%%%%%%%%%%%%%

    clear  nIBI dIBI IBI

end

function ibi=loadIBI(f,opt)
    if ~exist(f,'file')
        error(['Error opening file: ' f])
        return
    end

    ibi=[];
    DELIMITER = ',';
    HEADERLINES = opt.headerSize;

    %read ibi
    tmpData = importdata(f, DELIMITER, HEADERLINES);
    if HEADERLINES>0
        tmpData=tmpData.data;
    end

    %check ibi dimentions
    [rows cols] = size(tmpData);
    if rows==1
        tmpData=tmpData';
        ibi=zeros(cols,2);
        tmp=cumsum(tmpData);
        ibi(2:end,1)=tmp(1:end-1);
        ibi(:,2)=tmpData;
    elseif cols==1
        ibi=zeros(rows,2);
        tmp=cumsum(tmpData);
        ibi(2:end,1)=tmp(1:end-1);
        ibi(:,2)=tmpData;
    elseif rows<cols
        ibi=tmpData';
    else
        ibi=tmpData;
    end
    clear tmpData
end


%KWAVETRANSDUCER   Class definition for k-Wave transducer
%
% DESCRIPTION:
%       See makeTransducer for function arguments.
%
% ABOUT:
%       author          - Bradley Treeby
%       date            - 9th December 2010
%       last update     - 4th November 2013
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave 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.
%
% k-Wave 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 Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

% Define as a handle class - A handle class constructor returns a handle
% object that is a reference to the object created. You can assign the
% handle object to multiple variables or pass it to functions without
% causing MATLAB to make a copy of the original object. A function that
% modifies a handle object passed as an input argument does not need to
% return the object.
classdef kWaveTransducer < handle

    % define the properties of the transducer that cannot be modified by
    % the user after the transducer is initialised (these parameters are
    % stored). The numbers assigned here are the default values used if the
    % parameters are not set explicitly by the user.
    properties (GetAccess = 'public', SetAccess = 'private')

        % the total number of transducer elements
        number_elements = 128;

        % the width of each element in grid points
        element_width = 1;

        % the length of each element in grid points
        element_length = 20;

        % the spacing (kerf width) between the transducer elements in grid
        % points
        element_spacing = 0;

        % the position of the corner of the transducer in the grid
        position = [1, 1, 1];

        % the radius of curvature of the transducer [m]
        radius = inf;

    end

    % define the properties of the transducer that can be modified by the
    % user after the transducer is initialised (these parameters are
    % stored). The numbers assigned here are the default values used if the
    % parameters are not set explicitly by the user.
    properties (Access = 'public')

        % the transducer elements that are currently active elements
        active_elements;

        % the focus depth in the elevation direction [m]
        elevation_focus_distance = inf;

        % transmit apodization
        transmit_apodization = 'Rectangular';

        % receive apodization
        receive_apodization = 'Rectangular';

        % sound speed used to calculate beamforming delays [m/s]
        sound_speed = 1540;

        % focus distance used to calculate beamforming delays [m]
        focus_distance = inf;

        % time index to start recording if transducer is used as a sensor
        record_start_index = 1;

    end

    % define the dependent properties (these parameters are computed when
    % queried). Some of these properties act like normal user-modifiable
    % properties (e.g., steering_angle), but instead have explicit set and
    % get functions that change an internal stored value. This allows error
    % checking when the properties are changed.
    properties(Dependent = true)

        % binary mask of the active transducer elements
        active_elements_mask;

        % indexed mask of the active transducer elements, where the index
        % indicates the transducer element that each grid point belongs to.
        indexed_active_elements_mask;

        % binary mask of all the transducer elements (both active and
        % inactive)
        all_elements_mask;

        % indexed mask of all the transducer elements, where the index
        % indicates the transducer element that each grid point belongs to.
        indexed_elements_mask;

        % pseudonym for the active_elements_mask to maintain compatability
        % when the transducer is used in place of sensor or source.
        mask;

        % total width of the transducer in grid points
        transducer_width;

        % current number of active transducer elements
        number_active_elements;

        % user defined input signal with appended zeros dependent on the
        % beamforming (steering_angle, focus_distance) settings
        input_signal;

        % maximum steering angle - this sets the beamforming delay offset
        steering_angle_max;

        % number of zeros appended to input signal to allow a single time
        % series to be used within kspaceFirstOrder3D (either set to 'auto'
        % or based on the setting for steering_angle_max)
        appended_zeros;

        % offset used to make all the delays in the delay_mask positive
        % (either set to 'auto' or based on the setting for
        % steering_angle_max)
        beamforming_delays_offset;

        % steering angle used to calculate beamforming delays [deg]
        steering_angle;

    end

    % define the hidden properties - these cannot be seen or accessed
    % directly by the user, and are used to store internal properties
    properties (Hidden = true, Access = 'private')

        % indexed sensor mask from which the other masks are computed
        indexed_mask;

        % indexed mask for the voxels in each element used to compute
        % elevation focussing
        indexed_element_voxel_mask;

        % size of the grid in which the transducer is defined
        stored_grid_size;

        % corresponding grid spacing
        grid_spacing;

        % time spacing
        dt;

        % original copy of the user defined input signal
        stored_input_signal;

        % stored value of appended_zeros (accessed using get and
        % set methods). This is used to set the number of zeros that are
        % appended and prepended to the input signal.
        stored_appended_zeros = 'auto';

        % stored value of the minimum beamforming delay. This is used to
        % offset the delay mask so that all the delays are >= 0
        stored_beamforming_delays_offset = 'auto';

        % stored value of the steering_angle_max (accessed using get and
        % set methods). This can be set by the user and is used to derive
        % the two parameters above.
        stored_steering_angle_max = 'auto';

        % stored value of the steering_angle (accessed using get and
        % set methods)
        stored_steering_angle = 0;

    end

    % constructor function
    methods
        function transducer = kWaveTransducer(kgrid, transducer_properties)

            % allocate the grid size and spacing
            transducer.stored_grid_size = [kgrid.Nx, kgrid.Ny, kgrid.Nz];
            transducer.grid_spacing = [kgrid.dx, kgrid.dy, kgrid.dz];

            % allocate the temporal spacing
            if isnumeric(kgrid.dt)
                transducer.dt = kgrid.dt;
            elseif isnumeric(kgrid.t_array)
                transducer.dt = kgrid.t_array(2) - kgrid.t_array(1);
            else
                error('kgrid.dt or kgrid.t_array must be explicitly defined');
            end

            % check for properties input
            if nargin == 1
                transducer_properties = [];
            end

            % check the input fields
            checkFieldNames(transducer_properties, {...
                'number_elements', 'element_width', 'element_length', 'element_spacing',...
                'position', 'radius', ...
                'active_elements', 'elevation_focus_distance', 'focus_distance', 'steering_angle',...
                'receive_apodization', 'transmit_apodization',...
                'sound_speed', 'input_signal', 'steering_angle_max'});

            % replace default settings with user defined properties if
            % given
            % -------------------------------------------------------------

            if isfield(transducer_properties, 'number_elements')
                % force value to be a positive integer
                transducer.number_elements = round(abs(transducer_properties.number_elements));
            end

            if isfield(transducer_properties, 'element_width')
                % force value to be a positive integer
                transducer.element_width = round(abs(transducer_properties.element_width));
            end

            if isfield(transducer_properties, 'element_length')
                % force value to be a positive integer
                transducer.element_length = round(abs(transducer_properties.element_length));
            end

            if isfield(transducer_properties, 'element_spacing')
                % force value to be a positive integer
                transducer.element_spacing = round(abs(transducer_properties.element_spacing));
            end

            if isfield(transducer_properties, 'position')
                % force values to be positive integers
                transducer.position = round(abs(transducer_properties.position));
            end

            if isfield(transducer_properties, 'radius')
                transducer.radius = transducer_properties.radius;

                % only allow an infinite radius for now
                if ~isinf(transducer.radius)
                    error('Only a value of transducer.radius = inf is currently supported');
                end
            end

            if isfield(transducer_properties, 'active_elements')
                transducer.active_elements = transducer_properties.active_elements;
            else
                transducer.active_elements = ones(transducer_properties.number_elements, 1);
            end

            if isfield(transducer_properties, 'elevation_focus_distance')
                transducer.elevation_focus_distance = transducer_properties.elevation_focus_distance;
            end

            if isfield(transducer_properties, 'receive_apodization')
                % if a user defined apodization is given explicitly, check
                % the length of the input
                if isnumeric(transducer_properties.receive_apodization) ...
                        && (length(transducer_properties.receive_apodization) ~= transducer.number_active_elements)
                    error('The length of the receive apodization input must match the number of active elements');
                end

                % assign the input
                transducer.receive_apodization = transducer_properties.receive_apodization;
            end

            if isfield(transducer_properties, 'transmit_apodization')
                % if a user defined apodization is given explicitly, check
                % the length of the input
                if isnumeric(transducer_properties.transmit_apodization) ...
                        && (length(transducer_properties.transmit_apodization) ~= transducer.number_active_elements)
                    error('The length of the transmit apodization input must match the number of active elements');
                end

                % assign the input
                transducer.transmit_apodization = transducer_properties.transmit_apodization;
            end

            if isfield(transducer_properties, 'sound_speed')
                transducer.sound_speed = transducer_properties.sound_speed;

                % check to see the sound_speed is positive
                if ~(transducer.sound_speed> 0)
                    error('transducer.sound_speed must be greater than 0');
                end
            end

            if isfield(transducer_properties, 'focus_distance')
                transducer.focus_distance = transducer_properties.focus_distance;
            end

            if isfield(transducer_properties, 'input_signal')
                transducer.stored_input_signal = transducer_properties.input_signal;

                % force the input signal to be a column vector
                if numDim(transducer.input_signal) == 1
                    transducer.stored_input_signal = reshape(transducer.stored_input_signal, [], 1);
                else
                    error('transducer.input_signal must be a one-dimensional array');
                end
            end

            if isfield(transducer_properties, 'steering_angle_max')
                % set the maximum steering angle using the set method (this
                % avoids having to duplicate error checking)
                transducer.steering_angle_max = transducer_properties.steering_angle_max;
            end

            if isfield(transducer_properties, 'steering_angle')
                % set the maximum steering angle using the set method (this
                % avoids having to duplicate error checking)
                transducer.steering_angle = transducer_properties.steering_angle;
            end

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

            % check the transducer fits into the grid
            if sum(transducer.position(:) == 0)
                error('The defined transducer must be positioned within the grid');
            elseif (transducer.position(2) + transducer.number_elements*transducer.element_width + (transducer.number_elements - 1)*transducer.element_spacing) > transducer.stored_grid_size(2)
                error('The defined transducer is too large or positioned outside the grid in the y-direction');
            elseif (transducer.position(3) + transducer.element_length) > transducer.stored_grid_size(3)
                transducer.position(3)
                transducer.element_length
                transducer.stored_grid_size(3)
                error('The defined transducer is too large or positioned outside the grid in the z-direction');
            elseif transducer.position(1) > transducer.stored_grid_size(1)
                error('The defined transducer is positioned outside the grid in the x-direction');
            end

            % assign the data type for the transducer matrix based on the
            % number of different elements (uint8 supports 255 numbers so
            % most of the time this data type will be used)
            if transducer.number_elements < intmax('uint8');
                mask_type = 'uint8';
            elseif transducer.number_elements < intmax('uint16');
                mask_type = 'uint16';
            elseif transducer.number_elements < intmax('uint32');
                mask_type = 'uint32';
            else
                mask_type = 'uint64';
            end

            % create an empty transducer mask (the grid points within
            % element 'n' are all given the value 'n')
            transducer.indexed_mask = zeros(transducer.stored_grid_size, mask_type);

            % create a second empty mask used for the elevation beamforming
            % delays (the grid points across each element are numbered 1 to
            % M, where M is the number of grid points in the elevation
            % direction)
            transducer.indexed_element_voxel_mask = zeros(transducer.stored_grid_size, mask_type);

            % create the corresponding indexing variable 1:M
            element_voxel_index = repmat(1:transducer.element_length, transducer.element_width, 1);

            % for each transducer element, calculate the grid point indices
            for element_index = 1:transducer.number_elements

                % assign the current transducer position
                element_pos_x = transducer.position(1);
                element_pos_y = transducer.position(2) + (transducer.element_width + transducer.element_spacing)*(element_index - 1);
                element_pos_z = transducer.position(3);

                % assign the grid points within the current element the
                % index of the element
                transducer.indexed_mask(element_pos_x, element_pos_y:element_pos_y + transducer.element_width - 1, element_pos_z:element_pos_z + transducer.element_length - 1) = element_index;

                % assign the individual grid points an index corresponding
                % to their order across the element
                transducer.indexed_element_voxel_mask(element_pos_x, element_pos_y:element_pos_y + transducer.element_width - 1, element_pos_z:element_pos_z + transducer.element_length - 1) = element_voxel_index;

            end

            % double check the transducer fits within the desired grid size
            if any(size(transducer.indexed_elements_mask) ~= transducer.stored_grid_size)
                error('Desired transducer is larger than the input grid_size');
            end

        end
    end

    % set and get functions for dependent variables
    methods

        % return input signal
        function signal = get.input_signal(obj)

            % copy the stored input signal
            signal = obj.stored_input_signal;

            % check the signal is not empty
            if isempty(signal)
                error('Transducer input signal is not defined');
            end

            % automatically prepend and append zeros if the beamforming
            % delay offset is set

            % check if the beamforming delay offset is set. If so, use this
            % number to prepend and append this number of zeros to the
            % input signal. Otherwise, calculate how many zeros are needed
            % and prepend and append these.
            if ~strcmp(obj.stored_appended_zeros, 'auto')

                % use the current value of the beamforming offset to add
                % zeros to the input signal
                signal = [zeros(obj.stored_appended_zeros, 1); signal; zeros(obj.stored_appended_zeros, 1)];

            else

                % get the current delay beam forming
                delay_mask = obj.delay_mask;

                % find the maximum delay
                delay_max = max(delay_mask(:));

                % count the number of leading zeros in the input signal
                leading_zeros = find(signal ~= 0, 1) - 1;

                % count the number of trailing zeros in the input signal
                trailing_zeros = find(flipud(signal) ~= 0, 1) - 1;

                % check the number of leading zeros is sufficient given the
                % maximum delay
                if leading_zeros < delay_max + 1
                    disp(['  prepending transducer.input_signal with ' num2str(delay_max - leading_zeros + 1) ' leading zeros']);

                    % prepend extra leading zeros
                    signal = [zeros(delay_max - leading_zeros + 1, 1); signal];
                end

                % check the number of leading zeros is sufficient given the
                % maximum delay
                if trailing_zeros < delay_max + 1
                    disp(['  appending transducer.input_signal with ' num2str(delay_max - trailing_zeros + 1) ' trailing zeros']);

                    % append extra trailing zeros
                    signal = [signal; zeros(delay_max - trailing_zeros + 1, 1)];
                end
            end

        end

        function transducer_width = get.transducer_width(obj)
            % return the overall length of the transducer
            transducer_width = obj.number_elements*obj.element_width + (obj.number_elements - 1)*obj.element_spacing;
        end

        function number_active_elements = get.number_active_elements(obj)
            % return the current number of active elements
            number_active_elements = sum(obj.active_elements(:));
        end

        % allow mask query to allow compatability with regular sensor
        % structure - return the active sensor mask
        function mask = get.mask(obj)
            mask = obj.active_elements_mask;
        end

        % return a binary mask showing the locations of the active elements
        function mask = get.active_elements_mask(obj)

            % copy the indexed elements mask
            mask = obj.indexed_mask;

            % remove inactive elements from the mask
            for element_index = 1:obj.number_elements
                mask(mask == element_index) = obj.active_elements(element_index);
            end

            % convert remaining mask to binary
            mask(mask ~= 0) = 1;

        end

        % return a binary mask showing the locations of all the elements
        % (both active and inactive)
        function mask = get.all_elements_mask(obj)
            mask = obj.indexed_mask;
            mask(mask ~= 0) = 1;
        end

        % return a copy of the indexed elements mask
        function mask = get.indexed_elements_mask(obj)
            mask = obj.indexed_mask;
        end

        % return a copy of the indexed elements mask only for the active
        % elements
        function mask = get.indexed_active_elements_mask(obj)

            % copy the indexed elements mask
            mask = obj.indexed_mask;

            % remove inactive elements from the mask
            for element_index = 1:obj.number_elements
                if ~(obj.active_elements(element_index))
                    mask(mask == element_index) = 0;
                end
            end

            % force the lowest element index to be 1
            lowest_active_element_index = find(obj.active_elements, 1);
            mask(mask ~= 0) = mask(mask ~= 0) - lowest_active_element_index + 1;

        end

        % return the stored value of the steering angle
        function steering_angle = get.steering_angle(obj)
            steering_angle = obj.stored_steering_angle;
        end

        % set the stored value of the steering angle
        function set.steering_angle(obj, steering_angle)

            % force to be scalar
            steering_angle = steering_angle(1);

            % check if the steering angle is between -90 and 90
            if ~(steering_angle < 90 && steering_angle > -90)
                error('Input for steering_angle must be betweeb -90 and 90 degrees');
            end

            % check if the steering angle is less than the maximum steering
            % angle
            if ~strcmp(obj.stored_steering_angle_max, 'auto') && (abs(steering_angle) > obj.stored_steering_angle_max)
                error('Input for steering_angle cannot be greater than steering_angle_max');
            end

            % update the stored value
            obj.stored_steering_angle = steering_angle;

        end

        % return the stored value of the maximum steering angle
        function steering_angle_max = get.steering_angle_max(obj)
            steering_angle_max = obj.stored_steering_angle_max;
        end

        % set the stored value of the maximum steering angle and
        % beamforming offset
        function set.steering_angle_max(obj, steering_angle_max)

            % force input to be scalar and positive
            steering_angle_max = steering_angle_max(1);

            % check the steering angle is within range
            if ~(steering_angle_max < 90 && steering_angle_max > -90)
                error('Input for steering_angle_max must be between -90 and 90');
            end

            % check the maximum steering angle is greater than the current
            % steering angle
            if ~strcmp(obj.stored_steering_angle_max, 'auto') && (abs(obj.stored_steering_angle) > steering_angle_max)
                error('Input for steering_angle_max cannot be less than the current steering_angle');
            end

            % overwrite the stored value
            obj.stored_steering_angle_max = steering_angle_max;

            % store a copy of the current value for the steering angle
            current_steering_angle = obj.stored_steering_angle;

            % overwrite with the user defined maximum value
            obj.stored_steering_angle = steering_angle_max;

            % set the beamforming delay offset to zero (this means the
            % delays will contain negative values which we can use to
            % derive the new values for the offset)
            obj.stored_appended_zeros = 0;
            obj.stored_beamforming_delays_offset = 0;

            % get the element beamforming delays and reverse
            delay_times = -obj.beamforming_delays;

            % get the maximum and minimum beamforming delays
            max_delay = max(delay_times);
            min_delay = min(delay_times);

            % add the maximum and minimum elevation delay if the elevation
            % focus is not set to infinite
            if ~isinf(obj.elevation_focus_distance)
                max_delay = max_delay + max(obj.elevation_beamforming_delays);
                min_delay = min_delay + min(obj.elevation_beamforming_delays);
            end

            % set the beamforming offset to the difference between the
            % maximum and minimum delay
            obj.stored_appended_zeros = max_delay - min_delay;

            % set the minimum beamforming delay (converting to a positive
            % number)
            obj.stored_beamforming_delays_offset = - min_delay;

            % reset the previous value of the steering angle
            obj.stored_steering_angle = current_steering_angle;

        end

        % return the stored value of appended zeros
        function appended_zeros = get.appended_zeros(obj)
            appended_zeros = obj.stored_appended_zeros;
        end

        % return the stored value of the offset used to force the values in
        % delay_mask to be >= 0
        function beamforming_delays_offset = get.beamforming_delays_offset(obj)
            beamforming_delays_offset = obj.stored_beamforming_delays_offset;
        end

    end

    % general class methods
    methods

        % calculate the beamforming delays based on the focus and steering
        % settings
        function delay_times = beamforming_delays(obj)

            % calculate the element pitch in [m]
            element_pitch = (obj.element_spacing + obj.element_width)*obj.grid_spacing(2);

            % create indexing variable
            element_index = -(obj.number_active_elements - 1)/2:(obj.number_active_elements - 1)/2;

            % check for focus depth
            if isinf(obj.focus_distance)
                % calculate time delays for a steered beam
                delay_times = element_pitch*element_index*sin(obj.steering_angle*pi/180)/(obj.sound_speed);        % [s]
            else
                % calculate time delays for a steered and focussed beam
                delay_times = obj.focus_distance/obj.sound_speed * (1 - sqrt(...
                    1 ...
                    + (element_index*element_pitch./obj.focus_distance).^2 ...
                    - 2 * (element_index*element_pitch./obj.focus_distance) * sin(obj.steering_angle*pi/180) ...
                    ));      % [s]
            end

            % convert the delays to be in units of time points
            delay_times = round(delay_times/obj.dt);

        end

        % calculate the elevation beamforming delays based on the focus
        % setting
        function delay_times = elevation_beamforming_delays(obj)

            if ~isinf(obj.elevation_focus_distance)

                % create indexing variable
                element_index = -(obj.element_length - 1)/2:(obj.element_length - 1)/2;

                % calculate time delays for a focussed beam
                delay_times = (obj.elevation_focus_distance - sqrt( (element_index*obj.grid_spacing(3)).^2 + obj.elevation_focus_distance^2 ))./obj.sound_speed;

                % convert the delays to be in units of time points and then
                % reverse
                delay_times = -round(delay_times/obj.dt);

            else

                % create an empty array
                delay_times = zeros(1, obj.element_length);

            end

        end

        % function to create a scan line based on the input sensor data and
        % the current apodization and beamforming setting
        function line = scan_line(obj, sensor_data)

            % get the current apodization setting
            apodization = obj.get_receive_apodization;

            % get the current beamforming weights and reverse
            delays = -obj.beamforming_delays;

            % offset the received sensor_data by the beamforming delays and
            % apply receive apodization
            for element_index = 1:obj.number_active_elements
                if delays(element_index) > 0
                    % shift element data forwards
                    sensor_data(element_index, :) = apodization(element_index).*[sensor_data(element_index, 1 + delays(element_index):end), zeros(1, delays(element_index))];
                elseif delays(element_index) < 0
                    % shift element data backwards
                    sensor_data(element_index, :) = apodization(element_index).*[zeros(1, -delays(element_index)), sensor_data(element_index, 1:end + delays(element_index))];
                end
            end

            % form the a-line summing across the elements
            line = sum(sensor_data);

        end

        % function to average the individual time series recorded at each
        % grid point of each transducer element (as returned by the C++
        % code) and return a single time series per active transducer
        % element (as returned by the MATLAB code)
        function sensor_data_sum = combine_sensor_data(obj, sensor_data)

            % check the data is the correct size
            if size(sensor_data, 1) ~= (obj.number_active_elements * obj.element_width * obj.element_length)
                error('The number of time series in the input sensor_data must match the number of grid points in the active tranducer elements.');
            end

            % get index of which element each time series belongs to
            ind = obj.indexed_active_elements_mask(obj.indexed_active_elements_mask > 0);

            % create empty output
            sensor_data_sum = zeros(obj.number_active_elements, size(sensor_data, 2));

            % check if an elevation focus is set
            if isinf(obj.elevation_focus_distance)

                % loop over time series and sum
                for ii = 1:length(ind)
                    sensor_data_sum(ind(ii), :) = sensor_data_sum(ind(ii), :) + sensor_data(ii, :);
                end

            else

                % get the elevation delay for each grid point of each
                % active transducer element (this is given in units of grid
                % points)
                dm = obj.delay_mask(2);
                dm = dm(obj.active_elements_mask ~= 0);

                % loop over time series, shift and sum
                for ii = 1:length(ind)
                    sensor_data_sum(ind(ii), 1:end - dm(ii)) = sensor_data_sum(ind(ii), 1:end - dm(ii)) + sensor_data(ii, 1 + dm(ii):end);
                end

            end

            % divide by number of time series in each element
            sensor_data_sum = sensor_data_sum .* (1/ (obj.element_width * obj.element_length));

        end

        % plot the transducer using voxelPlot
        function plot(obj)
            voxelPlot(double(obj.all_elements_mask));
        end

        % print out the transducer properties
        function properties(obj)
            disp(' ');
            disp('k-Wave Transducer Properties');
            disp(['  transducer position:       [' num2str(obj.position) ']']);
            disp(['  transducer width:          ' scaleSI(obj.transducer_width*obj.grid_spacing(2)) 'm (' num2str(obj.transducer_width) ' grid points)']);
            disp(['  number of elements:        ' num2str(obj.number_elements)]);
            disp(['  number of active elements: ' num2str(obj.number_active_elements) ' (elements ' num2str(find(obj.active_elements, 1, 'first')) ' to ' num2str(find(obj.active_elements, 1, 'last')) ')']);
            disp(['  element width:             ' scaleSI(obj.element_width*obj.grid_spacing(2)) 'm (' num2str(obj.element_width) ' grid points)']);
            disp(['  element spacing (kerf):    ' scaleSI(obj.element_spacing*obj.grid_spacing(2)) 'm (' num2str(obj.element_spacing) ' grid points)']);
            disp(['  element pitch:             ' scaleSI((obj.element_spacing + obj.element_width)*obj.grid_spacing(2)) 'm (' num2str((obj.element_spacing + obj.element_width)) ' grid points)']);
            disp(['  element length:            ' scaleSI(obj.element_length*obj.grid_spacing(3)) 'm (' num2str(obj.element_length) ' grid points)']);
            disp(['  sound speed:               ' num2str(obj.sound_speed) 'm/s']);
            if isinf(obj.focus_distance)
                disp('  focus distance:             infinite');
            else
                disp(['  focus distance:            ' scaleSI(obj.focus_distance) 'm']);
            end
            if isinf(obj.elevation_focus_distance)
                disp('  elevation focus distance:  infinite');
            else
                disp(['  elevation focus distance:  ' scaleSI(obj.elevation_focus_distance) 'm']);
            end
            disp(['  steering angle:            ' num2str(obj.steering_angle) ' degrees']);
            disp(['  steering angle max:        ' num2str(obj.steering_angle_max) ' degrees']);
        end

    end

    % hidden class methods used with kspaceFirstOrder3D
    methods (Hidden = true, Access = 'public')

        % return a delay mask based on the transmit beamforming delays.
        % This is used if the transducer is a source. The delays are set
        % for each grid point of the active_elements_mask and define which
        % element of the input_signal should be used at each source point.
        % This mask is incremented within kspaceFirstOrder3D after each
        % time step. The mask is indexed from 0 and is incremented by 1
        % within kspaceFirstOrder3D .
        function mask = delay_mask(obj, mode)
            % mode == 1: both delays
            % mode == 2: elevation only
            % mode == 3: azimuth only

            % assign the delays to a new mask using the indexed_element_mask
            indexed_active_elements_mask = obj.indexed_active_elements_mask;
            active_elements_index = find(indexed_active_elements_mask ~= 0);
            mask = zeros(obj.stored_grid_size);

            % calculate azimuth focus delay times provided they are not all
            % zero
            if (~isinf(obj.focus_distance) || (obj.steering_angle ~= 0)) && (nargin == 1 || mode ~= 2)

                % get the element beamforming delays and reverse
                delay_times = -obj.beamforming_delays;

                % add delay times
                mask(active_elements_index) = delay_times(indexed_active_elements_mask(active_elements_index));

            end

            % calculate elevation focus time delays provided each element
            % is longer than one grid point
            if ~isinf(obj.elevation_focus_distance) && (obj.element_length > 1) && (nargin == 1 || mode ~= 3)

                % get elevation beamforming delays
                elevation_delay_times = obj.elevation_beamforming_delays;

                % get current mask
                element_voxel_mask = obj.indexed_element_voxel_mask;

                % add delay times
                mask(active_elements_index) = mask(active_elements_index) + elevation_delay_times(element_voxel_mask(active_elements_index)).';

            end

            % shift delay times (these should all be >= 0, where a value of
            % 0 means no delay)
            if strcmp(obj.stored_appended_zeros, 'auto')
                mask(active_elements_index) = mask(active_elements_index) - min(min(min(mask(active_elements_index))));
            else
                mask(active_elements_index) = mask(active_elements_index) + obj.stored_beamforming_delays_offset;
            end

        end

        % return a binary mask based on the elevation focus. This sets the
        % elements to extract at each timestep from the sensor_data_buffer
        % used in kspaceFirstOrder3D. This is used if the transducer is a
        % sensor and the elevation focus is set. The mask is of the form:
        %
        %   1 0 0 0
        %   0 1 0 0
        %   0 0 1 0
        %   0 0 1 0
        %   0 0 0 1
        %   0 0 0 1
        %   0 0 0 1
        %   0 0 1 0
        %   0 0 1 0
        %   0 1 0 0
        %   1 0 0 0
        %
        % The mask remains constant, but extracts new elements at each time
        % step as the FIFO buffer sensor_data_buffer is updated
        function mask = elevation_beamforming_mask(obj)

            % get elevation beamforming mask
            delay_mask = obj.delay_mask(2);

            % extract the active elements
            delay_mask = delay_mask(obj.active_elements_mask ~= 0);

            % force delays to start from zero
            delay_mask = delay_mask - min(delay_mask(:));

            % create an empty output mask
            mask = zeros(length(delay_mask), max(delay_mask) + 1);

            % populate the mask by setting 1's at the index given by the
            % delay time
            for index = 1:length(delay_mask)
                mask(index, delay_mask(index) + 1) = 1;
            end

            % flip the mask so the shortest delays are at the right
            mask = fliplr(mask);
        end

        % convert the transmit apodization into the form of a element mask,
        % where the apodization values are placed at the grid points
        % belonging to the active transducer elements. These values are
        % then extracted in the correct order within
        % kspaceFirstOrder_inputChecking using apodization =
        % transmit_apodization_mask(active_elements_mask ~= 0)
        function mask = transmit_apodization_mask(obj)

            % check if a user defined apodization is given and whether this
            % is still the correct size (in case the number of active
            % elements has changed)
            if isnumeric(obj.transmit_apodization)
                if (length(obj.transmit_apodization) ~= obj.number_active_elements)
                    error('The length of the transmit apodization input must match the number of active elements');
                else
                    % assign apodization
                    apodization = obj.transmit_apodization;
                end
            else
                % create apodization using getWin
                apodization = getWin(obj.number_active_elements, obj.transmit_apodization);
            end

            % create an empty mask;
            mask = zeros(obj.stored_grid_size);

            % assign the apodization values to every grid point in the
            % transducer
            mask_index = obj.indexed_active_elements_mask;
            mask_index = mask_index(mask_index ~= 0);
            mask(obj.active_elements_mask == 1) = apodization(mask_index);

        end

        % allow the mask to be resized by the simulation functions (this is
        % used when 'PMLInside' is set to false)
        function expand_grid(obj, expand_size)
            obj.indexed_mask = expandMatrix(obj.indexed_mask, expand_size, 0);
        end

        % allow the mask to be resized by the simulation functions (this is
        % used when 'PMLInside' is set to false)
        function retract_grid(obj, retract_size)
            obj.indexed_mask = obj.indexed_mask(1 + retract_size(1):end - retract_size(1), 1 + retract_size(2):end - retract_size(2), 1 + retract_size(3):end - retract_size(3));
        end

        % return the size of the grid
        function grid_size = grid_size(obj)
            grid_size = obj.stored_grid_size;
        end

    end

    % internal class methods only accessible by other functions within
    % kWaveTransducer
    methods (Hidden = true, Access = 'private')

        % return the transmit apodization, converting strings of window
        % type to actual numbers using getWin
        function apodization = get_transmit_apodization(obj)

            % check if a user defined apodization is given and whether this
            % is still the correct size (in case the number of active
            % elements has changed)
            if isnumeric(obj.transmit_apodization)
                if (length(obj.transmit_apodization) ~= obj.number_active_elements)
                    error('The length of the transmit apodization input must match the number of active elements');
                else
                    % assign apodization
                    apodization = obj.transmit_apodization;
                end
            else
                % create apodization using getWin
                apodization = getWin(obj.number_active_elements, obj.transmit_apodization);
            end
        end

        % return the receive apodization, converting strings of window
        % type to actual numbers using getWin
        function apodization = get_receive_apodization(obj)

            % check if a user defined apodization is given and whether this
            % is still the correct size (in case the number of active
            % elements has changed)
            if isnumeric(obj.receive_apodization)
                if (length(obj.receive_apodization) ~= obj.number_active_elements)
                    error('The length of the receive apodization input must match the number of active elements');
                else
                    % assign apodization
                    apodization = obj.receive_apodization;
                end
            else
                % create apodization using getWin
                apodization = getWin(obj.number_active_elements, obj.receive_apodization);
            end
        end

    end
end


%   FUZZY RULE BASE COMPRESSION SOFTWARE
%
%   This software implements advanced inference in fuzzy rule based systems
%   by filtration of non-monotonic rules. This software is for mandani type
%   system only.
%
%
%   Neelamugilan Gobalakrishnan
%   2006
%   $Revision:1.0 $ $Date: 15/03/2006 $

clc
clear all

disp('***********************************************************');
disp('*                                                         *');
disp('*         FUZZY RULE BASE COMPRESSION SOFTWARE            *');
disp('*                                                         *');
disp('*     This software implements advanced inference in      *');
disp('*     fuzzy rule based systems by filtration of non-      *');
disp('*     monotonic rules.                                    *');
disp('*                                                         *');
disp('***********************************************************');

fprintf('\n\n');

condStatus = 'start'; %Condition status for choice while loop

while strcmp(condStatus,'start') | strcmp(condStatus,'cont')
    clear choise
    fprintf('Enter 1 to create a new system\n');
    fprintf('Enter 2 to load a system (*.fis)\n\n');
    fprintf('Please enter your choice : ');
    choise = input('');
    if choise == 1 % Create a new system
        fprintf('\n');
        systemName = input('Enter the name of this system : ','s');
        % Call createinttable function to create integer table
        [sys intTable] = createinttable(systemName);
        % Arranged the rules into groups
        [groupedTable outNum] = arrangerules(sys,intTable);

        % Get the initial values for each inputs
        for i=1:size(sys.input, 2)
            fprintf('\nEnter the initial %s : ', sys.input(i).name);
            initialVal(i) = input('');
        end

        displayOpt = 'on'; % Option to display the compressed rules
        % Find dominant rules for provided initial values
        compressRules = finddomrules(sys,intTable,outNum,initialVal,displayOpt);

        newSysName = input('Please enter the name for compressed system : ','s');
        fprintf('\n');
        % Create the system with compressed rules
        createfis(newSysName,sys,compressRules);

        % Simulates the fuzzy inference system
        a = readfis(newSysName);
        outResult = evalfis([initialVal],a);

        fprintf('\n');
        fprintf('The %s value for \n', sys.output(outNum).name);

        for i=1:size(sys.input, 2)
            fprintf('%s(%.2f) \n',sys.input(i).name ,initialVal(i));
        end

        fprintf('= %.2f', outResult);
        fprintf('\n\n');

        view_surface = input('Do u want to view the output surface (yes/no) : ','s');

        % Display the output surface
        while strcmp(view_surface,'yes')
            fprintf('\nEnter 1 to view the original system, output surface\n');
            fprintf('Enter 2 to view the compressed system, output surface\n\n');
            fprintf('Please enter your option : ');
            option = input('');
            if option == 1
                % Create output surface for the original system
                createoutsurf(sys,systemName,intTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or compressed system? (yes/no) : ','s');
            elseif option == 2
                % Create output surface for the compressed system
                createoutsurf(sys,newSysName,intTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or original system? (yes/no) : ','s');
            else
                disp('Invalid value has been entered! ');
                fprintf('\n');
                view_surface = 'yes';
            end
        end
        condStatus = 'stop'; % choice while loop stopping condition
        % Load a system
    elseif choise == 2
        [fileName,pathName]=uigetfile('*.fis','Read FIS');
        if isequal(fileName,0) || isequal(pathName,0)
          % If fileName is zero, "cancel" was hit, or there was an error.
          errorStr='No file was loaded';
          return
        end
        systemName = [pathName fileName];

        sys = readfis(systemName);

        % Create the integer table
        for i=1:size(sys.rule,2)
            intTable(i,:) = [sys.rule(i).antecedent sys.rule(i).consequent];
        end

        fprintf('\n');
        disp('-------------------------------------------------------------');
        fprintf(' ');
        disp('                    Complete integer table                   ');
        fprintf(' ');
        disp('-------------------------------------------------------------');

        fprintf('\n');
        disp('-------------------------------------------------------------');
        fprintf(' ');

        for i=1:size(sys.input,2)
            fprintf('Input%d ', i);
        end

        for i=1:size(sys.output,2)
            fprintf('Output%d ', i);
        end

        fprintf('\n');
        disp('-------------------------------------------------------------');
        fprintf('\n');
        disp(intTable);
        disp('-------------------------------------------------------------');
        fprintf('\n');

        % Arranged the rules into groups
        [groupedTable outNum] = arrangerules(sys,intTable);

        % Get the initial values for each inputs
        for i=1:size(sys.input, 2)
            fprintf('\nEnter the initial %s : ', sys.input(i).name);
            initialVal(i) = input('');
        end

        displayOpt = 'on'; % Option to display the compressed rules
        % Find dominant rules for provided initial values
        compressRules = finddomrules(sys,intTable,outNum,initialVal,displayOpt);

        fprintf('\n');
        newSysName = input('Please enter the name for compressed system : ','s');
        % Create the system with compressed rules
        createfis(newSysName,sys,compressRules);

        % Simulates the fuzzy inference system
        a = readfis(newSysName);
        outResult = evalfis([initialVal],a);

        fprintf('\n');
        fprintf('The %s value for \n', sys.output(outNum).name);

        for i=1:size(sys.input, 2)
            fprintf('%s(%.2f) \n',sys.input(i).name ,initialVal(i));
        end

        fprintf('= %.2f', outResult);
        fprintf('\n\n');

        view_surface = input('Do u want to view the output surface (yes/no) : ','s');

        % Display the output surface
        while strcmp(view_surface,'yes')
            fprintf('\nEnter 1 to view the original system, output surface\n');
            fprintf('Enter 2 to view the compressed system, output surface\n\n');
            fprintf('Please enter your option : ');
            option = input('');
            if option == 1
                % Create output surface for the original system
                createoutsurf(sys,systemName,groupedTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or compressed system? (yes/no) : ','s');
            elseif option == 2
                % Create output surface for the compressed system
                createoutsurf(sys,newSysName,groupedTable,outNum,option);
                fprintf('\nDo u want to view the output surface with\n');
                view_surface = input('new points or original system? (yes/no) : ','s');
            else
                fprintf('\n');
                disp('Invalid value has been entered! ');
                view_surface = 'yes';
            end
        end
        condStatus = 'stop'; % choice while loop stopping condition
    else
        disp('Invalid value has been entered! ');
        condStatus = 'cont'; % Choice while loop continue condition
    end
end


function varargout = optionpricegui2(varargin)
% OPTIONPRICEGUI2 M-file for optionpricegui2.fig
%      OPTIONPRICEGUI2, by itself, creates a new OPTIONPRICEGUI2 or raises the existing
%      singleton*.
%
%      H = OPTIONPRICEGUI2 returns the handle to a new OPTIONPRICEGUI2 or the handle to
%      the existing singleton*.
%
%      OPTIONPRICEGUI2('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in OPTIONPRICEGUI2.M with the given input arguments.
%
%      OPTIONPRICEGUI2('Property','Value',...) creates a new OPTIONPRICEGUI2 or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before optionpricegui2_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to optionpricegui2_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help optionpricegui2

% Last Modified by GUIDE v2.5 14-Aug-2008 14:18:46

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @optionpricegui2_OpeningFcn, ...
    'gui_OutputFcn',  @optionpricegui2_OutputFcn, ...
    'gui_LayoutFcn',  [] , ...
    'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
end
% End initialization code - DO NOT EDIT

% --- Executes just before optionpricegui2 is made visible.
function optionpricegui2_OpeningFcn(hObject, eventdata, handles, varargin)%#ok
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to optionpricegui2 (see VARARGIN)

% Choose default command line output for optionpricegui2
handles.output = hObject;

set(handles.editStart, 'String', datestr(today, 2))
set(handles.editEnd, 'String', datestr(today + 91, 2))

handles = runSimulation(handles);

% This is an awkward place to put the rotate3d command.  If you place it in
% the runSimulation function, however, it tends to toggle rotate3d on and
% off after each simulation.  Here, it gets set to "on" once and stays that
% way.
rotate3d(handles.surfAxes)

% Update handles structure
guidata(hObject, handles);
end

% UIWAIT makes optionpricegui2 wait for user response (see UIRESUME)
% uiwait(handles.mainFigure);

% --- Outputs from this function are returned to the command line.
function varargout = optionpricegui2_OutputFcn(hObject, eventdata, handles) %#ok
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;
end

function editSpot_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editSpot (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editSpot as text
%        str2double(get(hObject,'String')) returns contents of editSpot as a double
end

% --- Executes during object creation, after setting all properties.
function editSpot_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editSpot (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editStrike_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editStrike (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editStrike as text
%        str2double(get(hObject,'String')) returns contents of editStrike as a double
end

% --- Executes during object creation, after setting all properties.
function editStrike_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editStrike (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editRFR_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editRFR (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editRFR as text
%        str2double(get(hObject,'String')) returns contents of editRFR as a double
end

% --- Executes during object creation, after setting all properties.
function editRFR_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editRFR (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editStart_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editStart (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editStart as text
%        str2double(get(hObject,'String')) returns contents of editStart as a double
end

% --- Executes during object creation, after setting all properties.
function editStart_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editStart (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editEnd_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editEnd (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editEnd as text
%        str2double(get(hObject,'String')) returns contents of editEnd as a double
end

% --- Executes during object creation, after setting all properties.
function editEnd_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editEnd (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editVol_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editVol (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editVol as text
%        str2double(get(hObject,'String')) returns contents of editVol as a double
end

% --- Executes during object creation, after setting all properties.
function editVol_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editVol (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editYield_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editYield (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editYield as text
%        str2double(get(hObject,'String')) returns contents of editYield as a double
end

% --- Executes during object creation, after setting all properties.
function editYield_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editYield (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

end

function editIter_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editIter (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editIter as text
%        str2double(get(hObject,'String')) returns contents of editIter as a double
end

% --- Executes during object creation, after setting all properties.
function editIter_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editIter (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

end

function editValue_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editValue (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editValue as text
%        str2double(get(hObject,'String')) returns contents of editValue as a double
end

% --- Executes during object creation, after setting all properties.
function editValue_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editValue (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

% --- Executes on selection change in popupmenu1.
function popupmenu1_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to popupmenu1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = get(hObject,'String') returns popupmenu1 contents as cell array
%        contents{get(hObject,'Value')} returns selected item from popupmenu1

% Some options only require a single strike price; others, two.  The
% butterfly option requires one strike price and a spread.
contents = get(hObject,'String');
switch contents{get(hObject,'Value')}
    case {'Call' 'Put' 'Straddle'}
        handles = oneStrike(handles);
    case {'Strangle' 'Bull Spread' 'Bear Spread'}
        handles = twoStrikes(handles);
    case {'Butterfly'}
        handles = butterfly(handles);
end
guidata(hObject, handles);
end
% --- Executes during object creation, after setting all properties.
function popupmenu1_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to popupmenu1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editStrike1_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editStrike1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editStrike1 as text
%        str2double(get(hObject,'String')) returns contents of editStrike1 as a double
end

% --- Executes during object creation, after setting all properties.
function editStrike1_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editStrike1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editStrike2_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editStrike2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editStrike2 as text
%        str2double(get(hObject,'String')) returns contents of editStrike2 as a double
end

% --- Executes during object creation, after setting all properties.
function editStrike2_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editStrike2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

function editSpread_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to editSpread (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of editSpread as text
%        str2double(get(hObject,'String')) returns contents of editSpread as a double
end

% --- Executes during object creation, after setting all properties.
function editSpread_CreateFcn(hObject, eventdata, handles)%#ok
% hObject    handle to editSpread (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
end

% --- Executes on button press in buttonRun.
function buttonRun_Callback(hObject, eventdata, handles)%#ok
% hObject    handle to buttonRun (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
handles = runSimulation(handles);
guidata(hObject, handles);
end

% --- Helper functions follow ---
% --- oneStrike, twoStrikes: toggle between displaying one and two strike
% prices in the UI panel ---
function handles = oneStrike(handles)
set(handles.text3, 'String', 'Strike Price')
set(handles.editStrike, 'Visible', 'on')
set(handles.editStrike1, 'Visible', 'off')
set(handles.editStrike2, 'Visible', 'off')
set(handles.editSpread, 'Visible', 'off')
set(handles.text11, 'Visible', 'off')
end

function handles = twoStrikes(handles)
set(handles.text3, 'String', 'Strike Prices')
set(handles.editStrike, 'Visible', 'off')
set(handles.editStrike1, 'Visible', 'on')
set(handles.editStrike2, 'Visible', 'on')
set(handles.editSpread, 'Visible', 'off')
set(handles.text11, 'Visible', 'off')
end

function handles = butterfly(handles)
set(handles.text3, 'String', 'Strike Price')
set(handles.editStrike, 'Visible', 'on')
set(handles.editStrike1, 'Visible', 'off')
set(handles.editStrike2, 'Visible', 'off')
set(handles.editSpread, 'Visible', 'on')
set(handles.text11, 'Visible', 'on')
end

%--- runSimulation: the main control program for the code ---
function handles = runSimulation(handles)
% This function has four steps:
% Step 1: Collect inputs and find option value
% Step 2: Plot the option value surface
% Step 3: Run a Monte Carlo simulation
% Step 4: Plot the Monte Carlo simulation
%
% In addition, there are two nested functions that allow you to incorporate
% a WindowButtonMotionFcn and a WindowButtonDownFcn on the Monte Carlo
% Paths axes.

% Step 1: Collect inputs and find option value
try
    spot = str2double(get(handles.editSpot, 'String'));
    rate = str2double(get(handles.editRFR, 'String'));
    vol  = str2double(get(handles.editVol, 'String'));
    yield= str2double(get(handles.editYield, 'String'));
    type = get(handles.popupmenu1,'String');
    type = type{get(handles.popupmenu1,'Value')};

    % No harm in getting Strike2 and Spread, even if they aren't used.
    strike2 = str2double(get(handles.editStrike2, 'String'));
    spread  = str2double(get(handles.editSpread, 'String'));

    startDate = get(handles.editStart, 'String');
    endDate = get(handles.editEnd, 'String');
    % I only include this extra TRY/CATCH loop in order to provide a better
    % error message if the invalid dates are applied: YEARFRAC itself will
    % do all of the appropriate error checking.
    try
        time = yearfrac(startDate, endDate);
    catch %#ok<CTCH>
        error('optionPriceGui:InvalidDates', ...
            'Dates must be in a valid format')
    end

    iter = str2double(get(handles.editIter, 'String'));
    if int32(iter) ~= iter || iter <= 0
        error('optionPriceGui:InvalidIterations',...
            'Iteration count must be a positive integer.')
    end

    switch type
        case {'Call' 'Put' 'Straddle' 'Butterfly'}
            strike = str2double(get(handles.editStrike, 'String'));
        case {'Strangle' 'Bull Spread' 'Bear Spread'}
            strike = str2double(get(handles.editStrike1, 'String'));
    end

    value = optPriceVal(type, spot, strike, rate, time, vol, yield, strike2, spread);
    set(handles.editValue, 'String', num2str(value,'%4.3f'))

catch ME
    errordlg(ME.message, 'Invalid Input', 'modal')
    set(handles.editValue, 'String', 'error')
    return
end

% Step 2: Now that we have all the data we need, let's plot the option
% value surface.

% A 51x51 surface plot should look nice and not be too taxing on the
% graphics processor, but you can adjust that here.
xPoints = 51;
yPoints = 51;

T = linspace(0, time, xPoints);

% The spot price axis span is a little tricky to determine in advance.  We
% will go with +/- 10% from the strike price in the case of a put, call, or
% straddle option, +/- 50% of the strike prices' span in the case of a
% strangle, bull spread, or bear spread option, and +/- 40% of the spread
% from a butterfly option.  Of course, we will truncate if these go below a
% price of 0.
switch type
    case {'Call' 'Put' 'Straddle'}
        lPrice = 0.9 * strike;
        uPrice = 1.1 * strike;
    case {'Strangle' 'Bull Spread' 'Bear Spread'}
        span = abs(strike2 - strike);
        if strike2 > strike
            lPrice = strike - 0.5 * span;
            uPrice = strike2+ 0.5 * span;
        else
            lPrice = strike2- 0.5 * span;
            uPrice = strike + 0.5 * span;
        end
    case 'Butterfly'
        lPrice = (1 - 1.4 * spread) * strike;
        uPrice = (1 + 1.4 * spread) * strike;
end
lPrice(lPrice < 0) = 0;
S = linspace(lPrice, uPrice, yPoints);
[S,T] = meshgrid(S,T);

% Get the option value for the surface
V = optPriceVal(type, S, strike, rate, T, vol, yield, strike2, spread);

% Plot the 3D surface of changing option value with spot price and time
surf(handles.surfAxes, S, T, V);
box(handles.surfAxes, 'on')
shading(handles.surfAxes, 'interp')
axis(handles.surfAxes, 'tight')
xlabel(handles.surfAxes, 'Underlying ($)','fontweight','bold')
ylabel(handles.surfAxes, 'Time to Expiry (Years)','fontweight','bold')
zlabel(handles.surfAxes, 'Option Value ($)','fontweight','bold')

% Step 3: Let's now run a Monte Carlo simulation
% We will run a single step of the simulation per day-- this is fairly
% coarse-grained, but it gets the message across.

% Assume geometric Brownian motion and apply Ito's lemma to simplify the
% Monte Carlo equation. (see: eqn T6.9 of Kevin Dowd's _An Introduction to
% Market Risk Measurement_, p. 224)
numDays = daysact(startDate, endDate);
TT = sqrt(time/numDays) * ones(numDays, iter);
rNum = randn(numDays, iter);

BT = TT.*rNum;
% And we pad this by an initial row of zeros to handle the initial point at
% day 0.
TT = [zeros(1, iter); TT];
BT = [zeros(1, iter); BT];

ST = spot * exp(cumsum((rate - 0.5 * vol^2) * TT + vol * BT));

% Step 4: Plot the Monte Carlo simulation
% Create line/bar object for histogram of monte-carlo price distributions
set(handles.axesHist,'yaxislocation','right','xaxislocation','top',...
    'xtick',[],'xlimmode','auto')

% Only pick at most 1000 lines from the Monte-Carlo simulation for plotting
% purposes; all data is still used for the underlying math, however.
if size(ST,2)>1000
    ind = randperm(size(ST,2))';
    STplot = ST(:,ind(1:1000));
else
    STplot = ST;
end

% Plot the monte-carlo underlying price paths
handles.pricePaths = plot(handles.axesMonte, (0:numDays), STplot,...
    'color', [.7,.7,.7]);

% Create the tracking line.  Rather, create an object to hold the tracking
% line as it is generated by the windowbuttonmotion function.
handles.trackLine = line('XData', NaN, 'YData', NaN,...
    'Parent', handles.axesMonte, 'erasemode', 'xor', 'Color', 'b', 'LineWidth', 2);
handles.trackText = text('parent', handles.axesMonte, 'erasemode','xor','edgecolor','k',...
    'background',[1,1,.87],'verticalalign','bot','fontsize',14, 'visible', 'off');
% handles.pdfLine = line('XData', NaN, 'YData', NaN,...
%     'Parent', handles.axesHist, 'erasemode', 'xor', 'Color', 'r');

% Plot the mean of the underlying monte-carlo prices paths.
STmean = mean(ST,2);
handles.meanLine = line('XData', (0:numDays), 'YData', STmean, ...
    'Parent', handles.axesMonte, 'Marker', '.' ,'MarkerSize', 10, 'Color', 'r');

% Annotate the monte-carlo plots
xlabel(handles.axesMonte, 'Time (Days)','fontweight','bold')
ylabel(handles.axesMonte, 'Underlying ($)','fontweight','bold')
axis(handles.axesMonte, 'tight')
grid(handles.axesMonte, 'on')
legend(handles.axesMonte, ...
    {'$$S_{t+dt} = S_te^{(r-\frac{{\sigma^2}}{2})dt+\sigma\epsilon\sqrt{dt}}$$'}, ...
    'fontsize',14,'interpreter','latex', 'Location', 'NorthWest');

set(handles.mainFigure,'windowbuttonmotionfcn',@wbm)

% Nested function: WBM for the window button motion function
    function wbm(src, event)
        drawnow
        ylimit = get(handles.axesMonte, 'ylim');
        xlimit = get(handles.axesMonte, 'xlim');
        pos =  get(handles.axesMonte, 'currentpoint');
        x = pos(1,1);
        y = pos(1,2);
        % check if the mouse is out of bounds of the axes
        if x < xlimit(1) || x > xlimit(2) || y < ylimit(1) || y>ylimit(2)
            return
        end

        % create a histogram of the underlying price paths at the current point
        binsC = linspace(ylimit(1), ylimit(2)+eps(ylimit(2)), 21);
        n = histc(ST(round(x)+1, :), binsC);
        n = n./max(n);
        barh(handles.axesHist, binsC, n, 'histc')
        set(handles.axesHist, 'ylim', ylimit)
        set(handles.axesHist,'YTick',[])
        set(handles.axesHist,'XTick',[])
        set(handles.trackLine,'xdata',[x,x],'ydata',ylim)

        % Create a maximum likelihood estimate of the histogram, assuming a
        % lognormal distribution.
        params = lognfit(ST(round(x)+1, :));
        pdfPts = ylimit(1) : ylimit(2);
        pdfVals = lognpdf(pdfPts, params(1), params(2));
        line('Parent', handles.axesHist, ...
            'XData', pdfVals./max(pdfVals), 'YData', pdfPts,...
            'erasemode', 'xor', 'color', 'b')

        % get the current datestr and mean value of the mouse position
        currentDate = datestr(round(x + datenum(startDate)), 2);
        currentMean = STmean(round(x)+1);

        str = sprintf(['Date: ' currentDate '\nAvg Price: $%.2f'], currentMean);
        set(handles.trackText,...
            'position',[x + diff(xlimit)*.02, y + diff(ylimit)*.02, 0],...
            'string', str, 'visible', 'on')

    end
end