  function back = my_cbct_back(proj, cg, ig, varargin)
% function back = cbct_back(proj, cg, ig, varargin)
%|
%| cone-beam backprojector for feldkamp.m
%| in
%|	proj	[ns nt na]	cone-beam projection views
%| option
%|	'use_mex' 1|2|3		default: 1 mex with loop in mex
%|					2 mex with loop in matlab
%|					3 mex with "st" data order (slower)
%|					0 no mex; use matlab
%|	'ia_skip'		default: 1
%|	'offset_source'		default: 0
%|	'nthread'		default: jf('ncore')
%| out
%|	back	[nx ny nz]	back projection result
%|
%| Copyright 2004-8-28 Jeff Fessler, University of Michigan

if nargin == 1 && streq(proj, 'test'), cbct_back_test, return, end
if nargin < 3, help(mfilename), error(mfilename), end

arg.ia_skip = 1;
arg.use_mex = 1;
arg.offset_source = 0;
arg.nthread = jf('ncore');
arg.back_call = @fdk_mex_call;
arg.scale_dang = true;
arg = vararg_pair(arg, varargin);

if arg.use_mex
	if cg.pitch ~= 0 || any(cg.source_zs ~= 0)
		fail('helix not yet supported')
	end
	back = cbct_back_mex(proj, cg.ns, cg.nt, cg.na, ...
		cg.ds, cg.dt, cg.offset_s, cg.offset_t, arg.offset_source, ...
		cg.dsd, cg.dso, cg.dfs, cg.orbit, cg.orbit_start, ...
		ig.mask_or, ig.nz, ig.dx, ig.dy, ig.dz, ...
		[ig.offset_x ig.offset_y ig.offset_z], ...
		arg.ia_skip, arg.scale_dang, ...
		arg.use_mex, arg.nthread, arg.back_call);

else

	back = cbct_back_mat(proj, cg, cg.ns, cg.nt, cg.na, ...
		cg.ds, cg.dt, cg.offset_s, cg.offset_t, arg.offset_source, ...
		cg.dsd, cg.dso, cg.dfs, cg.orbit, cg.orbit_start, ...
		cg.source_zs, ...
		ig.mask_or, ig.nz, ig.dx, ig.dy, ig.dz, ...
		[ig.offset_x ig.offset_y ig.offset_z], ...
		arg.ia_skip, arg.scale_dang);
end

end % cbct_back()

%
% cbct_back_mex()
% mex backprojector
%
function img = cbct_back_mex(proj, ns, nt, na, ...
	ds, dt, offset_s, offset_t, offset_source, ...
	dsd, dso, dfs, orbit, orbit_start, ...
	mask, nz, dx, dy, dz, offset_xyz, ia_skip, scale_dang, ...
	use_mex, nthread, back_call)

[nx ny] = size(mask);
ia_list = 1:ia_skip:na;
betas = deg2rad(orbit_start + orbit * [0:na-1] / na); % [na] source angles
proj = single(proj);
nthread = int32(nthread);

switch use_mex
case 1 % loop in mex
	proj = permute(proj, [2 1 3]); % sta -> tsa
	img = back_call('fdk,ts,back', ...
			int32([nx ny nz]), [dx dy dz], ...
			offset_xyz, uint8(mask), ...
			dso, dsd, dfs, [ds dt], [offset_s offset_t], ...
			proj(:,:,ia_list), betas(ia_list), nthread);
%	img = double6(img);
	img = permute(img, [2 3 1]); % zxy -> xyz

case 2 % loop in matlab (for testing)
	proj = permute(proj, [2 1 3]); % sta -> tsa
	img = 0;
	for ia=ia_list
		ticker(mfilename, ia, na)
		% note: 2006-5-30: replaced -dy with dy
		tmp = back_call('fdk,ts,back', ...
			int32([nx ny nz]), [dx dy dz], ...
			offset_xyz, uint8(mask), ...
			dso, dsd, dfs, [ds dt], [offset_s offset_t], ...
			proj(:,:,ia), betas(ia), nthread);
%		tmp = double6(tmp);
		img = img + tmp;
	end
	img = permute(img, [2 3 1]); % zxy -> xyz

case 3 % fdk,st (for testing only - slower!)
	warn 'fdk,st does not handle projection view edges completely'
	img = back_call('fdk,st,back', ...
			int32([nx ny nz]), [dx dy dz], ...
			offset_xyz, uint8(mask), ...
			dso, dsd, dfs, [ds dt], [offset_s offset_t], ...
			proj(:,:,ia_list), betas(ia_list), nthread);
%	img = double6(img);

otherwise
	fail 'bug'
end

if scale_dang % final "\der angle" scale:
	img = (0.5 * deg2rad(abs(orbit)) / (na/ia_skip)) * img;
end

end % cbct_back_mex()

%
% cbct_back_mat()
% matlab backprojector (slower)
%
function img = cbct_back_mat(proj, cg, ns, nt, na, ...
	ds, dt, offset_s, offset_t, offset_source, ...
	dsd, dso, dfs, orbit, orbit_start, ...
	source_zs, ...
	mask, nz, dx, dy, dz, offset_xyz, ia_skip, scale_dang)

if any(source_zs ~= 0)
	warn('helix not yet tested')
end

[nx ny] = size(mask);
betas = deg2rad(orbit_start + orbit * [0:na-1] / na); % [na] source angles

% precompute as much as possible
wx = (nx-1)/2 + offset_xyz(1);
wy = (ny-1)/2 + offset_xyz(2);
wz = (nz-1)/2 + offset_xyz(3);
[xc yc] = ndgrid(([0:nx-1] - wx) * dx, ([0:ny-1] - wy) * dy);
zc = ([0:nz-1] - wz) * dz;

if 0 % limit back-projection to FOV?  removed 2008-10-9
	rr = sqrt(xc.^2 + yc.^2); % [nx,ny]
	smax = ((ns-1)/2-abs(offset_s)) * ds; % maximum detector s coordinate

	if isinf(dfs)
		gamma_max = atan(smax/dsd);
	elseif dfs == 0
		gamma_max = smax / dsd;
	end

	rmax = dso * sin(gamma_max);
	mask = mask & (rr < rmax);
end
clear wx wy wz rr smax rmax

xc = xc(mask); % [np] pixels within mask
yc = yc(mask);

ws = (ns+1)/2 + offset_s; % trick: +1 because matlab starts from 1
wt = (nt+1)/2 + offset_t;

% loop over slices
img = zeros([size(mask) nz]);
sdim = [ns+3 nt+3]; % trick: extra zeros saves indexing in loop
proj1 = zeros(sdim);
ticker reset

% Greg Handy stuff:
num_turns = orbit/360;
halfNumAngles = round((na/num_turns)/2);
myPitch = cg.pitch * cg.nt * dso / dsd * cg.dt;
h = myPitch / (2*pi);
dist = .5 * myPitch;
zindex = 1;

for iz=1:nz

	% Greg Handy stuff:
	% Just trying to limit the number of angles used
	lower_limit = zc(iz) - dist;

	% enter into the acceptable range for the z-slices
	while zindex < size(cg.source_zs,1)+1 && cg.source_zs(zindex) < lower_limit
		zindex = zindex + 1;
	end
	lowerA = zindex - halfNumAngles;
	upperA = zindex + halfNumAngles;

	% a check to prevent error; images without enough views will be poor
	if lowerA <= 0
		lowerA = 1;
	end
	if upperA > na
		upperA = na;
	end

	% loop over each projection angle
	img2 = 0;
	for ia=lowerA:ia_skip:upperA
		ticker(mfilename, [iz ia], [nz na])
		beta = betas(ia);

		x_beta = +xc * cos(beta) + yc * sin(beta);
		y_betas = dso - (-xc * sin(beta) + yc * cos(beta));

		% detector indices
		if isinf(dsd) || isinf(dso) % par
			mag = ones(size(y_betas));
		else
			mag = dsd ./ y_betas;
		end

		if isinf(dfs) ... % flat
			|| isinf(dsd) || isinf(dso) % par
			sprime = mag .* x_beta;
		elseif (dfs == 0) % arc
			r_loop = x_beta - offset_source;
			sprime = dsd * atan2(r_loop, y_betas);
		end

		tprime = mag * (zc(iz) - source_zs(ia));
		bs = sprime / ds + ws;
		bt = tprime / dt + wt;

		% bi-linear interpolation:
		is = floor(bs); % left bin
		it = floor(bt);

		wr = bs - is;	% left weight
		wl = 1 - wr;	% right weight
		wu = bt - it;	% upper weight
		wd = 1 - wu;	% lower weight

		ibad = (is < 0) | (is > ns) | (it < 0) | (it > nt);
		is(ibad) = ns+1; % trick! point at harmless zeros
		it(ibad) = nt+1;

		proj1(1+[1:ns],1+[1:nt]) = proj(:,:,ia); % trick: left side
		p1 =	wl .* proj1(sub2ind(sdim, is+1,it+1)) + ...
			wr .* proj1(sub2ind(sdim, is+2,it+1));
		p2 =	wl .* proj1(sub2ind(sdim, is+1,it+2)) + ...
			wr .* proj1(sub2ind(sdim, is+2,it+2));

		p0 = wu .* p1 + wd .* p2; % vertical interpolation

		if isinf(dfs) ... % flat
			|| isinf(dsd) || isinf(dso) % par
			p0 = p0 .* mag.^2; % back-projection weighting for flat
		elseif dfs == 0 % arc
			p0 = p0 .* (dsd.^2) ./ (r_loop.^2 + y_betas.^2);
		end

		img2 = img2 + p0;
	end % ia

	img(:,:,iz) = embed(img2, mask);
end % iz

if scale_dang % final "\der angle" scale:
	img = (0.5 * deg2rad(abs(orbit)) / (na*ia_skip)) * img;
end

end % cbct_back_mat()

%
% fdk_mex_call()
%
function out = fdk_mex_call(varargin)
if exist('fdk_mex') == 3
	printm 'using fdk_mex'
	out = fdk_mex(varargin{:});
elseif exist('jf_mex') == 3
	out = jf_mex(varargin{:});
else
	fail('bug: neither fdk_mex nor jf_mex found')
end
end % fdk_mex_call()

%
% cbct_back_test()
% compare various versions to check consistency
%
function cbct_back_test

down = 8; % fast test
% todo: test parallel, flat, arc
%cg = ct_geom('ge1', 'nt', 800, 'na', 50*down, 'dsd', inf, 'down', down);
cg = ct_geom('ge1', 'nt', 800, 'na', 50*down, 'down', down);
%cg.dt = -cg.dt; % todo: handle this case
ig = image_geom('nx', 512, 'ny', 496, 'nz', 480, 'fov', 500, ...
	'mask', 'all-but-edge', 'down', down);

ell = [ ... % somewhat realistic phantom object
	[30 10 10	150 150 280	0 0 1000]; % 30cm diam
	[80 10 10	50 50 30	0 0 300]; % bone-like inserts
	[-10 -40 75	40 40 40	0 0 600];
	[-10 80 -20	30 30 30	0 0 600];
];

if 1
	proj = ellipsoid_proj(cg, ell);
	proj = fdk_filter(proj, 'ramp', cg.dsd, cg.dfs, cg.ds);
else
	proj = cg.zeros;
	proj(:,:,9) = 1;
end
% im clf, im(proj, 'true projections'), cbar, return

if 0 % zero outer edges of projection for comparing ,st to ,ts
	proj([1 cg.ns], :, :) = 0;
	proj(:, [1 cg.nt], :) = 0;
	f.compare_st = true;
else
	f.compare_st = false;
end

	ia_skip = 3; % stress test and makes it faster too
	args = {proj, cg, ig, 'ia_skip', ia_skip, 'scale_dang', 0};
if 1
	back0 = cbct_back(args{:}, 'use_mex', 0);
%	im(back0), return
end

if exist('jf_mex') == 3
	for ii = 1:3
		use_mex = ii;
		printm('testing jf_mex with use_mex=%d', use_mex)
		back1{ii} = cbct_back(args{:}, ...
			'use_mex', use_mex, 'back_call', @jf_mex);
	end
	printm('mpd mex1 vs mat: %g%%', max_percent_diff(back1{1}, back0))
	printm('mpd mex1 vs mex2: %g%%', max_percent_diff(back1{1}, back1{2}))
	if f.compare_st % only if edges are zeroed
	printm('mpd mex1 vs mex3: %g%%', max_percent_diff(back1{1}, back1{3}))
	end
end

if exist('fdk_mex') == 3
	printm('found fdk_mex:\n %s', which('fdk_mex'))
	back2 = cbct_back(args{:}, 'use_mex', 1, 'back_call', @fdk_mex);
	printm('mpd jf_mex vs fdk_mex: %g%%', max_percent_diff(back1{1}, back2))

	if 1
		im plc 1 3
		iz = 1:ig.nz;
		im(1, back1{1}(:,:,iz))
		im(2, back2(:,:,iz))
		err = back2(:,:,iz) - back1{1}(:,:,iz);
		im(3, err)
	end
end

end % cbct_back_test()


function [aVal,aJacob,aHess,papt]=aPolarCoordTurn2DOmega(x,tauInv,tauAccelInv)
%%APOLARCOORDTURN2DOMEGA The continuous-time drift function for a 2D
%           coordinated turn model with the velocity expressed in terms of
%           a heading angle and a speed rather than in terms of a Cartesian
%           velocity vector. Additionally a turn rate is part of the state
%           (unlike in aPolarLin2D) and a linear acceleration term can be
%           given, which acts in the direction of motion. The turn rate and
%           linear acceleration can optionally have time constants
%           associated with them, like in the Singer model, modelling a
%           tendancy to eventually return to non-accelerating, straight-
%           line motion.
%
%INPUTS: xState The 5X1 or 6X1 target state for 2D motion. If there is no
%               linear acceleration (acceleration along the direction of
%               motion), then the state is [xPos;yPos;theta;v;omega],
%               where (xPos,yPos) are the Cartesian position, theta is the
%               angle of the velocity vector in radians counterclockwise
%               from the x axis, v is the speed, and omega is the turn
%               rate, typically in radians per second. If a linear
%               acceleration component is provided, then the state is
%               [xPos;yPos;theta;v;omega,al].
%        tauInv The inverse of the correlation time constant tau for the
%               turn rate in seconds. tauInv must be positive. The default
%               if omitted or an empty matrix is passed is 0.
%   tauAccelInv The inverse of the correlation time constant for the linear
%               acceleration in seconds. This parameter is not used if
%               there is no linear acceleration. the default if omitted or
%               an empty matrix is passed is 0.
%
%OUTPUTS: aVal The 5X1 (or 6X1 with linear acceleration) time-derivative of
%              the state.
%       aJacob The 5X5 (or 6X6) matrix of partial derivatives of aVals
%              such that aJacob(:,k) is the partial derivative of
%              aVals(:,k) with respect to x(k).
%        aHess The 5X5X5 (or 6X6X6) matrix of second derivatives of aVals
%              such that aHess(:,k1,k2) is the second partial derivative of
%              aVals with respect to x(k1) and x(k2).
%         papt The 5X1 or 6X1 derivative with resect to time of aVals.
%              This is all zeros, because the model is time invariant.
%
%The basic 2D coordinated turn model in Cartesian coordinates is described
%in Section VA of [1]. When the turn rate is something that must be
%estimated, it is assumed that the continuous-time turn rate model is
%omegaDot=-(1/tauTurn)*Omega+noise
%Note that the ordering of the state elements assumed by this function
%differs from the ordering of the state elements assumed in [1].
%
%The 2D coordinates turn model in Cartesian coordinates is also described
%in Chapter 4.2.3 of [2].
%
%The corresponding diffusion matrix is given by the function DCoordTurn2D.
%The corresponding discrete-time functions are FCoordTurn2D and
%QCoordTurn. However, note that the discrete-time functions are
%direct-discrete models and not discretizations of the continuous-time
%models as the propagated PDF does not remain Gaussian over time.
%
%REFERENCES:
%[1] X. R. Li and V. P. Jilkov, "Survey of maneuvering target tracking.
%    Part I: Dynamic models," IEEE Transactions on Aerospace and Electronic
%    Systems, vol. 39, no. 4, pp. 1333-1364, Oct. 2003.
%[2] S. Blackman and R. Popoli, Design and Analysis of Modern Tracking
%    Systems. Norwood, MA: Artech House, 1999.
%
%January 2019 David F. Crouse, Naval Research Laboratory, Washington D.C.
%(UNCLASSIFIED) DISTRIBUTION STATEMENT A. Approved for public release.

if(nargin<2||isempty(tauInv))
    tauInv=0;
end

if(nargin<3||isempty(tauAccelInv))
    tauAccelInv=0;
end

%Extract the heading and speed (polar velocity components).
theta=x(3);
v=x(4);
omega=x(5);%The turn rate

xDim=size(x,1);

sinTheta=sin(theta);
cosTheta=cos(theta);
switch(xDim)
    case 5%There is no linear acceleration
        %From Equations 61 and 67 in Li's paper.
        aVal=[v*cosTheta;%Position dervative
              v*sinTheta;%Position derivative
              omega;%Heading derivative
              0;%Speed derivative
              -tauInv*omega];%Turn rate derivative

        if(nargout>1)
            dXdTheta=-v*sinTheta;
            dYdTheta=v*cosTheta;
            dXdv=cosTheta;
            dYdv=sinTheta;

            aJacob=[0,0,dXdTheta,dXdv,0;
                    0,0,dYdTheta,dYdv,0;
                    0,0,0,          0,1;
                    0,0,0,          0,0;
                    0,0,0,          0,-tauInv];

            if(nargout>2)
                aHess=zeros(5,5,5);

                dXdThetadTheta=-v*cosTheta;
                dXdvdTheta=-sinTheta;

                dXdThetadv=dXdvdTheta;
                dXdvdv=0;

                %%%
                dYdThetadTheta=-v*sinTheta;
                dYdvdTheta=cosTheta;

                dYdThetadv=dYdvdTheta;
                dYdvdv=0;

                aHess(:,:,3)=[0,0,dXdThetadTheta,dXdvdTheta,0;
                              0,0,dYdThetadTheta,dYdvdTheta,0;
                              zeros(3,5)];
                aHess(:,:,4)=[0,0,dXdThetadv,dXdvdv,0;
                              0,0,dYdThetadv,dYdvdv,0;
                              zeros(3,5)];

                if(nargout>3)
                    papt=zeros(5,1);
                end
            end
        end
    case 6%There is a linear acceleration component.
        al=x(6);%The linear acceleration (vDot)

        aVal=[v*cosTheta;%Position dervative
              v*sinTheta;%Position derivative
              omega;%Heading derivative
              al;%Speed derivative
              -tauInv*omega;%Turn rate derivative
              -tauAccelInv*al];%Linear acceleration derivative

        if(nargout>1)
            dXdTheta=-v*sinTheta;
            dYdTheta=v*cosTheta;
            dXdv=cosTheta;
            dYdv=sinTheta;

            aJacob=[0,0,dXdTheta,dXdv,        0,0;
                    0,0,dYdTheta,dYdv,        0,0;
                    0,0,       0,   0,        1,0;
                    0,0,       0,   0,        0,1;
                    0,0,       0,   0,  -tauInv,0;
                    0,0,       0,   0,        0,-tauAccelInv];

            if(nargout>2)
                aHess=zeros(6,6,6);

                dXdThetadTheta=-v*cosTheta;
                dXdvdTheta=-sinTheta;

                dXdThetadv=dXdvdTheta;
                dXdvdv=0;

                %%%
                dYdThetadTheta=-v*sinTheta;
                dYdvdTheta=cosTheta;

                dYdThetadv=dYdvdTheta;
                dYdvdv=0;

                aHess(:,:,3)=[0,0,dXdThetadTheta,dXdvdTheta,0,0;
                              0,0,dYdThetadTheta,dYdvdTheta,0,0;
                              zeros(4,6)];

                aHess(:,:,4)=[0,0,dXdThetadv,dXdvdv,0,0;
                              0,0,dYdThetadv,dYdvdv,0,0;
                              zeros(4,6)];

                if(nargout>3)
                    papt=zeros(6,1);
                end
            end
        end
    otherwise
        error('The dimensionality of the state is neither 5 nor 6.');
end
end

%LICENSE:
%
%The source code is in the public domain and not licensed or under
%copyright. The information and software may be used freely by the public.
%As required by 17 U.S.C. 403, third parties producing copyrighted works
%consisting predominantly of the material produced by U.S. government
%agencies must provide notice with such work(s) identifying the U.S.
%Government material incorporated and stating that such material is not
%subject to copyright protection.
%
%Derived works shall not identify themselves in a manner that implies an
%endorsement by or an affiliation with the Naval Research Laboratory.
%
%RECIPIENT BEARS ALL RISK RELATING TO QUALITY AND PERFORMANCE OF THE
%SOFTWARE AND ANY RELATED MATERIALS, AND AGREES TO INDEMNIFY THE NAVAL
%RESEARCH LABORATORY FOR ALL THIRD-PARTY CLAIMS RESULTING FROM THE ACTIONS
%OF RECIPIENT IN THE USE OF THE SOFTWARE.


function [ fea, out ] = ex_navierstokes12( varargin )
%EX_NAVIERSTOKES12 3D Example flow over a backwards facing step
%
%   [ FEA, OUT ] = EX_NAVIERSTOKES12( VARARGIN ) Sets up and solves stationary and
%   laminar 3D flow over a backwards facing step. Accepts the following property/value pairs.
%
%       Input       Value/{Default}        Description
%       -----------------------------------------------------------------------------------
%       rho         scalar {1}             Density
%       miu         scalar {2/3/389}       Molecular/dynamic viscosity
%       uin         scalar {1}             Magnitude of inlet velocity
%       sf_u        string {sflag1}        Shape function for velocity
%       sf_p        string {sflag1}        Shape function for pressure
%       solver      string 'openfoam'/{''} Use OpenFOAM or default solver
%       iplot       scalar 0/{1}           Plot solution and error (=1)
%                                                                                .
%       Output      Value/(Size)           Description
%       -----------------------------------------------------------------------------------
%       fea         struct                 Problem definition struct
%       out         struct                 Output struct
%
%   See also EX_NAVIERSTOKES4

% Copyright 2013-2022 Precise Simulation, Ltd.

cOptDef = {   ...
  'rho',      1;
  'miu',      2/3/389;
  'uin',      1;
  'igrid',    1;
  'sf_u',     'sflag1';
  'sf_p',     'sflag1';
  'solver',   '';
  'iplot',    1;
  'tol',      0.55;
  'fid',      1 };
[got,opt] = parseopt(cOptDef,varargin{:});
fid       = opt.fid;

% Geometry.
h_step = 0.0049/0.0101;
l_inlet = 0.02/0.0101;
l_channel = 0.08/0.0101;
fea.sdim = { 'x', 'y', 'z' };
gobj1 = gobj_block( -l_inlet, l_channel, -0.5, 0.5, -h_step, 1-h_step, 'B1' );
gobj2 = gobj_block( -l_inlet, 0, -0.5, 0.5, -h_step, 0, 'B2' );
fea.geom.objects = { gobj1, gobj2 };
fea = geom_apply_formula( fea, 'B1-B2' );

% Grid generation.
if( opt.igrid>=1 )
  n = 4;
  fea.grid = rectgrid(10*n,n,[-l_inlet, l_channel; -0.5, 0.5]);
  fea.grid = delcells( fea.grid, selcells(fea.grid,'(y<=0).*(x<=0)') );
  ix = find( fea.grid.p(2,:) <= -0.5 + sqrt(eps) );
  fea.grid.p(2,ix) = -h_step;
  ix = find( fea.grid.p(2,:) >= 0.5 - sqrt(eps) );
  fea.grid.p(2,ix) = 1-h_step;

  fea.grid = gridextrude( fea.grid, n, 1, -2 );
  fea.grid.p(2,:) = fea.grid.p(2,:) + 0.5;
  fea.grid = assign_bdr( fea.grid, fea.geom );
  for i=1:opt.igrid
    fea.grid = gridrefine( fea.grid, fid );
  end
else
  fea.grid = gridgen( fea, 'hmax', 0.1, 'fid', fid );
  % fea.grid = gridsmooth( tet2hex( fea.grid ), 5 );
end

% Problem definition.
fea = addphys( fea, @navierstokes );
fea.phys.ns.eqn.coef{1,end} = { opt.rho };
fea.phys.ns.eqn.coef{2,end} = { opt.miu };
fea.phys.ns.sfun            = { opt.sf_u opt.sf_u opt.sf_u opt.sf_p };
% fea.phys.ns.prop.artstab.iupw = 4;
if( any(strcmp(opt.solver,{'openfoam','su2'})) )
  [fea.phys.ns.sfun{:}] = deal('sflag1');
end

% Boundary conditions.
i_inflow  = findbdr( fea, ['x<',num2str(-l_inlet+1e-3)] );   % Inflow boundary number.
i_outflow = findbdr( fea, ['x>',num2str( l_channel-1e-3)] );   % Outflow boundary number.
% s_inflow  = ['4*',num2str(umax),'*(y*(',num2str((1-y)*h),'-y))/',num2str((1-y)*h),'^2'];   % Definition of inflow profile.
s_inflow  = ['4*',num2str(opt.uin),'*(z*(',num2str(1-h_step),'-z))/(1-',num2str(1-h_step),')^2'];
u_init    = [s_inflow,'*(z>0)'];
fea.phys.ns.bdr.sel(i_inflow) = 2;
fea.phys.ns.bdr.sel(i_outflow) = 4;
fea.phys.ns.bdr.coef{2,end}{1,i_inflow} = s_inflow;
if( ~strcmp(opt.solver,'openfoam') )
  fea.phys.ns.eqn.coef{6,end} = { u_init };
end

% Parse and solve problem.
fea  = parsephys( fea );
fea  = parseprob( fea );
if( strcmp(opt.solver,'openfoam') )
  logfid = fid; if( ~got.fid ), fid = []; end
  fea.sol.u = openfoam( fea, 'fid', fid, 'logfid', logfid );
  fid = logfid;
elseif( strcmp(opt.solver,'su2') )
  logfid = fid; if( ~got.fid ), fid = []; end
  fea.sol.u = su2( fea, 'fid', fid, 'logfid', logfid );
  fid = logfid;
else
  fea.sol.u = solvestat( fea, 'maxnit',50, 'nlrlx',1, 'tolchg',1e-3, 'fid', fid );
end

% Postprocessing.
if( opt.iplot>0 )
  postplot( fea, 'sliceexpr', 'sqrt(u^2+v^2+w^2)' )
end

% Error checking.
[~,slen] = minmaxsubd( ['(u<-eps)*x/',num2str(h_step),'*(z<0)*(y<0.01)*(y>-0.01)'], fea );
if( ~isempty(fid) )
  fprintf(fid,'\nRecirculation zone length: %3f (Ref: 7.93)\n\n',slen)
  fprintf(fid,'\n\n')
end

out.slen = [slen, 7.93];
out.err  = abs(diff(out.slen))/out.slen(end);
out.pass = out.err<opt.tol;

if ( nargout==0 )
  clear fea out
end


function pde = TorusTimeInitial3(mum,mup,sigm,sigp,epsm,epsp,omega,x0,y0,z0,r1,r2,a,b,intPt)
%% USAGE: polynomial solution for Poisson equation
%  Last Modified: 02/21/2020 by Xu Zhang

%% PDE Structure
pde = struct('intf',@intf,...
    'f1',@f1,'f2',@f2,'f3',@f3,...
    'fm1',@fm1,'fm2',@fm2,'fm3',@fm3,...
    'fp1',@fp1,'fp2',@fp2,'fp3',@fp3,...
    'Mu',@Mu,'Mum',@Mum,'Mup',@Mup,'one',@one,...
    'Epslon',@Epslon,'Epslonm',@Epslonm,'Epslonp',@Epslonp,...
    'Sig',@Sig,'Sigm',@Sigm,'Sigp',@Sigp,...
    'E1',@E1,'E2',@E2,'E3',@E3,...
    'Et1',@Et1,'Et2',@Et2,'Et3',@Et3);

pde.mum = mum;
pde.mup = mup;
pde.sigm = sigm;
pde.sigp = sigp;
pde.epsm = epsm;
pde.epsp = epsp;
%% interface function
    function u = intf(x,y,z)
        u = (sqrt((x-x0).^2+(y-y0).^2)-r2).^2 + (z-z0).^2 - r1^2;
    end

%% exact solution

%% right hand side function
    function u = f1(x,y,z,t)
        u = fm1(x,y,z,t);
        id = intf(x,y,z) > 0;
        u(id) = fp1(x(id),y(id),z(id),t);
    end
    function u = f2(x,y,z,t)
        u = fm2(x,y,z,t);
        id = intf(x,y,z) > 0;
        u(id) = fp2(x(id),y(id),z(id),t);
    end
    function u = f3(x,y,z,t)
        u = fm3(x,y,z,t);
        id = intf(x,y,z) > 0;
        u(id) = fp3(x(id),y(id),z(id),t);
    end

    function u = fm1(x,y,z,t)
        u = zeros(size(x));
    end
    function u = fm2(x,y,z,t)
        u = zeros(size(x));
    end
    function u = fm3(x,y,z,t)
        u = zeros(size(x));
    end
    function u = fp1(x,y,z,t)
        u = zeros(size(x));
    end
    function u = fp2(x,y,z,t)
        u = zeros(size(x));
    end
    function u = fp3(x,y,z,t)
        u = zeros(size(x));
    end

%%%%% intial condition
    function u = E1(x,y,z,t)
        u = exp(-b*(a*(x-intPt)-omega*t).^2);
%         id = pulregion(x,y,z) > 0;
%         u(id) = zeros(size(x(id)));
    end
    function u = E2(x,y,z,t)
        u = -exp(-b*(a*(x-intPt)-omega*t).^2);
    end
    function u = E3(x,y,z,t)
        u = zeros(size(x));
    end
    function u = Et1(x,y,z,t)
        u = exp(-b*(a*(x-intPt)-omega*t).^2)*2*omega*b.*(a*(x-intPt)-omega*t);
    end
    function u = Et2(x,y,z,t)
        u = -exp(-b*(a*(x-intPt)-omega*t).^2)*2*omega*b.*(a*(x-intPt)-omega*t);
    end
    function u = Et3(x,y,z,t)
        u = zeros(size(x));
    end
%     function u = pulregion(x,y,z)
%        u = (x-intPt).^2/(0.15)^2 - 1;
%     end
%% Diffusion coefficient function
    function u = Mu(x,y,z)
        u = Mum(x,y,z);
        id = intf(x,y,z) > 0;
        u(id) = Mup(x(id),y(id),z(id));
    end
    function u = Mum(x,y,z)
        u = mum^(-1)*ones(size(x));
    end
    function u = Mup(x,y,z)
        u = mup^(-1)*ones(size(x));
    end

%% Mass coefficient function
    function u = Epslon(x,y,z)
        u = Epslonm(x,y,z);
        id = intf(x,y,z) > 0;
        u(id) = Epslonp(x(id),y(id),z(id));
    end
    function u = Epslonm(x,y,z)
        u = epsm*ones(size(x));
    end
    function u = Epslonp(x,y,z)
        u = epsp*ones(size(x));
    end

    function u = Sig(x,y,z)
        u = Sigm(x,y,z);
        id = intf(x,y,z) > 0;
        u(id) = Sigp(x(id),y(id),z(id));
    end
    function u = Sigm(x,y,z)
        u = sigm*ones(size(x));
    end
    function u = Sigp(x,y,z)
        u = sigp*ones(size(x));
    end

%% Other function
    function u = one(x,y,z)
        u = ones(size(x));
    end
end


% Function to derive short term power contour as is done in Ishi et al 2008
%
% Description
% Function to derive short term power contour as is done in Ishi et al 2008
%
%
% Inputs
%  x        : [samples] [Nx1]  Speech signal
%  fs       : [Hz]      [1x1]  Sampling frequency
%
% Outputs
%  pow      : [dB]      [Mx1]  Power contour
%  pow_std  : [samples] [Px1]  Standard deviation of power contour
%
% Example
%  Please see the HOWTO_glottalsource.m example file.
%
% References
%  [1] Ishi, C., Sakakibara, K-I, Ishiguro, H., (2008) `A method for
%       automatic detection of vocal fry', IEEE TASLP, 16(1), 47-56.
%  [2] Drugman, T., Kane, J., Gobl, C., `Automatic Analysis of Creaky
%       Excitation Patterns', Submitted to Computer Speech and
%       Language.
%  [3] Kane, J., Drugman, T., Gobl, C., (2013) `Improved automatic
%       detection of creak', Computer Speech and Language 27(4), pp.
%       1028-1047.
%  [4] Drugman, T., Kane, J., Gobl, C., (2012) `Resonator-based creaky
%       voice detection', Interspeech 2012, Portland, Oregon, USA.
%
% Copyright (c) 2013 University of Mons, FNRS & 2013 Trinity College Dublin
%
% License
%  This code is a part of the GLOAT toolbox with the following
%  licence:
%  This program 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.
%  This program 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.
%
% This function is part of the Covarep project: http://covarep.github.io/covarep
%
% Authors
%  Thomas Drugman <thomas.drugman@umons.ac.be> & John Kane <kanejo@tcd.ie>

function [pow,pow_std,pow_std_inter] = get_short_pow(x,fs)

% Get very short term power contour
veryShort_len = 4*(fs/1000); % 4ms frame length for "very short-term" analysis
veryShort_shift = 2*(fs/1000); % 2ms shift for "very short-term" analysis
veryShort_powCont = zeros(1,ceil((length(x)-veryShort_len)/veryShort_shift));
start=1;
finish=start+veryShort_len-1;

n=1;
x2 = x.^2;
while finish <= length(x)
    veryShort_powCont(n) = mean(x2(start:finish));
    start = start + veryShort_shift;
    finish=start+veryShort_len-1;
    n=n+1;
end
clear x2;

pow = 20*log10(veryShort_powCont);
inf_idx=isinf(pow);
pow(inf_idx)=min(pow(~inf_idx));

pow_std=zeros(1,length(pow));
std_len=16;

for n=std_len+1:length(pow)-std_len

    pow_std(n) = std(pow(n-std_len:n+std_len));
end
pow_std=medfilt1(pow_std,13);

pow_std_inter=interp1(linspace(1,length(x),length(pow)),pow_std,1:length(x));


function ds_dsm = cosmo_dissimilarity_matrix_measure(ds, varargin)
% Compute a dissimilarity matrix measure
%
% ds_dsm = cosmo_dissimilarity_matrix_measure(ds[, varargin])
%
% Inputs:
%  dataset            dataset struct with fields .samples (PxQ) and
%                     .sa.targets (Px1) for P samples and Q features.
%                     each target should occur exactly once
%  args               optional struct:
%      .metric        a string with the name of the distance
%                     metric to be used by pdist (default: 'correlation')
%      .center_data   If true, then data is centered before the pair-wise
%                     distances are computed. The default is false; when
%                     used with the 'correlation' metric, it is recommended
%                     to use center_data, 'true'.
%
%   Returns
%
% Output:
%    ds_sa            Struct with fields:
%      .samples       Nx1 flattened lower triangle of a dissimilarity
%                     matrix as returned by [cosmo_]pdist, where
%                     N=P*(P-1)/2 is the number of pairwise distances
%                     between all samples in the dataset.
%      .a.sdim.labels Set to
%      .sa            Struct with field:
%        .targets1    } Nx1 vectors indicating the pairs of indices in the
%        .targets2    } lower part of the square form of the dissimilarity
%                       matrix. if .dsm_pairs(k,:)==[i,j] then .samples(k)
%                     the dissimlarity between the i-th and j-th sample
%                     target.
%
%
% Example:
%     % ds is a dataset struct with ds.sa.targets=(11:16)';
%     ds=struct();
%     ds.samples=[1 2 3; 1 2 3; 1 0 1; 1 1 2; 1 1 2];
%     ds.sa.targets=(11:15)';
%     %
%     % compute dissimilarity with centered data
%     dsm_ds=cosmo_dissimilarity_matrix_measure(ds,'center_data',true);
%     cosmo_disp(dsm_ds);
%     %|| .sa
%     %||   .targets1
%     %||     [ 2
%     %||       3
%     %||       4
%     %||       :
%     %||       4
%     %||       5
%     %||       5 ]@10x1
%     %||   .targets2
%     %||     [ 1
%     %||       1
%     %||       1
%     %||       :
%     %||       3
%     %||       3
%     %||       4 ]@10x1
%     %|| .a
%     %||   .sdim
%     %||     .labels
%     %||       { 'targets1'  'targets2' }
%     %||     .values
%     %||       { [ 11    [ 11
%     %||           12      12
%     %||           13      13
%     %||           14      14
%     %||           15 ]    15 ] }
%     %|| .samples
%     %||   [         0
%     %||             2
%     %||             2
%     %||         :
%     %||      1.11e-16
%     %||      1.11e-16
%     %||     -2.22e-16 ]@10x1
%     %
%     % map results to matrix. values of 0 mean perfect correlation
%     [samples, labels, values]=cosmo_unflatten(dsm_ds,1,...
%                                           'set_missing_to',NaN);
%     cosmo_disp(samples)
%     %|| [ NaN       NaN       NaN       NaN       NaN
%     %||     0       NaN       NaN       NaN       NaN
%     %||     2         2       NaN       NaN       NaN
%     %||     2         2  1.11e-16       NaN       NaN
%     %||     2         2  1.11e-16 -2.22e-16       NaN ]
%     %
%     cosmo_disp(labels)
%     %|| { 'targets1'  'targets2' }
%     %
%     cosmo_disp(values)
%     %|| { [ 11    [ 11
%     %||     12      12
%     %||     13      13
%     %||     14      14
%     %||     15 ]    15 ] }
%
%     % Searchlight using this measure
%     ds=cosmo_synthetic_dataset('ntargets',6,'nchunks',1);
%     % (in this toy example there are only 6 voxels, and the radius
%     %  of the searchlight is 1 voxel. Real-life examples use larger
%     %  datasets and a larger radius)
%     nbrhood=cosmo_spherical_neighborhood(ds,'radius',1,'progress',false);
%     opt=struct();
%     opt.progress=false;          % do not show progress
%     opt.metric='euclidean'; % (instead of default 'correlation')
%     measure=@cosmo_dissimilarity_matrix_measure;
%     sl_ds=cosmo_searchlight(ds, nbrhood, measure, opt);
%     cosmo_disp(sl_ds);
%     %|| .a
%     %||   .fdim
%     %||     .labels
%     %||       { 'i'  'j'  'k' }
%     %||     .values
%     %||       { [ 1         2         3 ]  [ 1         2 ]  [ 1 ] }
%     %||   .vol
%     %||     .mat
%     %||       [ 2         0         0        -3
%     %||         0         2         0        -3
%     %||         0         0         2        -3
%     %||         0         0         0         1 ]
%     %||     .dim
%     %||       [ 3         2         1 ]
%     %||     .xform
%     %||       'scanner_anat'
%     %||   .sdim
%     %||     .labels
%     %||       { 'targets1'  'targets2' }
%     %||     .values
%     %||       { [ 1    [ 1
%     %||           2      2
%     %||           3      3
%     %||           4      4
%     %||           5      5
%     %||           6 ]    6 ] }
%     %|| .fa
%     %||   .nvoxels
%     %||     [ 3         4         3         3         4         3 ]
%     %||   .radius
%     %||     [ 1         1         1         1         1         1 ]
%     %||   .center_ids
%     %||     [ 1         2         3         4         5         6 ]
%     %||   .i
%     %||     [ 1         2         3         1         2         3 ]
%     %||   .j
%     %||     [ 1         1         1         2         2         2 ]
%     %||   .k
%     %||     [ 1         1         1         1         1         1 ]
%     %|| .samples
%     %||   [  3.1      3.68      3.56      1.47      2.96      2.27
%     %||     6.06      6.39      3.29      6.54      4.43       4.1
%     %||     5.85       4.2      2.11      6.47      6.18      3.47
%     %||       :         :        :          :         :         :
%     %||     4.62      3.18     0.829      5.53      5.53      3.14
%     %||      3.7      3.08      1.75      4.71      4.95      3.39
%     %||     1.23      0.83      1.48      1.03      1.75      1.31 ]@15x6
%     %|| .sa
%     %||   .targets1
%     %||     [ 2
%     %||       3
%     %||       4
%     %||       :
%     %||       5
%     %||       6
%     %||       6 ]@15x1
%     %||   .targets2
%     %||     [ 1
%     %||       1
%     %||       1
%     %||       :
%     %||       4
%     %||       4
%     %||       5 ]@15x1
%     %||
%
%     % limitation: cannot have repeated targets
%     ds=cosmo_synthetic_dataset('nchunks',2,'ntargets',3);
%     cosmo_dissimilarity_matrix_measure(ds);
%     %|| error('...')
%
%     % averaging the samples for each unique target resolves the issue of
%     % repeated targets
%     ds=cosmo_synthetic_dataset('nchunks',2,'ntargets',3);
%     ds_avg=cosmo_fx(ds,@(x)mean(x,1),'targets');
%     ds_dsm=cosmo_dissimilarity_matrix_measure(ds_avg);
%     cosmo_disp(ds_dsm);
%     ||.sa
%||  .targets1
%||    [ 2
%||      3
%||      3 ]
%||  .targets2
%||    [ 1
%||      1
%||      2 ]
%||.a
%||  .sdim
%||    .labels
%||      { 'targets1'  'targets2' }
%||    .values
%||      { [ 1    [ 1
%||          2      2
%||          3 ]    3 ] }
%||.samples
%||  [  1.68
%||     1.71
%||    0.711 ]
%
% Notes:
%   - it is recommended to set the 'center_data' to true when using
%     the default 'correlation' metric, as this removes a main effect
%     common to all samples; but note that this option is disabled by
%     default due to historical reasons.
%  -  [cosmo_]pdist defaults to 'euclidean' distance, but correlation
%     distance is preferable for neural dissimilarity matrices, hence it
%     is used as the default here
%  -  Results from this function, when used with the default 'correlation'
%     metric, should *not* be Fisher transformed (using atanh) because
%     the output ranges from 0 to 2 (=one minus Pearson correlation)
%     and the Fisher transform of a value >1 is complex (non-real). This is
%     generally a Bad Thing.
%
% See also: cosmo_pdist, pdist
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    % check input
    check_input(ds);
    args=get_args(varargin);

    % make new dataset
    ds_dsm=struct();

    % if center_data, then subtract the mean first
    samples=ds.samples;
    if args.center_data
        samples=bsxfun(@minus,samples,mean(samples,1));
    end

    % compute pair-wise distances between all samples using cosmo_pdist,
    % then store them as samples in ds_dsm
    % >@@>
    dsm = cosmo_pdist(samples, args.metric)';

    % store dsm
    ds_dsm=get_sample_attributes(ds.sa.targets);
    ds_dsm.samples=dsm;
    % <@@<

function check_input(ds)
    if ~(isstruct(ds) && ...
                isfield(ds,'samples') && ...
                isfield(ds,'sa') && ...
                isfield(ds.sa,'targets'))
        error(['require dataset structure with fields '...
                    '.samples and .sa.targets']);
    end

function args=get_args(varargin)
    persistent cached_varargin;
    persistent cached_args;

    if ~isequal(varargin, cached_varargin)
        cached_args=cosmo_structjoin('metric','correlation',...
                            'center_data',false,...
                            varargin);
        cached_varargin=varargin;
    end

    args=cached_args;

function ds_skeleton=get_sample_attributes(targets)
    persistent cached_targets;
    persistent cached_ds_skeleton;

    if ~isequal(targets, cached_targets)
        ntargets=numel(targets);

        % unique targets
        classes=unique(targets);
        nclasses=numel(classes);

        % each should occur exactly once
        if nclasses~=ntargets
            error(['.sa.targets should be permutation of unique targets; '...
                    'to average samples with the same targets, consider '...
                    'ds_mean=cosmo_fx(ds,@(x)mean(x,1),''targets'')'],...
                        nclasses);
        end

        % store single sample attribute: the pairs of sample attribute indices
        % used to compute the dsm.
        [i,j]=find(triu(repmat(1:nclasses,nclasses,1),1)');
        cached_ds_skeleton.sa=struct();
        cached_ds_skeleton.sa.targets1=i;
        cached_ds_skeleton.sa.targets2=j;

        % set sample dimensions
        add_labels={'targets1','targets2'};
        add_values={targets, targets};

        cached_ds_skeleton.a.sdim=struct();
        cached_ds_skeleton.a.sdim.labels=add_labels;
        cached_ds_skeleton.a.sdim.values=add_values;

        cached_targets=targets;
    end

    ds_skeleton=cached_ds_skeleton;


function SRout = SR(VAR,SIGN,VARopt)
% =======================================================================
% Compute IRs, VDs, and HDs for a VAR model estimated with VARmodel and
% identified with sign restrictions
% =======================================================================
% SRout = SR(VAR,R,VARopt)
% -----------------------------------------------------------------------
% INPUT
%   - VAR: structure, result of VARmodel function
%   - SIGN: matrix containing the sign restrictions (nvar,nshocks). 1
%       stands for positive, -1 for negative, 0 for unrestricted. For
%       example the following identifies only one shock with positive
%       impact on the VAR1, and negative imnpact on VAR2 and VAR3
%
%             shock1      shock2     shock3
%  SIGN = [     1           0           0          % VAR1
%              -1           0           0          % VAR2
%              -1           0           0];        % VAR3
%
%   - VARopt: options of the VAR (see VARoption from VARmodel)
% -----------------------------------------------------------------------
% OUTPUT
%   - SRout
%       * IRall : 4-D matrix of IRs  (nsteps,nvar,nshocks,ndraws)
%       * IRmed : median of IRall
%       * IRinf : lower bound of IRall
%       * IRsup : upper bound of IRall
%       * IR    : IR based on true B matrix that is closest to median B
%       * (similar structure for VD, B, and HD)
% -----------------------------------------------------------------------
% EXAMPLE
%   - See VARToolbox_Code.m in "../Primer/"
% =======================================================================
% VAR Toolbox 3.0
% Ambrogio Cesa-Bianchi
% ambrogiocesabianchi@gmail.com
% March 2012. Updated November 2020
% -----------------------------------------------------------------------

%% Check inputs
%==========================================================================
if ~exist('SIGN','var')
    error('You have not provided sign restrictions (SIGN)')
end

if ~exist('VARopt','var')
    error('You need to provide VAR options (VARopt from VARmodel)');
end

%% Retrieve parameters and preallocate variables
%==========================================================================
nvar    = VAR.nvar;
nvar_ex = VAR.nvar_ex;
nsteps  = VARopt.nsteps;
ndraws  = VARopt.ndraws;
nobs    = VAR.nobs;
nlag    = VAR.nlag;
pctg    = VARopt.pctg;

% Initialize empty matrix for the IR draws
IRall  = nan(nsteps,nvar,nvar,ndraws);
VDall = nan(nsteps,nvar,nvar,ndraws);
HDall.shock  = zeros(nobs+nlag,nvar,nvar,ndraws);
HDall.init   = zeros(nobs+nlag,nvar,ndraws);
HDall.const  = zeros(nobs+nlag,nvar,ndraws);
HDall.trend  = zeros(nobs+nlag,nvar,ndraws);
HDall.trend2 = zeros(nobs+nlag,nvar,ndraws);
HDall.endo   = zeros(nobs+nlag,nvar,ndraws);
HDall.exo    = zeros(nobs+nlag,nvar,nvar_ex,ndraws);
Ball = nan(nvar,nvar,ndraws);

%% Sign restriction routine
%==========================================================================
jj = 0; % accepted draws
tt = 0; % total draws
ww = 1; % index for printing on screen
while jj < ndraws

    % Check total number of draws
    if tt>VARopt.sr_draw
        disp('------------------------------------------------------------')
        disp( 'Total number of draws for finding sign restrictions exceeed')
        disp( 'Change the restrictions or increase VARopt.sr_draw');
        disp('------------------------------------------------------------')
        error('See details above')
    end

    % Set up VAR_draw.(draw{j}) for rotations: Only identification uncertainty
    label  = {['draw' num2str(jj)]};
    VAR_draw.(label{1}) = VAR;
    VARopt.ident = 'sign';
    % If selected, set up VAR_draw.(draw{j}) to consider identification + model uncertainty
    if VARopt.sr_mod==1
        % Draw F and sigma from the posterior and
        [sigma_draw, Ft_draw, F_draw, Fcomp_draw] = VARdrawpost(VAR);
        VAR_draw.(label{1}).Ft = Ft_draw;
        VAR_draw.(label{1}).F = F_draw;
        VAR_draw.(label{1}).Fcomp = Fcomp_draw;
        VAR_draw.(label{1}).sigma = sigma_draw;
    end

    % Compute rotated B matrix
    B = SignRestrictions(SIGN,VAR_draw.(label{1}),VARopt);

    % Note: e = (inv(B)*VAR_draw.(draw{j}).resid')';
    % Check orthogonality:
    % round(corr((inv(B)*VAR_draw.(draw{j}).resid')'))

    if ~isempty(B)
        % Store B
        jj = jj+1; tt = tt+1;
        Ball(:,:,jj) = B;

        % Update VAR_draw.(draw{j}) with the rotated B matrix for IR, VD, and HD
        VAR_draw.(label{1}).B = B;

        % Compute and store IR, VD, HD
        [aux_irf, VAR_draw.(label{1})] = VARir(VAR_draw.(label{1}),VARopt);
        IRall(:,:,:,jj)  = aux_irf;
        aux_fevd = VARvd(VAR_draw.(label{1}),VARopt);
        VDall(:,:,:,jj)  = aux_fevd;
        aux_hd = VARhd(VAR_draw.(label{1}),VARopt);
        HDall.shock(:,:,:,jj) = aux_hd.shock;
        HDall.init(:,:,jj)    = aux_hd.init;
        HDall.const(:,:,jj)   = aux_hd.const;
        HDall.trend(:,:,jj)   = aux_hd.trend;
        HDall.trend2(:,:,jj)  = aux_hd.trend2;
        HDall.endo(:,:,jj)    = aux_hd.endo;
        if nvar_ex>0; HDall.exo(:,:,:,jj) = aux_hd.exo; end

        % Display number of loops
        if jj==VARopt.mult*ww
            disp(['Rotation: ' num2str(jj) ' / ' num2str(ndraws)]);
            ww=ww+1;
        end
    else
        tt=tt+1;
    end
end
disp('-- Done!');
disp(' ');

%% Store results
%==========================================================================
% Store all accepted IRs and VDs
SRout.IRall = IRall;
SRout.VDall = VDall;
SRout.Ball  = Ball;
SRout.HDall = HDall;

% Compute and save median B matrix
SRout.Bmed = median(Ball,3);

% Compute and save true B matrix that is closer to median
aux = sum(sum((Ball-SRout.Bmed).^2,1),2);
sel = find(aux==min(aux));
sel = sel(1);
SRout.B = Ball(:,:,sel);

% Compute IR and VD based on all rotations
pctg_inf = (100-pctg)/2;
pctg_sup = 100 - (100-pctg)/2;
SRout.IRmed = median(IRall,4);
aux = prctile(IRall,[pctg_inf pctg_sup],4);
SRout.IRinf = aux(:,:,:,1);
SRout.IRsup = aux(:,:,:,2);
SRout.VDmed = median(VDall,4);
aux = prctile(VDall,[pctg_inf pctg_sup],4);
SRout.VDinf = aux(:,:,:,1);
SRout.VDsup = aux(:,:,:,2);

% Compute IR, VD, and HD based on the VARdraw that is closest to the median
% B matrix
VARopt.ident = 'sign';
label = {['draw' num2str(sel)]};  % Recover the position in VARdraw
VAR = VAR_draw.(label{1});        % Set a VAR based on the selected VARdraw
SRout.IR = VARir(VAR,VARopt);     % Compute IR
SRout.VD = VARvd(VAR,VARopt);     % Compute VD
SRout.HD = VARhd(VAR,VARopt);     % Compute HD


function [f] = spm_cost_SHC_fxa(x,v,a,P)
% equations of motion for a foraging problem
% FORMAT [f] = spm_cost_SHC_fxa(x,v,a,P)
%
% x   - hidden states
% v   - exogenous inputs
% a   - action
% P   - parameters for mountain car
%
% returns f = dx/dt (see spm_cost_SHC_fx)
% These equations of motion model dissipative flow x.x and x.v on a flat
% potential and increases in physiological states x.q as radial basis
% functions of secrete locations. The agent has to discover these
% locations % using an appropriate policy. This generative process would
% also substitute for Morris water-maze simulations or unbounded saccades.
%__________________________________________________________________________
% Copyright (C) 2010 Wellcome Trust Centre for Neuroimaging

% Karl Friston
% $Id: spm_cost_SHC_fxa.m 3757 2010-03-08 11:41:53Z guillaume $

% location and radius of attractors A (only A.q attractors deliver reward)
%--------------------------------------------------------------------------
global A; X   = A.x(:,A.q);

% physical flow
%--------------------------------------------------------------------------
f   = x;
f.x = x.v;
f.v = a - x.x*2 - x.v*4;

% physiological flow
%--------------------------------------------------------------------------
for i = 1:size(X,2)
    f.q(i) = (norm(x.x - X(:,i)) < A.d) - x.q(i)/2;
end

% flow
%--------------------------------------------------------------------------
dt  = 1/8;
f.x = f.x*dt;
f.v = f.v*dt;
f.q = f.q*dt;


classdef LogisticRegression < handle
    %LOGISTICREGRESSION  Logistic Regression classifier
    %
    % ## Logistic Regression
    %
    % ML implements logistic regression, which is a probabilistic
    % classification technique. Logistic Regression is a binary classification
    % algorithm which is closely related to Support Vector Machines (SVM).
    % Like SVM, Logistic Regression can be extended to work on multi-class
    % classification problems like digit recognition (i.e. recognizing digitis
    % like 0,1 2, 3,... from the given images). This version of Logistic
    % Regression supports both binary and multi-class classifications (for
    % multi-class it creates a multiple 2-class classifiers). In order to
    % train the logistic regression classifier, Batch Gradient Descent and
    % Mini-Batch Gradient Descent algorithms are used (see [BatchDesWiki]).
    % Logistic Regression is a discriminative classifier (see [LogRegTomMitch]
    % for more details). Logistic  Regression is implemented as a C++ class in
    % cv.LogisticRegression.
    %
    % In Logistic Regression, we try to optimize the training parameter
    % `theta` such that the hypothesis `0 <= h_theta(x) <= 1` is achieved. We
    % have `h_theta(x) = g(h_theta(x))` and `g(z)=1/(1+e^(-z))` as the
    % logistic or sigmoid function. The term "Logistic" in Logistic Regression
    % refers to this function. For given data of a binary classification
    % problem of classes 0 and 1, one can determine that the given data
    % instance belongs to class 1 if `h_theta(x) >= 0.5` or class 0 if
    % `h_theta(x) < 0.5`.
    %
    % In Logistic Regression, choosing the right parameters is of utmost
    % importance for reducing the training error and ensuring high training
    % accuracy:
    %
    % * The learning rate can be set with `LearningRate` property. It
    %   determines how fast we approach the solution. It is a positive real
    %   number.
    % * Optimization algorithms like Batch Gradient Descent and Mini-Batch
    %   Gradient Descent are supported in cv.LogisticRegression. It is
    %   important that we mention the number of iterations these optimization
    %   algorithms have to run. The number of iterations can be set with
    %   `Iterations` property. This parameter can be thought as number of
    %   steps taken and learning rate specifies if it is a long step or a
    %   short step. This and previous parameter define how fast we arrive at a
    %   possible solution.
    % * In order to compensate for overfitting regularization is performed,
    %   which can be enabled with `Regularization` property. One can specify
    %   what kind of regularization has to be performed by passing one of
    %   regularization kinds to this property.
    % * Logistic regression implementation provides a choice of 2 training
    %   methods with Batch Gradient Descent or the MiniBatch Gradient Descent.
    %   To specify this, set `TrainMethod` property as either `Batch` or
    %   `MiniBatch`. If training method is set to `MiniBatch`, the size of the
    %   mini batch has to be to a positive integer set with `MiniBatchSize`
    %   property.
    %
    % ## Example
    % A sample set of training parameters for the Logistic Regression
    % classifier can be initialized as follows:
    %
    %     lr = cv.LogisticRegression();
    %     lr.LearningRate = 0.05;
    %     lr.Iterations = 1000;
    %     lr.Regularization = 'L2';
    %     lr.TrainMethod = 'MiniBatch';
    %     lr.MiniBatchSize = 10;
    %
    % ## References
    % [LogRegWiki]:
    % > [Logistic regression](https://en.wikipedia.org/wiki/Logistic_regression)
    %
    % [BatchDesWiki]:
    % > [Gradient descent optimization](https://en.wikipedia.org/wiki/Gradient_descent_optimization)
    %
    % [LogRegTomMitch]:
    % > "Generative and Discriminative Classifiers: Naive Bayes and Logistic
    % > Regression" in Machine Learning, Tom Mitchell.
    % > [Machine Learning](http://www.cs.cmu.edu/~tom/NewChapters.html)
    %
    % [RenMalik2003]:
    % > "Learning a Classification Model for Segmentation". Proc. CVPR, Nice,
    % > France (2003).
    %
    % See also: cv.LogisticRegression.LogisticRegression, fitglm
    %

    properties (SetAccess = private)
        % Object ID
        id
    end

    properties (Dependent)
        % The learning rate of the optimization algorithm.
        %
        % The higher the value, faster the rate and vice versa. If the value
        % is too high, the learning algorithm may overshoot the optimal
        % parameters and result in lower training accuracy. If the value is
        % too low, the learning algorithm converges towards the optimal
        % parameters very slowly. The value must a be a positive real number.
        % You can experiment with different values with small increments as in
        % 0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, ... and select
        % the learning rate with less training error. Default 0.001
        LearningRate
        % Number of iterations.
        %
        % The number of iterations required for the learing algorithm
        % (Gradient Descent or Mini Batch Gradient Descent). It has to be a
        % positive integer. You can try different number of iterations like in
        % 100, 1000, 2000, 3000, 5000, 10000, .. so on. Default 1000
        Iterations
        % Kind of regularization to be applied.
        %
        % Default 'L2'. Possible values:
        %
        % * __Disable__ Regularization disabled.
        % * __L1__ L1 norm.
        % * __L2__ L2 norm.
        Regularization
        % Kind of training method used to train the classifier.
        %
        % Default 'Batch'. Possible values:
        %
        % * __Batch__ batch gradient descent.
        % * __MiniBatch__ Mini-Batch Gradient Descent. Set `MiniBatchSize` to
        %   a positive integer when using this method.
        TrainMethod
        % Number of training samples taken in each step of Mini-Batch Gradient
        % Descent.
        %
        % Will only be used if using `MiniBatch` training algorithm. It has to
        % take values less than the total number of training samples.
        % Default 1
        MiniBatchSize
        % Termination criteria of the training algorithm.
        %
        % A struct with the following fields is accepted:
        %
        % * __type__ one of 'Count', 'EPS', 'Count+EPS'. default 'Count+EPS'
        % * __maxCount__ maximum number of iterations. default `Iterations`
        % * __epsilon__ tolerance value. default `LearningRate`
        TermCriteria
    end

    %% Constructor/destructor
    methods
        function this = LogisticRegression(varargin)
            %LOGISTICREGRESSION  Creates/trains a logistic regression model
            %
            %     model = cv.LogisticRegression()
            %     model = cv.LogisticRegression(...)
            %
            % The first variant creates Logistic Regression model with default
            % parameters.
            %
            % The second variant accepts the same parameters as the train
            % method, in which case it forwards the call after construction.
            %
            % See also: cv.LogisticRegression, cv.LogisticRegression.train
            %
            this.id = LogisticRegression_(0, 'new');
            if nargin > 0
                this.train(varargin{:});
            end
        end

        function delete(this)
            %DELETE  Destructor
            %
            %     model.delete()
            %
            % See also: cv.LogisticRegression
            %
            if isempty(this.id), return; end
            LogisticRegression_(this.id, 'delete');
        end
    end

    %% Algorithm
    methods
        function clear(this)
            %CLEAR  Clears the algorithm state
            %
            %     model.clear()
            %
            % The method clear does the same job as the destructor: it
            % deallocates all the memory occupied by the class members. But
            % the object itself is not destructed and can be reused further.
            % This method is called from the destructor, from the `train` and
            % `load` methods, or even explicitly by the user.
            %
            % See also: cv.LogisticRegression.empty, cv.LogisticRegression.load
            %
            LogisticRegression_(this.id, 'clear');
        end

        function b = empty(this)
            %EMPTY  Returns true if the algorithm is empty
            %
            %     b = model.empty()
            %
            % ## Output
            % * __b__ Returns true if the algorithm is empty (e.g. in the very
            %   beginning or after unsuccessful read).
            %
            % See also: cv.LogisticRegression.clear, cv.LogisticRegression.load
            %
            b = LogisticRegression_(this.id, 'empty');
        end

        function varargout = save(this, filename)
            %SAVE  Saves the algorithm parameters to a file or a string
            %
            %     model.save(filename)
            %     str = model.save(filename)
            %
            % ## Input
            % * __filename__ Name of the file to save to. In case of string
            %   output, only the filename extension is used to determine the
            %   output format (XML or YAML).
            %
            % ## Output
            % * __str__ optional output. If requested, the model is persisted
            %   to a string in memory instead of writing to disk.
            %
            % This method stores the complete model state to the specified
            % XML or YAML file (or to a string in memory, based on the number
            % of output arguments).
            %
            % See also: cv.LogisticRegression.load
            %
            [varargout{1:nargout}] = LogisticRegression_(this.id, 'save', filename);
        end

        function load(this, fname_or_str, varargin)
            %LOAD  Loads algorithm from a file or a string
            %
            %     model.load(filename)
            %     model.load(str, 'FromString',true)
            %     model.load(..., 'OptionName',optionValue, ...)
            %
            % ## Input
            % * __filename__ Name of the file to read.
            % * __str__ String containing the serialized model you want to
            %   load.
            %
            % ## Options
            % * __ObjName__ The optional name of the node to read (if empty,
            %   the first top-level node will be used). default empty
            % * __FromString__ Logical flag to indicate whether the input is a
            %   filename or a string containing the serialized model (switches
            %   between `Algorithm<T>::load()` and
            %   `Algorithm<T>::loadFromString()` C++ methods). default false
            %
            % This method loads the complete model state from the specified
            % XML or YAML file (either from disk or serialized string). The
            % previous model state is cleared.
            %
            % See also: cv.LogisticRegression.save
            %
            LogisticRegression_(this.id, 'load', fname_or_str, varargin{:});
        end

        function name = getDefaultName(this)
            %GETDEFAULTNAME  Returns the algorithm string identifier
            %
            %     name = model.getDefaultName()
            %
            % ## Output
            % * __name__ This string is used as top level XML/YML node tag
            %   when the object is saved to a file or string.
            %
            % See also: cv.LogisticRegression.save, cv.LogisticRegression.load
            %
            name = LogisticRegression_(this.id, 'getDefaultName');
        end
    end

    %% StatModel
    methods
        function count = getVarCount(this)
            %GETVARCOUNT  Returns the number of variables in training samples
            %
            %     count = model.getVarCount()
            %
            % ## Output
            % * __count__ number of variables in training samples (plus one to
            %   account for the implicitly prepended bias/intercept term).
            %
            % See also: cv.LogisticRegression.train
            %
            count = LogisticRegression_(this.id, 'getVarCount');
        end

        function b = isTrained(this)
            %ISTRAINED  Returns true if the model is trained
            %
            %     b = model.isTrained()
            %
            % ## Output
            % * __b__ Returns true if the model is trained, false otherwise.
            %
            % See also: cv.LogisticRegression.empty, cv.LogisticRegression.train
            %
            b = LogisticRegression_(this.id, 'isTrained');
        end

        function b = isClassifier(this)
            %ISCLASSIFIER  Returns true if the model is a classifier
            %
            %     b = model.isClassifier()
            %
            % ## Output
            % * __b__ Returns true if the model is a classifier, false if the
            %   model is a regressor.
            %
            % Always true for logistic regression.
            %
            % See also: cv.LogisticRegression.isTrained
            %
            b = LogisticRegression_(this.id, 'isClassifier');
        end

        function status = train(this, samples, responses, varargin)
            %TRAIN  Trains the statistical model
            %
            %     status = model.train(samples, responses)
            %     status = model.train(csvFilename, [])
            %     [...] = model.train(..., 'OptionName', optionValue, ...)
            %
            % ## Input
            % * __samples__ matrix of training samples. It should have
            %   `single` type. By default, each row represents a sample (see
            %   the `Layout` option).
            % * __responses__ matrix of associated responses. A vector of
            %   categorical labels, stored in an array of type `single`.
            % * __csvFilename__ The input CSV file name from which to load
            %   dataset. In this variant, you should set the second argument
            %   to an empty array.
            %
            % ## Output
            % * __status__ Success flag.
            %
            % ## Options
            % * __Data__ Training data options, specified as a cell array of
            %   key/value pairs of the form `{'key',val, ...}`. See below.
            % * __Flags__ The optional training flags, model-dependent.
            %   Not used. default 0
            %
            % ### Options for `Data` (first variant with samples and reponses)
            % * __Layout__ Sample types. Default 'Row'. One of:
            %   * __Row__ each training sample is a row of samples.
            %   * __Col__ each training sample occupies a column of samples.
            % * __VarIdx__ vector specifying which variables to use for
            %   training. It can be an integer vector (`int32`) containing
            %   0-based variable indices or logical vector (`uint8` or
            %   `logical`) containing a mask of active variables. Not set by
            %   default, which uses all variables in the input data.
            % * __SampleIdx__ vector specifying which samples to use for
            %   training. It can be an integer vector (`int32`) containing
            %   0-based sample indices or logical vector (`uint8` or
            %   `logical`) containing a mask of training samples of interest.
            %   Not set by default, which uses all samples in the input data.
            % * __SampleWeights__ optional floating-point vector with weights
            %   for each sample. Some samples may be more important than
            %   others for training. You may want to raise the weight of
            %   certain classes to find the right balance between hit-rate and
            %   false-alarm rate, and so on. Not set by default, which
            %   effectively assigns an equal weight of 1 for all samples.
            % * __VarType__ optional vector of type `uint8` and size
            %   `<num_of_vars_in_samples> + <num_of_vars_in_responses>`,
            %   containing types of each input and output variable. By default
            %   considers all variables as numerical (both input and output
            %   variables). In case there is only one output variable of
            %   integer type, it is considered categorical. You can also
            %   specify a cell-array of strings (or as one string of single
            %   characters, e.g 'NNNC'). Possible values:
            %   * __Numerical__, __N__ same as 'Ordered'
            %   * __Ordered__, __O__ ordered variables
            %   * __Categorical__, __C__ categorical variables
            % * __MissingMask__ Indicator mask for missing observation (not
            %   currently implemented). Not set by default
            % * __TrainTestSplitCount__ divides the dataset into train/test
            %   sets, by specifying number of samples to use for the test set.
            %   By default all samples are used for the training set.
            % * __TrainTestSplitRatio__ divides the dataset into train/test
            %   sets, by specifying ratio of samples to use for the test set.
            %   By default all samples are used for the training set.
            % * __TrainTestSplitShuffle__ when splitting dataset into
            %   train/test sets, specify whether to shuffle the samples.
            %   Otherwise samples are assigned sequentially (first train then
            %   test). default true
            %
            % ### Options for `Data` (second variant for loading CSV file)
            % * __HeaderLineCount__ The number of lines in the beginning to
            %   skip; besides the header, the function also skips empty lines
            %   and lines staring with '#'. default 1
            % * __ResponseStartIdx__ Index of the first output variable. If
            %   -1, the function considers the last variable as the response.
            %   If the dataset only contains input variables and no responses,
            %   use `ResponseStartIdx = -2` and `ResponseEndIdx = 0`, then the
            %   output variables vector will just contain zeros. default -1
            % * __ResponseEndIdx__ Index of the last output variable + 1. If
            %   -1, then there is single response variable at
            %   `ResponseStartIdx`. default -1
            % * __VarTypeSpec__ The optional text string that specifies the
            %   variables' types. It has the format
            %   `ord[n1-n2,n3,n4-n5,...]cat[n6,n7-n8,...]`. That is, variables
            %   from `n1` to `n2` (inclusive range), `n3`, `n4` to `n5` ...
            %   are considered ordered and `n6`, `n7` to `n8` ... are
            %   considered as categorical. The range
            %   `[n1..n2] + [n3] + [n4..n5] + ... + [n6] + [n7..n8]` should
            %   cover all the variables. If `VarTypeSpec` is not specified,
            %   then algorithm uses the following rules:
            %   * all input variables are considered ordered by default. If
            %     some column contains has non- numerical values, e.g.
            %     'apple', 'pear', 'apple', 'apple', 'mango', the
            %     corresponding variable is considered categorical.
            %   * if there are several output variables, they are all
            %     considered as ordered. Errors are reported when
            %     non-numerical values are used.
            %   * if there is a single output variable, then if its values are
            %     non-numerical or are all integers, then it's considered
            %     categorical. Otherwise, it's considered ordered.
            % * __Delimiter__ The character used to separate values in each
            %   line. default ','
            % * __Missing__ The character used to specify missing
            %   measurements. It should not be a digit. Although it's a
            %   non-numerical value, it surely does not affect the decision of
            %   of whether the variable ordered or categorical. default '?'
            % * __TrainTestSplitCount__ same as above.
            % * __TrainTestSplitRatio__ same as above.
            % * __TrainTestSplitShuffle__ same as above.
            %
            % The training algorithm uses one-vs-rest scheme for multiclass
            % case (i.e a binary problem is fit for each label).
            %
            % See also: cv.LogisticRegression.predict, cv.LogisticRegression.calcError
            %
            status = LogisticRegression_(this.id, 'train', samples, responses, varargin{:});
        end

        function [err,resp] = calcError(this, samples, responses, varargin)
            %CALCERROR  Computes error on the training or test dataset
            %
            %     err = model.calcError(samples, responses)
            %     err = model.calcError(csvFilename, [])
            %     [err,resp] = model.calcError(...)
            %     [...] = model.calcError(..., 'OptionName', optionValue, ...)
            %
            % ## Input
            % * __samples__ See the train method.
            % * __responses__ See the train method.
            % * __csvFilename__ See the train method.
            %
            % ## Output
            % * __err__ computed error.
            % * __resp__ the optional output responses.
            %
            % ## Options
            % * __Data__ See the train method.
            % * __TestError__ if true, the error is computed over the test
            %   subset of the data, otherwise it's computed over the training
            %   subset of the data. Please note that if you loaded a
            %   completely different dataset to evaluate an already trained
            %   classifier, you will probably want not to set the test subset
            %   at all with `TrainTestSplitRatio` and specify
            %   `TestError=false`, so that the error is computed for the whole
            %   new set. Yes, this sounds a bit confusing. default false
            %
            % The method uses the predict method to compute the error. For
            % regression models the error is computed as RMS, for classifiers
            % as a percent of missclassified samples (0%-100%).
            %
            % See also: cv.LogisticRegression.train, cv.LogisticRegression.predict
            %
            [err,resp] = LogisticRegression_(this.id, 'calcError', samples, responses, varargin{:});
        end

        function [results,f] = predict(this, samples, varargin)
            %PREDICT  Predicts responses for input samples
            %
            %     [results,f] = model.predict(samples)
            %     [...] = model.predict(..., 'OptionName', optionValue, ...)
            %
            % ## Input
            % * __samples__ The input data for the prediction algorithm. `MxN`
            %   matrix, where each row contains variables (features) of one
            %   object being classified. Should have `single` data type.
            %
            % ## Output
            % * __results__ Predicted labels as a column vector of `int32`
            %   type. In case `RawOutput` was set, it returns a `single`
            %   matrix of size `size(samples,1)-by-size(thetas,1)` which
            %   contains the raw output of the sigmoid function(s).
            % * __f__ The same as the response of the first sample, i.e
            %   `results(1)`.
            %
            % ## Options
            % * __Flags__ The optional predict flags, model-dependent. For
            %   convenience, you can set the individual flag options below,
            %   instead of directly setting bits here. default 0
            % * __RawOutput__ makes the method return the raw results (the
            %   value of the sigmoid function), not the class label.
            %   default false
            %
            % See also: cv.LogisticRegression.train, cv.LogisticRegression.calcError
            %
            [results,f] = LogisticRegression_(this.id, 'predict', samples, varargin{:});
        end
    end

    %% LogisticRegression
    methods
        function thetas = getLearntThetas(this)
            %GETLEARNTTHETAS  Returns the trained parameters
            %
            %     thetas = model.getLearntThetas()
            %
            % ## Output
            % * __thetas__ It returns learnt parameters of the Logistic
            %   Regression as a matrix of type `single` arranged across rows.
            %   For a two-class classifcation problem, it returns a single row
            %   matrix.
            %
            % `thetas` is a matrix of size `nclasses-by-model.getVarCount()`
            % if `nclasses>2`, otherwise `1-by-model.getVarCount()` if
            % `nclasses=2`.
            %
            % See also: cv.LogisticRegression.train
            %
            thetas = LogisticRegression_(this.id, 'get_learnt_thetas');
        end
    end

    %% Getters/Setters
    methods
        function value = get.Iterations(this)
            value = LogisticRegression_(this.id, 'get', 'Iterations');
        end
        function set.Iterations(this, value)
            LogisticRegression_(this.id, 'set', 'Iterations', value);
        end

        function value = get.LearningRate(this)
            value = LogisticRegression_(this.id, 'get', 'LearningRate');
        end
        function set.LearningRate(this, value)
            LogisticRegression_(this.id, 'set', 'LearningRate', value);
        end

        function value = get.MiniBatchSize(this)
            value = LogisticRegression_(this.id, 'get', 'MiniBatchSize');
        end
        function set.MiniBatchSize(this, value)
            LogisticRegression_(this.id, 'set', 'MiniBatchSize', value);
        end

        function value = get.Regularization(this)
            value = LogisticRegression_(this.id, 'get', 'Regularization');
        end
        function set.Regularization(this, value)
            LogisticRegression_(this.id, 'set', 'Regularization', value);
        end

        function value = get.TermCriteria(this)
            value = LogisticRegression_(this.id, 'get', 'TermCriteria');
        end
        function set.TermCriteria(this, value)
            LogisticRegression_(this.id, 'set', 'TermCriteria', value);
        end

        function value = get.TrainMethod(this)
            value = LogisticRegression_(this.id, 'get', 'TrainMethod');
        end
        function set.TrainMethod(this, value)
            LogisticRegression_(this.id, 'set', 'TrainMethod', value);
        end
    end

end


function [nlogL,nlogLvar,exitflag,output] = ibslike(fun,params,respMat,designMat,options,varargin)
%IBSLIKE Unbiased negative log-likelihood via inverse binomial sampling.
%   NLOGL = IBLLIKE(FUN,PARAMS,RESPMAT,DESIGNMAT) returns unbiased estimate
%   NLOGL of the negative of the log-likelihood for the simulated model
%   and data, calculated using inverse binomial sampling (IBS).
%   FUN is a function handle to a function that simulates the model's
%   responses (see below).
%   PARAMS is the parameter vector used to simulate the model's responses.
%   RESPMAT is a "response" data matrix, where each row correspond to one
%   observation or "trial" (e.g., a trial of a psychophysical experiment),
%   and each column represents a different response feature (e.g., the
%   subject's response and reported confidence level). Responses need to
%   belong to a finite set.
%   DESIGNMAT is an optional experimental design matrix, where each row
%   corresponds to one trial, and each column corresponds to a different
%   trial feature (such as condition, stimulus value, etc.).
%
%   FUN takes as input a vector of parameters PARAMS and an experimental
%   design matrix DMAT (one row per trial), and generates a matrix of
%   simulated model responses (one row per trial, corresponding to rows of
%   DMAT). DMAT is built by the algorithm out of rows of DESIGNMAT.
%
%   DESIGNMAT can be omitted or left empty, in which case FUN needs to
%   accept a parameter vector PARAMS and an array of trial numbers T, and
%   returns a matrix of simulated responses, where the i-th row contains
%   the simulated response for trial T(i) (the indices in T may repeat).
%
%   NLOGL = IBSLIKE(FUN,PARAMS,RESPMAT,DESIGNMAT,OPTIONS) uses options in
%   structure OPTIONS to replace default values. (To be explained...)
%
%   NLOGL = IBSLIKE(...,VARARGIN) additional arguments are passed to FUN.
%
%   [NLOGL,NLOGLVAR] = IBSLIKE(...) also returns an estimate NLOGVAR of the
%   variance of the log likelihood.
%
%   [NLOGL,NLOGLVAR,EXITFLAG] = IBSLIKE(...) returns an EXITFLAG that
%   describes the exit condition. Possible values of EXITFLAG and the
%   corresponding exit conditions are
%
%    2  IBS terminated after reaching the maximum runtime specified by the
%       user (the estimate can be arbitrarily biased).
%    1  IBS terminated after reaching the negative log-likelihood
%       threshold specified by the user (the estimate is biased).
%    0  Correct run of IBS; the estimate is unbiased.
%
%   [NLOGL,NLOGLVAR,EXITFLAG,OUTPUT] = IBSLIKE(...) returns a structure
%   OUTPUT with additional information about the sampling.
%
%   OPTIONS = IBSLIKE('defaults') returns a basic default OPTIONS structure.
%
%   EXITFLAG = IBSLIKE('test') runs some tests. Here EXITFLAG is 0 if
%   everything works correctly.
%
%   Test code on binomial sampling:
%      p = 0.7; Ntrials = 100;                  % Define binomial probability
%      fun = @(x,dmat) rand(size(dmat)) < x;    % Simulating function
%      rmat = fun(p,NaN(Ntrials,1));            % Generate responses
%      [nlogL,nlogLvar,pc,output] = ibslike(fun,p,rmat);
%      nlogL_true = -log(p)*sum(rmat == 1) - log(1-p)*sum(rmat == 0);
%      fprintf('Ground truth: %.4g, Estimate: %.4g ± %.4g.\n',nlogL_true,nlogL,sqrt(nlogLvar));
%
%   Reference:
%   van Opheusden*, B., Acerbi*, L. & Ma, W. J. (2020), "Unbiased and
%   efficient log-likelihood estimation with inverse binomial sampling".
%   (* equal contribution), PLoS Computational Biology 16(12): e1008483.
%   Link: https://doi.org/10.1371/journal.pcbi.1008483
%
%   See also @.

%--------------------------------------------------------------------------
% IBS: Inverse Binomial Sampling for unbiased log-likelihood estimation
% To be used under the terms of the MIT License
% (https://opensource.org/licenses/MIT).
%
%   Authors (copyright): Luigi Acerbi and Bas van Opheusden, 2020-2022
%   e-mail: luigi.acerbi@helsinki.fi, svo@princeton.edu
%   URL: http://luigiacerbi.com
%   Version: 0.96
%   Release date: Jan 21, 2021
%   Code repository: https://github.com/acerbilab/ibs
%--------------------------------------------------------------------------

if nargin < 4; designMat = []; end
if nargin < 5; options = []; end

t0 = tic;

% Default options
% defopts.Display       = 'off';        % Level of display on screen
defopts.Nreps           = 10;           % # independent log-likelihood estimates per trial
defopts.NegLogLikeThreshold = Inf;      % Stop sampling if estimated nLL is above threshold (incompatible with vectorized sampling)
defopts.Vectorized      = 'auto';       % Use vectorized sampling algorithm with acceleration
defopts.Acceleration    = 1.5;          % Acceleration factor for vectorized sampling
defopts.NsamplesPerCall = 0;            % # starting samples per trial per function call (0 = choose automatically)
defopts.MaxIter         = 1e5;          % Maximum number of iterations (per trial and estimate)
defopts.ReturnPositive  = false;        % If true, the first returned output is the *positive* log-likelihood
defopts.ReturnStd       = false;        % If true, the second returned output is the standard deviation of the estimate
defopts.MaxTime         = Inf;          % Maximum time for a IBS call (in seconds)
defopts.TrialWeights    = [];           % Vector of per-trial weights to yield a weighted sum of log-likelihood

%% If called with no arguments or with 'defaults', return default options
if nargout <= 1 && (nargin == 0 || (nargin == 1 && ischar(fun) && strcmpi(fun,'defaults')))
    if nargin < 1
        fprintf('Basic default options returned (type "help ibslike" for help).\n');
    end
    nlogL = defopts;
    return;
end

%% If called with the first argument as 'test', run test
if ischar(fun) && strcmpi(fun,'test')
    if nargin < 2; options = []; else; options = params; end
    figure;
    subplot(1,3,1);
    exitflag(1) = runtest1(options);
    subplot(1,3,2);
    exitflag(2) = runtest2(options);
    subplot(1,3,3);
    exitflag(3) = runtest3(options);
    nlogL = any(exitflag);
    return;
end

for f = fields(defopts)'
    if ~isfield(options,f{:}) || isempty(options.(f{:}))
        options.(f{:}) = defopts.(f{:});
    end
end

Ntrials = size(respMat,1);

% Add hard-coded options
options.MaxSamples = 1e4;                       % Maximum # of samples per function call
options.AccelerationThreshold = 0.1;            % Keep accelerating until threshold is passed (in s)
options.VectorizedThreshold  = 0.1;             % Max threshold for using vectorized algorithm (in s)
options.MaxMem = 1e6;                           % Maximum number of samples for vectorized implementation
options.MaxMem = max(min(Ntrials,1e4),10)*100;  % Maximum number of samples for vectorized implementation

% NSAMPLESPERCALL should be a scalar integer
if ~isnumeric(options.NsamplesPerCall) || ~isscalar(options.NsamplesPerCall)
    error('ibslike:NsamplesPerCall','OPTIONS.NsamplesPerCall should be a scalar integer.');
end

% ACCELERATION should be a scalar equal or greater than 1
if ~isnumeric(options.Acceleration) || ~isscalar(options.Acceleration) || ...
        options.Acceleration < 1
    error('ibslike:Acceleration','OPTIONS.Acceleration should be a scalar equal or greater than one.');
end

% NEGLOGLIKETHRESHOLD should be a scalar greater than 0 (or Inf)
if ~isnumeric(options.NegLogLikeThreshold) || ~isscalar(options.NegLogLikeThreshold) || ...
        options.NegLogLikeThreshold <= 0
    error('ibslike:NegLogLikeThreshold','OPTIONS.NegLogLikeThreshold should be a positive scalar (including Inf).');
end

% MAXTIME should be a positive scalar (including Inf)
if ~isnumeric(options.MaxTime) || ~isscalar(options.MaxTime) || ...
        options.MaxTime <= 0
    error('ibslike:MaxTime','OPTIONS.MaxTime should be a positive scalar (or Inf).');
end

% WEIGHT vector should be a scalar or same as number of trials
weights = options.TrialWeights(:);
if isempty(weights); weights = 1; end

if numel(weights) ~= 1 && numel(weights) ~= Ntrials
    error('ibslike:NumWeights','OPTIONS.TrialWeights should be empty, a scalar or an array of weights with as many elements as the number of trials.');
end

Trials = (1:Ntrials)';
funcCount = 0;

simdata = []; elapsed_time = [];

% Use vectorized or loop version?
if ischar(options.Vectorized) && options.Vectorized(1) == 'a'
    if options.Nreps == 1
        vectorized_flag = false;
    else
        % First full simulation to determine computation time
        fun_clock = tic;
        if isempty(designMat)    % Pass only trial indices
            simdata = fun(params,Trials(:),varargin{:});
        else                    % Pass full design matrix per trial
            simdata = fun(params,designMat(Trials(:),:),varargin{:});
        end
        elapsed_time = toc(fun_clock);
        vectorized_flag = elapsed_time < options.VectorizedThreshold;
        funcCount = 1;
    end
else
    vectorized_flag = logical(options.Vectorized);
    if options.Nreps == 1 && vectorized_flag
        vectorized_flag = false;
        warning('Vectorized IBS requires OPTIONS.Nreps > 1. Switching to non-vectorized algorithm.');
    end
end

if vectorized_flag
    [nlogL,K,Nreps,Ns,fc,exitflag] = ...
        vectorized_ibs_sampling(fun,params,respMat,designMat,simdata,elapsed_time,t0,options,varargin{:});
else
    [nlogL,K,Nreps,Ns,fc,exitflag] = ...
        loop_ibs_sampling(fun,params,respMat,designMat,simdata,elapsed_time,t0,options,varargin{:});
end
funcCount = funcCount + fc;

% Variance of estimate per trial
if nargout > 1
    K_max = max(max(K(:),1));
    Ktab = -(psi(1,1:K_max)' - psi(1,1));
    LLvar = Ktab(max(K,1));
    nlogLvar = sum(LLvar,2)./Nreps.^2;
end

% OUTPUT structure with additional information
if nargout > 2
    output.funcCount = funcCount;
    output.NsamplesPerTrial = Ns/Ntrials;
    output.nlogL_trials = nlogL;
    output.nlogLvar_trials = nlogLvar;
end

% Return negative log-likelihood and variance summed over trials
nlogL = sum(nlogL.*weights);
if options.ReturnPositive; nlogL = -nlogL; end
if nargout > 1
    nlogLvar = sum(nlogLvar.*(weights.^2));
    if options.ReturnStd    % Return standard deviation instead of variance
        nlogLvar = sqrt(nlogLvar);
    end
end

end

%--------------------------------------------------------------------------
function [nlogL,K,Nreps,Ns,fc,exitflag] = vectorized_ibs_sampling(fun,params,respMat,designMat,simdata0,elapsed_time0,t0,options,varargin)

Ntrials = size(respMat,1);
Trials = (1:Ntrials)';
Ns = 0;
fc = 0;
exitflag = 0;

Psi_tab = [];   % Empty PSI table

% Empty matrix of K values (samples-to-hit) for each repeat for each trial
K_mat = zeros([max(options.Nreps),Ntrials]);

% Matrix of rep counts
K_place0 = repmat((1:size(K_mat,1))',[1,Ntrials]);

% Current rep being sampled for each trial
Ridx = ones(1,Ntrials);

% Current vector of "open" K values per trial (not reached a "hit" yet)
K_open = zeros(1,Ntrials);

targetHits = options.Nreps(:)'.*ones(1,Ntrials);
MaxIter = options.MaxIter*max(options.Nreps);

% Starting samples
if options.NsamplesPerCall == 0
    samples_level = options.Nreps;
else
    samples_level = options.NsamplesPerCall;
end

for iter = 1:MaxIter
    % Pick trials that need more hits, sample multiple times
    T = Trials(Ridx <= targetHits);
    if isfinite(options.MaxTime) && toc(t0) > options.MaxTime
        T = [];
        exitflag = 2;
    end
    if isempty(T); break; end

    Ttrials = numel(T);    % Number of trials under consideration

    % With accelerated sampling, might request multiple samples at once
    Nsamples = min(options.MaxSamples,max(1,round(samples_level)));
    MaxSamples = ceil(options.MaxMem / Ttrials);
    Nsamples = min(Nsamples, MaxSamples);
    Tmat = repmat(T,[1,Nsamples]);

    % Simulate trials
    if iter == 1 && Nsamples == 1 && ~isempty(simdata0)
        simdata = simdata0;
        elapsed_time = elapsed_time0;
    else
        fun_clock = tic;
        if isempty(designMat)    % Pass only trial indices
            simdata = fun(params,Tmat(:),varargin{:});
            fc = fc + 1;
        else                    % Pass full design matrix per trial
            simdata = fun(params,designMat(Tmat(:),:),varargin{:});
            fc = fc + 1;
        end
        elapsed_time = toc(fun_clock);
    end

    % Check that the returned simulated data have the right size
    if size(simdata,1) ~= numel(Tmat)
        error('ibslike:SizeMismatch', ...
            'Number of rows of returned simulated data does not match the number of requested trials.');
    end

    Ns = Ns + Ttrials;

    % Accelerated sampling
    if options.Acceleration > 0 && elapsed_time < options.AccelerationThreshold
        samples_level = samples_level*options.Acceleration;
    end

    % Check new "hits"
    hits_temp = all(respMat(Tmat(:),:) == simdata,2);

    % Build matrix of new hits (sandwich with buffer of hits, then removed)
    hits_new = [ones(1,Ttrials);reshape(hits_temp,size(Tmat))';ones(1,Ttrials)];

    % Warning: from now on it's going to be incomprehensible
    % (all vectorized for speed)

    % Extract matrix of Ks from matrix of hits for this iteration
    h = size(hits_new,1);
    list = find(hits_new(:) == 1)-1;
    row = floor(list/h)+1;
    col = mod(list,h)+1;
    delta = diff([col;1]);
    remidx = delta <= 0;
    delta(remidx) = [];
    row(remidx) = [];
    indexcol = find(diff([0;row]));
    col = 1 + (1:numel(row))' - indexcol(row);
    K_iter = zeros(size(T,1),max(col));
    K_iter(row + (col-1)*size(K_iter,1)) = delta;

    % This is the comprehensible version that we want to get to:
    %
    %   for iTrial = 1:Ntrials
    %       index = find(hits_new(iTrial,:),targetHits(iTrial));
    %       K = diff([0 index]);
    %       logL(iTrial) = sum(Ktab(K))/numel(index);
    %   end

    % Add still-open K to first column
    K_iter(:,1) = K_open(T)' + K_iter(:,1);

    % Find last K position for each trial
    [~,idx_last] = min([K_iter,zeros(Ttrials,1)],[],2);
    idx_last = idx_last - 1;
    ii = sub2ind(size(K_iter),(1:Ttrials)',idx_last);

    % Subtract one hit from last K (it was added)
    K_iter(ii) = K_iter(ii) - 1;
    K_open(T) = K_iter(ii)';

    % For each trial, ignore entries of K_iter past max # of reps
    idx_mat = bsxfun(@plus,Ridx(T)',repmat(0:size(K_iter,2)-1,[Ttrials,1]));
    K_iter(idx_mat > (options.Nreps)) = 0;

    % Find last K position for each trial again
    [~,idx_last2] = min([K_iter,zeros(Ttrials,1)],[],2);
    idx_last2 = idx_last2 - 1;

    % Add current K to full K matrix
    K_iter_place = bsxfun(@ge,K_place0(:,1:Ttrials),Ridx(T)) & bsxfun(@le,K_place0(:,1:Ttrials),Ridx(T) + idx_last2'- 1);
    K_place = false(size(K_place0));
    K_place(:,T) = K_iter_place;
    Kt = K_iter';
    K_mat(K_place) = Kt(Kt > 0);
    Ridx(T) = Ridx(T) + idx_last' - 1;

    % Compute log-likelihood only if requested for thresholding
    if isfinite(options.NegLogLikeThreshold)
        Rmin = min(Ridx(T));    % Find repeat still ongoing
        if Rmin > size(K_mat,1); continue; end
        [LL_temp,Psi_tab] = get_LL_from_K(Psi_tab,K_mat(Rmin,:));
        nLL_temp = -sum(LL_temp,1);
        if nLL_temp > options.NegLogLikeThreshold
            idx_move = Ridx == Rmin;
            Ridx(idx_move) = Rmin+1;
            K_open(idx_move) = 0;
            exitflag = 1;
        end
    end
end

if ~isempty(T)
    error('ibslike:ConvergenceFail', ...
        'Maximum number of iterations or time limit reached and algorithm did not converge. Check FUN and DATA.');
end

% Log likelihood estimate per trial and run lengths K for each repetition
Nreps = sum(K_mat > 0,1)';
[LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K_mat);
nlogL = sum(-LL_mat',2)./Nreps;
K = K_mat';

end

%--------------------------------------------------------------------------
function [nlogL,K,Nreps,Ns,fc,exitflag] = loop_ibs_sampling(fun,params,respMat,designMat,simdata0,elapsed_time0,t0,options,varargin)

Ntrials = size(respMat,1);
Trials = (1:Ntrials)';
MaxIter = options.MaxIter;
exitflag = 0;

K = zeros(Ntrials,options.Nreps);
Ns = 0;
fc = 0;
Psi_tab = [];

for iRep = 1:options.Nreps

    offset = 1;
    hits = zeros(Ntrials,1);
    if isfinite(options.MaxTime) && toc(t0) > options.MaxTime
        exitflag = 2;
        break;
    end

    for iter = 1:MaxIter
        % Pick trials that need more hits, sample multiple times
        T = Trials(hits < 1);
        if isempty(T); break; end

        % Simulate trials
        if iter == 1 && iRep == 1 && ~isempty(simdata0)
            simdata = simdata0;
        elseif isempty(designMat)    % Pass only trial indices
            simdata = fun(params,T(:),varargin{:});
            fc = fc + 1;
        else                    % Pass full design matrix per trial
            simdata = fun(params,designMat(T(:),:),varargin{:});
            fc = fc + 1;
        end

        % Check that the returned simulated data have the right size
        if size(simdata,1) ~= numel(T)
            error('ibslike:SizeMismatch', ...
                'Number of rows of returned simulated data does not match the number of requested trials.');
        end

        Ns = Ns + numel(T); % Count samples
        hits_new = all(respMat(T(:),:) == simdata,2);
        hits(T) = hits(T) + hits_new;

        K(T(hits_new),iRep) = offset;
        offset = offset + 1;

        % Terminate if negative log likelihood is above a given threshold
        if isfinite(options.NegLogLikeThreshold)
            K(hits < 1,iRep) = offset;
            [LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K(:,iRep));
            nlogL_sum = -sum(LL_mat,1);
            if nlogL_sum > options.NegLogLikeThreshold
                T = [];
                exitflag = 1;
                break;
            end
        end

        % Terminate if above maximum allowed runtime
        if isfinite(options.MaxTime) && toc(t0) > options.MaxTime
            T = [];
            exitflag = 2;
            break;
        end

    end

    if ~isempty(T)
        error('ibslike:ConvergenceFail', ...
            'Maximum number of iterations reached and algorithm did not converge. Check FUN and DATA.');
    end
end

Nreps = sum(K > 0,2);
[LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K);
nlogL = sum(-LL_mat,2)./Nreps;

end

%--------------------------------------------------------------------------
function [LL_mat,Psi_tab] = get_LL_from_K(Psi_tab,K_mat)
%GET_LL_FROM_K Convert matrix of K values into log-likelihoods.

K_max = max(1,max(K_mat(:)));
if K_max > numel(Psi_tab)   % Fill digamma function table
    Psi_tab = [Psi_tab; (psi(1) - psi(numel(Psi_tab)+1:K_max)')];
end
LL_mat = Psi_tab(max(1,K_mat));

end

%--------------------------------------------------------------------------
function exitflag = runtest1(options)

Nreps = 1e3;
RMSE_tol = 2/sqrt(Nreps);

% Binomial probability model
p_model = exp(linspace(log(1e-3),log(1),10));
fun = @(x,dmat) rand(size(dmat)) < x;   % Simulating function
rmat = fun(1,NaN);

fprintf('\n');
fprintf('TEST 1: Using IBS to compute log(p) of Bernoulli distributions with %d repeats.\n',Nreps);
fprintf('We consider p = %s.\n',mat2str(p_model,3));

options.Nreps = Nreps;
options.NegLogLikeThreshold = Inf;

nlogL = zeros(1,numel(p_model));
nlogLvar = zeros(1,numel(p_model));
for iter = 1:numel(p_model)
    [nlogL(iter),nlogLvar(iter)] = ibslike(fun,p_model(iter),rmat,[],options);
end

% We expect the true value to be almost certainly (> 99.99%) in this range
LL_min = (-nlogL - 4*sqrt(nlogLvar));
LL_max = (-nlogL + 4*sqrt(nlogLvar));

exitflag = any(log(p_model) < LL_min) | any(log(p_model) > LL_max);

rmse = sqrt(mean((-nlogL - log(p_model)).^2));
fprintf('Average RMSE of log(p) estimates across p: %.4f.\n',rmse);

exitflag = exitflag | (rmse > RMSE_tol);

if exitflag
    fprintf('Test FAILED. Something might be wrong.\n');
else
    fprintf('Test PASSED. IBS estimates are calibrated and close to ground truth.\n');
end

% Plot figure
xx = log(p_model);
h(1) = plot(xx,xx,'k-','LineWidth',2); hold on;

yy = -nlogL;
xxerr = [xx, fliplr(xx)];
yyerr_down = yy - 1.96*sqrt(nlogLvar);
yyerr_up = yy + 1.96*sqrt(nlogLvar);
yyerr = [yyerr_down, fliplr(yyerr_up)];
fill(xxerr, yyerr,'b','FaceAlpha',0.5,'LineStyle','none'); hold on;
h(2) = plot(xx,yy,'b-','LineWidth',2); hold on;

box off;
set(gca,'TickDir','out');
set(gcf,'Color','w');
xlabel('True log(p)');
ylabel('Estimated log(p)')
%xlim([-5 5]);
hl = legend(h,'True log(p)','IBS estimate (95% CI)');
set(hl,'Location','NorthWest','Box','off');
title('IBS estimation test');

end

%--------------------------------------------------------------------------
function exitflag = runtest2(options)

% Binomial probability model
Ntrials = 100;
p_true = 0.9*rand() + 0.05;             % True probability
p_model = 0.9*rand() + 0.05;            % Model probability
fun = @(x,dmat) rand(size(dmat)) < x;   % Simulating function
Nexps = 2e3;

options.NegLogLikeThreshold = Inf;

fprintf('\n');
fprintf('TEST 2: Using IBS to compute the log-likelihood of a binomial distribution.\n');
fprintf('Parameters: p_true=%.2g, p_model=%.2g, %d trials per experiment.\n',p_true,p_model,Ntrials);
fprintf('The distribution of z-scores should approximate a standard normal distribution (mean 0, SD 1).\n');

zscores = zeros(1,Nexps);
for iter = 1:Nexps
    rmat = fun(p_true,NaN(Ntrials,1));            % Generate data
    [nlogL,nlogLvar] = ibslike(fun,p_model,rmat,[],options);
    nlogL_exact = -log(p_model)*sum(rmat == 1) - log(1-p_model)*sum(rmat == 0);
    zscores(iter) = (nlogL_exact - nlogL)/sqrt(nlogLvar);
end

edges = -4.75:0.5:4.75;
nz = histc(zscores,edges);
h(1) = bar(edges,nz,'histc');
hold on;
xx = linspace(-5,5,1e4);
h(2) = plot(xx,Nexps*exp(-xx.^2/2)/sqrt(2*pi)/2,'k-','LineWidth',2);

box off;
set(gca,'TickDir','out');
set(gcf,'Color','w');
xlabel('z-score');
ylabel('pdf')
xlim([-5 5]);
hl = legend(h,'z-scores histogram','expected pdf');
set(hl,'Location','NorthEast','Box','off');
title('Calibration test');

exitflag = abs(mean(zscores)) > 0.15 || abs(std(zscores) - 1) > 0.1;

fprintf('Distribution of z-scores (%d experiments). Mean: %.4g. Standard deviation: %.4g.\n',Nexps,mean(zscores),std(zscores));
if exitflag
    fprintf('Test FAILED. Something might be wrong.\n');
else
    fprintf('Test PASSED. We verified that IBS is unbiased (~zero mean) and calibrated (SD ~1).\n');
end

end

%--------------------------------------------------------------------------
function exitflag = runtest3(options)

Nreps = 100;
RMSE_tol = 4/sqrt(Nreps);

% Binomial probability model
p_model = exp(linspace(log(1e-3),log(0.1),10));
fun = @(x,dmat) rand(size(dmat)) < x;   % Simulating function
rmat = fun(1,NaN);
thresh = -log(0.01);
p_target = max(p_model,exp(-thresh));

fprintf('\n');
fprintf('TEST 3: Log-likelihood thresholding at log(p) = %.3f.\n',-thresh);
fprintf('Using IBS to compute thresholded log(p) of Bernoulli distributions with %d repeats.\n',Nreps);
fprintf('We consider p = %s.\n',mat2str(p_model,3));

options.Nreps = Nreps;
options.NegLogLikeThreshold = thresh;
options.Acceleration = 1;

nlogL = zeros(1,numel(p_model));
nlogLvar = zeros(1,numel(p_model));
for iter = 1:numel(p_model)
    [nlogL(iter),nlogLvar(iter)] = ibslike(fun,p_model(iter),rmat,[],options);
end

% We expect the true value to be almost certainly (> 99.99%) in this range
LL_min = (-nlogL - 4*sqrt(nlogLvar));
LL_max = (-nlogL + 4*sqrt(nlogLvar));

% We expect the estimates to be (almost) correct away from the threshold
idx = log(p_model) > -thresh*0.75;
exitflag = any(log(p_model(idx)) < LL_min(idx)) | any(log(p_model(idx)) > LL_max(idx));

% We expect the estimates to be above the true value below the threshold
LL_thresh = (-nlogL - sqrt(nlogLvar));
idx_below = log(p_model) < -thresh;
exitflag = exitflag | any(log(p_model(idx_below)) > LL_thresh(idx_below));
% exitflag = any(log(p_target) < LL_min) | any(log(p_target) > LL_max);

rmse = sqrt(mean((-nlogL(idx) - log(p_target(idx))).^2));
fprintf('Average RMSE of log(p) estimates across p: %.4f.\n',rmse);

exitflag = exitflag | (rmse > RMSE_tol);

if exitflag
    fprintf('Test FAILED. Something might be wrong.\n');
else
    fprintf('Test PASSED. IBS estimates are calibrated and close to (thresholded) ground truth.\n');
end

% Plot figure
xx = log(p_model);
h(1) = plot(xx,xx,'k-','LineWidth',2); hold on;

yy = -nlogL;
xxerr = [xx, fliplr(xx)];
yyerr_down = yy - 1.96*sqrt(nlogLvar);
yyerr_up = yy + 1.96*sqrt(nlogLvar);
yyerr = [yyerr_down, fliplr(yyerr_up)];
fill(xxerr, yyerr,'b','FaceAlpha',0.5,'LineStyle','none'); hold on;

h(2) = plot(xx,yy,'b-','LineWidth',2); hold on;
h(3) = plot([xx(1),xx(end)],-thresh*[1 1],'k:','LineWidth',2);

box off;
set(gca,'TickDir','out');
set(gcf,'Color','w');
xlabel('True log(p)');
ylabel('Estimated log(p) with thresholding')
%xlim([-5 5]);
hl = legend(h,'True log(p)','IBS estimate (95% CI)','Threshold');
set(hl,'Location','NorthWest','Box','off');
title('Thresholded IBS test');

end

%   TODO:
%   - Fix help and documentation
%   - Optimal allocation of estimates?