(minor) timer

(minor) timer

mata:

// --------------------------------------------------------------------------
// Acceleration Schemes
// --------------------------------------------------------------------------

`Variables' function accelerate_test(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter, g
	`Variables'		resid
	`Factor' f
	pragma unset resid

	for (iter=1; iter<=S.maxiter; iter++) {
		for (g=1; g<=S.G; g++) {
			f = S.factors[g]
			if (g==1) resid = y - panelmean(f.sort(y), f)[f.levels, .]
			else resid = resid - panelmean(f.sort(resid), f)[f.levels, .]
		}
		if (check_convergence(S, iter, resid, y)) break
		y = resid
	}
	return(resid)
}

// --------------------------------------------------------------------------

`Variables' function accelerate_none(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter
	`Variables'		resid
	pragma unset resid

	for (iter=1; iter<=S.maxiter; iter++) {
		(*T)(S, y, resid) // Faster version of "resid = S.T(y)"
		if (check_convergence(S, iter, resid, y)) break
		y = resid
	}
	return(resid)
}
// --------------------------------------------------------------------------

// Start w/out acceleration, then switch to CG
`Variables' function accelerate_hybrid(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer' iter, accel_start
	`Variables' resid
	pragma unset resid

	accel_start = 6

	for (iter=1; iter<=accel_start; iter++) {
		(*T)(S, y, resid) // Faster version of "resid = S.T(y)"
		if (check_convergence(S, iter, resid, y)) break
		y = resid
	}

	T = &transform_sym_kaczmarz() // Override

	return(accelerate_cg(S, y, T))
}

// --------------------------------------------------------------------------
// Memory cost is approx = 4*size(y) (actually 3 since y is already there)
// But we need to add maybe 1 more due to u:*v
// And I also need to check how much does project and T use..
// Double check with a call to memory

// For discussion on the stopping criteria, see the following presentation:
// Arioli & Gratton, "Least-squares problems, normal equations, and stopping criteria for the conjugate gradient method". URL: https://www.stfc.ac.uk/SCD/resources/talks/Arioli-NAday2008.pdf

// Basically, we will use the Hestenes and Stiefel rule

`Variables' function accelerate_cg(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	// BUGBUG iterate the first 6? without acceleration??
	`Integer'	iter, d, Q
	`Variables'		r, u, v
	`RowVector' alpha, beta, ssr, ssr_old, improvement_potential
	`Matrix' recent_ssr
	pragma unset r
	pragma unset v

	Q = cols(y)

	d = 1 // BUGBUG Set it to 2/3 // Number of recent SSR values to use for convergence criteria (lower=faster & riskier)
	// A discussion on the stopping criteria used is described in
	// http://scicomp.stackexchange.com/questions/582/stopping-criteria-for-iterative-linear-solvers-applied-to-nearly-singular-system/585#585

	improvement_potential = weighted_quadcolsum(S, y, y)
	recent_ssr = J(d, Q, .)

	if (S.timeit) timer_on(71)
	(*T)(S, y, r, 1)
	if (S.timeit) timer_off(71)
	ssr = weighted_quadcolsum(S, r, r) // cross(r,r) when cols(y)==1 // BUGBUG maybe diag(quadcross()) is faster?
	u = r

	for (iter=1; iter<=S.maxiter; iter++) {
		if (S.timeit) timer_on(71)
		(*T)(S, u, v, 1) // This is the hottest loop in the entire program
		if (S.timeit) timer_off(71)
		alpha = safe_divide( ssr , weighted_quadcolsum(S, u, v) )
		recent_ssr[1 + mod(iter-1, d), .] = alpha :* ssr
		improvement_potential = improvement_potential - alpha :* ssr
		y = y - alpha :* u
		if (S.compute_rre & !S.prune) reghdfe_rre_benchmark(y[., 1], S.rre_true_residual, S.rre_depvar_norm)
		r = r - alpha :* v
		ssr_old = ssr
		ssr = weighted_quadcolsum(S, r, r)
		beta = safe_divide( ssr , ssr_old) // Fletcher-Reeves formula, but it shouldn't matter in our problem
		u = r + beta :* u
		// Convergence if sum(recent_ssr) > tol^2 * improvement_potential
		if ( check_convergence(S, iter, colsum(recent_ssr), improvement_potential, "hestenes") ) break
	}
	return(y)
}

// --------------------------------------------------------------------------

`Variables' function accelerate_sd(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter, g
	`Variables' proj
	`RowVector' t
	pragma unset proj

	for (iter=1; iter<=S.maxiter; iter++) {
		(*T)(S, y, proj, 1)
		if (check_convergence(S, iter, y-proj, y)) break
		t = safe_divide( weighted_quadcolsum(S, y, proj) , weighted_quadcolsum(S, proj, proj) )
		if (uniform(1,1)<0.1) t = 1 // BUGBUG: Does this REALLY help to randomly unstuck an iteration?

		y = y - t :* proj
		if (S.compute_rre & !S.prune) reghdfe_rre_benchmark(y[., 1], S.rre_true_residual, S.rre_depvar_norm)

		if (S.storing_alphas) {
			for (g=1; g<=S.G; g++) {
				//g, ., ., t
				//asarray(S.factors[g].extra, "alphas"), asarray(S.factors[g].extra, "tmp_alphas")
				if (S.save_fe[g]) {
					asarray(S.factors[g].extra, "alphas",
					    asarray(S.factors[g].extra, "alphas") +
					    t :* asarray(S.factors[g].extra, "tmp_alphas")
					)
				}
			}
		}
	}
	return(y-proj)
}

// --------------------------------------------------------------------------
// This is method 3 of Macleod (1986), a vector generalization of the Aitken-Steffensen method
// Also: "when numerically computing the sequence.. stop..  when rounding errors become too
// important in the denominator, where the ^2 operation may cancel too many significant digits"
// Note: Sometimes the iteration gets "stuck"; can we unstuck it with adding randomness
// in the accelerate decision? There should be a better way.. (maybe symmetric kacz instead of standard one?)

`Variables' function accelerate_aitken(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter
	`Variables'		resid, y_old, delta_sq
	`Boolean'	accelerate
	`RowVector' t
	pragma unset resid

	y_old = J(rows(y), cols(y), .)

	for (iter=1; iter<=S.maxiter; iter++) {

		(*T)(S, y, resid)
		accelerate = iter>=S.accel_start & !mod(iter,S.accel_freq)

		// Accelerate
		if (accelerate) {
			delta_sq = resid - 2 * y + y_old // = (resid - y) - (y - y_old) // Equivalent to D2.resid
			// t is just (d'd2) / (d2'd2)
			t = safe_divide( weighted_quadcolsum(S,  (resid - y) , delta_sq) ,  weighted_quadcolsum(S, delta_sq , delta_sq) )
			resid = resid - t :*  (resid - y)
		}

		// Only check converge on non-accelerated iterations
		// BUGBUG: Do we need to disable the check when accelerating?
		// if (check_convergence(S, iter, accelerate? resid :* .  : resid, y)) break
		if (S.compute_rre & !S.prune) reghdfe_rre_benchmark(resid[., 1], S.rre_true_residual, S.rre_depvar_norm)
		if (check_convergence(S, iter, resid, y)) break
		y_old = y // y_old is resid[iter-2]
		y = resid // y is resid[iter-1]
	}
	return(resid)
}

// --------------------------------------------------------------------------

`Boolean' check_convergence(`FixedEffects' S, `Integer' iter, `Variables' y_new, `Variables' y_old,| `String' method) {
	`Boolean'	is_last_iter
	`Real'		update_error

	// max() ensures that the result when bunching vars is at least as good as when not bunching
	if (args()<5) method = "vectors"

	if (S.G==1 & !S.storing_alphas) {
		// Shortcut for trivial case (1 FE)
		update_error = 0
	}
	else if (method=="vectors") {
		update_error = max(mean(reldif(y_new, y_old), S.weight))
	}
	else if (method=="hestenes") {
		// If the regressor is perfectly explained by the absvars, we can have SSR very close to zero but negative
		// (so sqrt is missing)
		// todo: add weights to hestenes (S.weight , as in "vectors" method)
		update_error = max(safe_divide( sqrt(y_new) , editmissing(sqrt(y_old), sqrt(epsilon(1)) ) , sqrt(epsilon(1)) ))
	}
	else {
		exit(error(100))
	}

	assert_msg(!missing(update_error), "update error is missing")

	S.converged = (update_error <= S.tolerance)
	is_last_iter = iter==S.maxiter

	if (S.converged) {
		S.iteration_count = iter
		if (S.verbose==1) printf("{txt}    converged in %g iterations (last error =%3.1e)\n", iter, update_error)
		if (S.verbose>1) printf("\n{txt}    - Converged in %g iterations (last error =%3.1e)\n", iter, update_error)
	}
	else if (is_last_iter & S.abort) {
		printf("\n{err}convergence not achieved in %g iterations (last error=%e); try increasing maxiter() or decreasing tol().\n", S.maxiter, update_error)
		exit(430)
	}
	else {
		if ((S.verbose>=2 & S.verbose<=3 & mod(iter,1)==0) | (S.verbose==1 & mod(iter,1)==0)) {
			printf("{res}.{txt}")
			displayflush()
		}
		if ( (S.verbose>=2 & S.verbose<=3 & mod(iter,100)==0) | (S.verbose==1 & mod(iter,100)==0) ) {
			printf("{txt}%9.1f\n      ", update_error/S.tolerance)
		}

		if (S.verbose==4 & method!="hestenes") printf("{txt} iter={res}%4.0f{txt}\tupdate_error={res}%-9.6e\n", iter, update_error)
		if (S.verbose==4 & method=="hestenes") printf("{txt} iter={res}%4.0f{txt}\tupdate_error={res}%-9.6e  {txt}norm(ssr)={res}%g\n", iter, update_error, norm(y_new))

		if (S.verbose==5) {
			printf("\n{txt} iter={res}%4.0f{txt}\tupdate_error={res}%-9.6e{txt}\tmethod={res}%s\n", iter, update_error, method)
			"old:"
			y_old
			"new:"
			y_new
		}
	}
	return(S.converged)
}

// --------------------------------------------------------------------------

`Matrix' weighted_quadcolsum(`FixedEffects' S, `Matrix' x, `Matrix' y) {
	// BUGBUG: colsum or quadcolsum??
	// BUGBUG: override S.has_weights with pruning
	// BUGBUG: speed up with cross() if only one column
	return( quadcolsum(S.has_weights ? (x :* y :* S.weight) : (x :* y) ) )
}

// RRE benchmarking
//	|| yk - y || / || y || === || ek - e || / || y ||
`Real' reghdfe_rre_benchmark(`Vector' resid, `Vector' true_resid, `Real' norm_y) {
	`Real' ans
	ans = norm(resid - true_resid) / norm_y
	return(ans)
}

end

mata:

// --------------------------------------------------------------------------
// Acceleration Schemes
// --------------------------------------------------------------------------

`Variables' function accelerate_test(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter, g
	`Variables'		resid
	`Factor' f
	pragma unset resid

	for (iter=1; iter<=S.maxiter; iter++) {
		for (g=1; g<=S.G; g++) {
			f = S.factors[g]
			if (g==1) resid = y - panelmean(f.sort(y), f)[f.levels, .]
			else resid = resid - panelmean(f.sort(resid), f)[f.levels, .]
		}
		if (check_convergence(S, iter, resid, y)) break
		y = resid
	}
	return(resid)
}

// --------------------------------------------------------------------------

`Variables' function accelerate_none(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter
	`Variables'		resid
	pragma unset resid

	for (iter=1; iter<=S.maxiter; iter++) {
		(*T)(S, y, resid) // Faster version of "resid = S.T(y)"
		if (check_convergence(S, iter, resid, y)) break
		y = resid
	}
	return(resid)
}
// --------------------------------------------------------------------------

// Start w/out acceleration, then switch to CG
`Variables' function accelerate_hybrid(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer' iter, accel_start
	`Variables' resid
	pragma unset resid

	accel_start = 6

	for (iter=1; iter<=accel_start; iter++) {
		(*T)(S, y, resid) // Faster version of "resid = S.T(y)"
		if (check_convergence(S, iter, resid, y)) break
		y = resid
	}

	T = &transform_sym_kaczmarz() // Override

	return(accelerate_cg(S, y, T))
}

// --------------------------------------------------------------------------
// Memory cost is approx = 4*size(y) (actually 3 since y is already there)
// But we need to add maybe 1 more due to u:*v
// And I also need to check how much does project and T use..
// Double check with a call to memory

// For discussion on the stopping criteria, see the following presentation:
// Arioli & Gratton, "Least-squares problems, normal equations, and stopping criteria for the conjugate gradient method". URL: https://www.stfc.ac.uk/SCD/resources/talks/Arioli-NAday2008.pdf

// Basically, we will use the Hestenes and Stiefel rule

`Variables' function accelerate_cg(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	// BUGBUG iterate the first 6? without acceleration??
	`Integer'	iter, d, Q
	`Variables'		r, u, v
	`RowVector' alpha, beta, ssr, ssr_old, improvement_potential
	`Matrix' recent_ssr
	pragma unset r
	pragma unset v

	Q = cols(y)

	d = 1 // BUGBUG Set it to 2/3 // Number of recent SSR values to use for convergence criteria (lower=faster & riskier)
	// A discussion on the stopping criteria used is described in
	// http://scicomp.stackexchange.com/questions/582/stopping-criteria-for-iterative-linear-solvers-applied-to-nearly-singular-system/585#585

	improvement_potential = weighted_quadcolsum(S, y, y)
	recent_ssr = J(d, Q, .)

	(*T)(S, y, r, 1)
	ssr = weighted_quadcolsum(S, r, r) // cross(r,r) when cols(y)==1 // BUGBUG maybe diag(quadcross()) is faster?
	u = r

	for (iter=1; iter<=S.maxiter; iter++) {
		if (S.timeit) timer_on(71)
		(*T)(S, u, v, 1) // This is the hottest loop in the entire program
		if (S.timeit) timer_off(71)
		alpha = safe_divide( ssr , weighted_quadcolsum(S, u, v) )
		recent_ssr[1 + mod(iter-1, d), .] = alpha :* ssr
		improvement_potential = improvement_potential - alpha :* ssr
		y = y - alpha :* u
		if (S.compute_rre & !S.prune) reghdfe_rre_benchmark(y[., 1], S.rre_true_residual, S.rre_depvar_norm)
		r = r - alpha :* v
		ssr_old = ssr
		ssr = weighted_quadcolsum(S, r, r)
		beta = safe_divide( ssr , ssr_old) // Fletcher-Reeves formula, but it shouldn't matter in our problem
		u = r + beta :* u
		// Convergence if sum(recent_ssr) > tol^2 * improvement_potential
		if ( check_convergence(S, iter, colsum(recent_ssr), improvement_potential, "hestenes") ) break
	}
	return(y)
}

// --------------------------------------------------------------------------

`Variables' function accelerate_sd(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter, g
	`Variables' proj
	`RowVector' t
	pragma unset proj

	for (iter=1; iter<=S.maxiter; iter++) {
		(*T)(S, y, proj, 1)
		if (check_convergence(S, iter, y-proj, y)) break
		t = safe_divide( weighted_quadcolsum(S, y, proj) , weighted_quadcolsum(S, proj, proj) )
		if (uniform(1,1)<0.1) t = 1 // BUGBUG: Does this REALLY help to randomly unstuck an iteration?

		y = y - t :* proj
		if (S.compute_rre & !S.prune) reghdfe_rre_benchmark(y[., 1], S.rre_true_residual, S.rre_depvar_norm)

		if (S.storing_alphas) {
			for (g=1; g<=S.G; g++) {
				//g, ., ., t
				//asarray(S.factors[g].extra, "alphas"), asarray(S.factors[g].extra, "tmp_alphas")
				if (S.save_fe[g]) {
					asarray(S.factors[g].extra, "alphas",
					    asarray(S.factors[g].extra, "alphas") +
					    t :* asarray(S.factors[g].extra, "tmp_alphas")
					)
				}
			}
		}
	}
	return(y-proj)
}

// --------------------------------------------------------------------------
// This is method 3 of Macleod (1986), a vector generalization of the Aitken-Steffensen method
// Also: "when numerically computing the sequence.. stop..  when rounding errors become too
// important in the denominator, where the ^2 operation may cancel too many significant digits"
// Note: Sometimes the iteration gets "stuck"; can we unstuck it with adding randomness
// in the accelerate decision? There should be a better way.. (maybe symmetric kacz instead of standard one?)

`Variables' function accelerate_aitken(`FixedEffects' S, `Variables' y, `FunctionP' T) {
	`Integer'	iter
	`Variables'		resid, y_old, delta_sq
	`Boolean'	accelerate
	`RowVector' t
	pragma unset resid

	y_old = J(rows(y), cols(y), .)

	for (iter=1; iter<=S.maxiter; iter++) {

		(*T)(S, y, resid)
		accelerate = iter>=S.accel_start & !mod(iter,S.accel_freq)

		// Accelerate
		if (accelerate) {
			delta_sq = resid - 2 * y + y_old // = (resid - y) - (y - y_old) // Equivalent to D2.resid
			// t is just (d'd2) / (d2'd2)
			t = safe_divide( weighted_quadcolsum(S,  (resid - y) , delta_sq) ,  weighted_quadcolsum(S, delta_sq , delta_sq) )
			resid = resid - t :*  (resid - y)
		}

		// Only check converge on non-accelerated iterations
		// BUGBUG: Do we need to disable the check when accelerating?
		// if (check_convergence(S, iter, accelerate? resid :* .  : resid, y)) break
		if (S.compute_rre & !S.prune) reghdfe_rre_benchmark(resid[., 1], S.rre_true_residual, S.rre_depvar_norm)
		if (check_convergence(S, iter, resid, y)) break
		y_old = y // y_old is resid[iter-2]
		y = resid // y is resid[iter-1]
	}
	return(resid)
}

// --------------------------------------------------------------------------

`Boolean' check_convergence(`FixedEffects' S, `Integer' iter, `Variables' y_new, `Variables' y_old,| `String' method) {
	`Boolean'	is_last_iter
	`Real'		update_error

	// max() ensures that the result when bunching vars is at least as good as when not bunching
	if (args()<5) method = "vectors"

	if (S.G==1 & !S.storing_alphas) {
		// Shortcut for trivial case (1 FE)
		update_error = 0
	}
	else if (method=="vectors") {
		update_error = max(mean(reldif(y_new, y_old), S.weight))
	}
	else if (method=="hestenes") {
		// If the regressor is perfectly explained by the absvars, we can have SSR very close to zero but negative
		// (so sqrt is missing)
		// todo: add weights to hestenes (S.weight , as in "vectors" method)
		update_error = max(safe_divide( sqrt(y_new) , editmissing(sqrt(y_old), sqrt(epsilon(1)) ) , sqrt(epsilon(1)) ))
	}
	else {
		exit(error(100))
	}

	assert_msg(!missing(update_error), "update error is missing")

	S.converged = (update_error <= S.tolerance)
	is_last_iter = iter==S.maxiter

	if (S.converged) {
		S.iteration_count = iter
		if (S.verbose==1) printf("{txt}    converged in %g iterations (last error =%3.1e)\n", iter, update_error)
		if (S.verbose>1) printf("\n{txt}    - Converged in %g iterations (last error =%3.1e)\n", iter, update_error)
	}
	else if (is_last_iter & S.abort) {
		printf("\n{err}convergence not achieved in %g iterations (last error=%e); try increasing maxiter() or decreasing tol().\n", S.maxiter, update_error)
		exit(430)
	}
	else {
		if ((S.verbose>=2 & S.verbose<=3 & mod(iter,1)==0) | (S.verbose==1 & mod(iter,1)==0)) {
			printf("{res}.{txt}")
			displayflush()
		}
		if ( (S.verbose>=2 & S.verbose<=3 & mod(iter,100)==0) | (S.verbose==1 & mod(iter,100)==0) ) {
			printf("{txt}%9.1f\n      ", update_error/S.tolerance)
		}

		if (S.verbose==4 & method!="hestenes") printf("{txt} iter={res}%4.0f{txt}\tupdate_error={res}%-9.6e\n", iter, update_error)
		if (S.verbose==4 & method=="hestenes") printf("{txt} iter={res}%4.0f{txt}\tupdate_error={res}%-9.6e  {txt}norm(ssr)={res}%g\n", iter, update_error, norm(y_new))

		if (S.verbose==5) {
			printf("\n{txt} iter={res}%4.0f{txt}\tupdate_error={res}%-9.6e{txt}\tmethod={res}%s\n", iter, update_error, method)
			"old:"
			y_old
			"new:"
			y_new
		}
	}
	return(S.converged)
}

// --------------------------------------------------------------------------

`Matrix' weighted_quadcolsum(`FixedEffects' S, `Matrix' x, `Matrix' y) {
	// BUGBUG: colsum or quadcolsum??
	// BUGBUG: override S.has_weights with pruning
	// BUGBUG: speed up with cross() if only one column
	return( quadcolsum(S.has_weights ? (x :* y :* S.weight) : (x :* y) ) )
}

// RRE benchmarking
//	|| yk - y || / || y || === || ek - e || / || y ||
`Real' reghdfe_rre_benchmark(`Vector' resid, `Vector' true_resid, `Real' norm_y) {
	`Real' ans
	ans = norm(resid - true_resid) / norm_y
	return(ans)
}

end


Fix path quoting in the clean target

Fix path quoting in the clean target

# define all project wide variables

export ROOTDIR=$(pwd)
export SRCDIR=$ROOTDIR/src
export OUTDIR=$ROOTDIR/out
export VERSION="pre-0.01"

DESTDIR=${DESTDIR-/usr/local/bin}

if [ "$1" = "all" ]; then
	redo-ifchange "$OUTDIR/redo"
elif [ "$1" = "clean" ]; then
	rm -rf "$OUTDIR"/*.tmp "$OUTDIR"/*.o "$OUTDIR"/redo "$OUTDIR"/CC
	# autoconf stuff
	rm -rf autom4te.cache config.h.in configure config.status config.log config.h
elif [ "$1" = "install" ]; then
	redo-ifchange all
	mkdir -p "$DESTDIR"
	install "$OUTDIR/redo" "$DESTDIR"
	ln -sf "$DESTDIR/redo" "$DESTDIR/redo-ifchange"
	ln -sf "$DESTDIR/redo" "$DESTDIR/redo-ifcreate"
	ln -sf "$DESTDIR/redo" "$DESTDIR/redo-always"
	echo "Finished installing."
fi

# define all project wide variables

export ROOTDIR=$(pwd)
export SRCDIR=$ROOTDIR/src
export OUTDIR=$ROOTDIR/out
export VERSION="pre-0.01"

DESTDIR=${DESTDIR-/usr/local/bin}

if [ "$1" = "all" ]; then
	redo-ifchange "$OUTDIR/redo"
elif [ "$1" = "clean" ]; then
	rm -rf "$OUTDIR/*.tmp" "$OUTDIR/*.o" "$OUTDIR/redo" "$OUTDIR/CC"
	# autoconf stuff
	rm -rf autom4te.cache config.h.in configure config.status config.log config.h
elif [ "$1" = "install" ]; then
	redo-ifchange all
	mkdir -p "$DESTDIR"
	install "$OUTDIR/redo" "$DESTDIR"
	ln -sf "$DESTDIR/redo" "$DESTDIR/redo-ifchange"
	ln -sf "$DESTDIR/redo" "$DESTDIR/redo-ifcreate"
	ln -sf "$DESTDIR/redo" "$DESTDIR/redo-always"
	echo "Finished installing."
fi


ieboilstart - allow version 14.2

ieboilstart - allow version 14.2

*! version 5.1 31MAY2017  Kristoffer Bjarkefur kbjarkefur@worldbank.org

	capture program drop ieboilstart
	program ieboilstart , rclass

	qui {

		syntax ,  Versionnumber(string) [noclear maxvar(numlist) matsize(numlist) Quietly veryquietly Custom(string) setmem(string) ]

		version 11.0

		/*********************************

			Check user specifed version
			and apply it is valid

		*********************************/

		local stata_versions "11 11.0 11.1 11.2 12 12.0 12.1 13 13.0 13.1 14 14.0 14.1 14.2 15 15.0"

		if `:list versionnumber in stata_versions' == 0 {

			di as error "{phang}Only recent major releases are allowed. The releases currently allowed are:{break}`stata_versions'{p_end}"
			di ""
			error 198
			exit
		}

		*Return the value
		return local version "version `versionnumber'"

		*Set the version specfied in the command
		version `versionnumber'

		/*********************************

			Check settings related to older
			versions and Stata IC

		*********************************/

		*Test that maxvar is not set in combination when using Stata IC
		if 	!(c(MP) == 1 | c(SE) == 1) & "`maxvar'" != "" {

			di as error "{phang}In Stata IC the maximum number of variables allowed is fixed at 2,047 and maxvar() is therefore not allowed.{p_end}"
			error 198
		}

		if 	"`setmem'" != ""	{

			if `versionnumber' >= 12  {

				di as error "{phang}Option setmem() is only allowed when setting the version number to 11. Setmem() is only applicable in Stata 11 or eralier, but those versions wont be able to run this file as the version number is set to 12 or higher.{p_end}"
				error 198
			}

			*Split the memory setting string into value and byte unit
			local mem_strlen 	= strlen("`setmem'") -1
			local mem_number	= substr("`setmem'",  1 , `mem_strlen')
			local mem_bytetype	= substr("`setmem'", -1 , 1)

			*Test that the number part is a number and a integer
			cap confirm integer number `mem_number'
			if _rc {

				di as error "{phang}The value in setmem(`setmem') must be a number (followed by either b, k, m or g). See {help memory} for details.{p_end}"
				error _rc
			}

			*Test that the byte type is not anything else than b, k, m or g
			if !`=inlist(lower("`mem_bytetype'"), "b", "k", "m", "g")' {

				di as error "{phang}The last character in setmem(`setmem') must be either b, k, m or g. See {help memory} for details.{p_end}""
				error 7

			}
		}
		else {

			*Default value is 50M. This is probably too little, but
			*it will safely work. And users can manually increase this
			local setmem "50M"

		}

		/*********************************

			Check input for maxvar and matsize
			if specified, other wise set
			maximum value allowed.

		*********************************/

		*Setting maxvar requires a cleared memory. Therefore
		*maxvar() and noclear cannot be used at the same time.
		if "`maxvar'" != "" & "`clear'" != "" {

			di as error "{phang}It is not possible to set allowed maximum numbers of variables without clearing the data. noclear and maxvar() can therefore not be specified at the same time{p_end}"
			di ""
			error 198
			exit
		}

		foreach maxlocal in maxvar matsize {

			*Set locals with the max and min values fox maxvar and matsize
			if "`maxlocal'" == "maxvar" {
				local max 32767
				local min 2048
			}
			if "`maxlocal'" == "matsize" {
				local max 11000
				local min 10
			}

			*Test if user set a value for this value
			if "``maxlocal''" != "" {

				*If user specified a value, test that it is between the min and the max
				if !(``maxlocal'' >= `min' & ``maxlocal'' <= `max') {

					di as error "{phang}`maxlocal' must be between `min' and `max' (inclusive) if you are using Stata SE and Stata MP. You entered ``maxlocal''.{p_end}"
					di ""
					*Throw appropriate error if below or above error
					if ``maxlocal'' < `min' error 910
					if ``maxlocal'' > `max' error 912
					exit
				}
			}

			else {
				*User did not specify value, use ieboilstart's defaults:

				if "`maxlocal'" == "maxvar" {
					*Set maxvar to max value allowed as this is often an issue when working with large survey data
					local `maxlocal' `max'
				}
				if "`maxlocal'" == "matsize" {
					*Set to the default as the maximum is rarely requered.
					local `maxlocal' 400
				}
			}
		}

		/*********************************

			Set the settings

		*********************************/

		local setDispLocal "{col 5}{ul:Settings set by this command:}"

		*Set basic memory limits
		if "`clear'" == "" {

			*Setting
			clear all
			local setDispLocal "`setDispLocal'{break}{col 5}clear all"

			**Setting maxvar not allowed in Stata IC.
			if 	(c(MP) == 1 | c(SE) == 1) {

				*Setting
				set maxvar `maxvar'
				local setDispLocal "`setDispLocal'{break}{col 5}set maxvar {col 22}`maxvar'"
			}
		}

		*Setting
		set matsize `matsize'
		local setDispLocal "`setDispLocal'{break}{col 5}set matsize {col 22}`matsize'"

		****************
		*Memory	settings

		*For compatibility with Stata 11 the do file includes and if/else
		*condition testing version number. The memoroy settings introduced
		*in Stata 12 will be applied to Stata version more recent than
		*Stata 11, and set memory will be applied to Stata 11.
		if c(stata_version) >= 12 {

			*Setting
			set niceness 5
			local setDispLocal "`setDispLocal'{break}{col 5}set niceness{col 22}5"

			*These settings cannot be modified with data in memory
			if "`clear'" == "" {

				*Settings
				set min_memory 0
				set max_memory .
				local setDispLocal "`setDispLocal'{break}{col 5}set min_memory {col 22}0{break}{col 5}set max_memory {col 22}."

				*Set segment size to the largest value allowed by the operative system
				if c(bit) == 64 {
					*Setting
					set segmentsize	32m
					local setDispLocal "`setDispLocal'{break}{col 5}set segmentsize	{col 22}32m"
				}
				else {
					*Setting
					set segmentsize	16m
					local setDispLocal "`setDispLocal'{break}{col 5}set segmentsize	{col 22}16m"
				}
			}
		}
		else {

			*If this dofile is generated in Stata 11 then only the old
			*way of setting memory is included. This will be ignored by
			*more recent versios of Stata

			*Setting
			set memory `setmem'
			local setDispLocal "`setDispLocal'{break}{col 5}set memory {col 22}`setmem'"

		}

		*********************
		*Set default settings
		set more 		off , perm
		pause 			on
		set varabbrev 	off
		local setDispLocal "`setDispLocal'{break}{col 5}set more {col 22}off {col 28}, perm"
		local setDispLocal "`setDispLocal'{break}{col 5}pause {col 22}on"
		local setDispLocal "`setDispLocal'{break}{col 5}set varabbrev {col 22}off"

		/*********************************

			Add custom lines of code

		*********************************/

		if `"`custom'"' != "" {

			local setDispLocal `"`setDispLocal'{break} {break}{col 5}{ul:User specified settings:}"'

			*Create a local with the rowlabel input to be tokenized
			local custom_code_lines `custom'

			while `"`custom_code_lines'"' != "" {

				*Parsing name and label pair
				gettoken code_line custom_code_lines : custom_code_lines, parse("@")

				*Removing leadning or trailing spaces
				local code_line = trim(`"`code_line'"')

				*Set custom setting
				local setDispLocal `"`setDispLocal'{break}{col 5}`code_line'"'
				`code_line'

				*Parse char is not removed by gettoken
				local custom_code_lines = subinstr(`"`custom_code_lines'"' ,"@","",1)
			}
		}

		/*********************************

			Create return value and output message

		*********************************/

		if "`quietly'" == "" & "`veryquietly'" == "" {

			noi di ""
			noi di "{phang}{err:DISCLAIMER:} Due to how settings works in Stata, this command can only attempt to harmonize settings as much as possible across users, but no guarantee can be given that all commands will always behave identical unless the exact same version and type of Stata is used.{p_end}"
			noi di ""
			noi di `"`setDispLocal'"'
		}

		if  "`veryquietly'" == "" {

			noi di ""
			noi di "{phang}{err:IMPORTANT:} The most important setting of this command cannot be set inside the command due to technical reasons. The setting has been prepared by this command, and you only need to write \`r(version)' after this command (include the apostrophes).{p_end}"

		}

	}
	end

*! version 5.1 31MAY2017  Kristoffer Bjarkefur kbjarkefur@worldbank.org

	capture program drop ieboilstart
	program ieboilstart , rclass

	qui {

		syntax ,  Versionnumber(string) [noclear maxvar(numlist) matsize(numlist) Quietly veryquietly Custom(string) setmem(string) ]

		version 11.0

		/*********************************

			Check user specifed version
			and apply it is valid

		*********************************/

		local stata_versions "11 11.0 11.1 11.2 12 12.0 12.1 13 13.0 13.1 14 14.0 14.1 15 15.0"

		if `:list versionnumber in stata_versions' == 0 {

			di as error "{phang}Only recent major releases are allowed. The releases currently allowed are:{break}`stata_versions'{p_end}"
			di ""
			error 198
			exit
		}

		*Return the value
		return local version "version `versionnumber'"

		*Set the version specfied in the command
		version `versionnumber'

		/*********************************

			Check settings related to older
			versions and Stata IC

		*********************************/

		*Test that maxvar is not set in combination when using Stata IC
		if 	!(c(MP) == 1 | c(SE) == 1) & "`maxvar'" != "" {

			di as error "{phang}In Stata IC the maximum number of variables allowed is fixed at 2,047 and maxvar() is therefore not allowed.{p_end}"
			error 198
		}

		if 	"`setmem'" != ""	{

			if `versionnumber' >= 12  {

				di as error "{phang}Option setmem() is only allowed when setting the version number to 11. Setmem() is only applicable in Stata 11 or eralier, but those versions wont be able to run this file as the version number is set to 12 or higher.{p_end}"
				error 198
			}

			*Split the memory setting string into value and byte unit
			local mem_strlen 	= strlen("`setmem'") -1
			local mem_number	= substr("`setmem'",  1 , `mem_strlen')
			local mem_bytetype	= substr("`setmem'", -1 , 1)

			*Test that the number part is a number and a integer
			cap confirm integer number `mem_number'
			if _rc {

				di as error "{phang}The value in setmem(`setmem') must be a number (followed by either b, k, m or g). See {help memory} for details.{p_end}"
				error _rc
			}

			*Test that the byte type is not anything else than b, k, m or g
			if !`=inlist(lower("`mem_bytetype'"), "b", "k", "m", "g")' {

				di as error "{phang}The last character in setmem(`setmem') must be either b, k, m or g. See {help memory} for details.{p_end}""
				error 7

			}
		}
		else {

			*Default value is 50M. This is probably too little, but
			*it will safely work. And users can manually increase this
			local setmem "50M"

		}

		/*********************************

			Check input for maxvar and matsize
			if specified, other wise set
			maximum value allowed.

		*********************************/

		*Setting maxvar requires a cleared memory. Therefore
		*maxvar() and noclear cannot be used at the same time.
		if "`maxvar'" != "" & "`clear'" != "" {

			di as error "{phang}It is not possible to set allowed maximum numbers of variables without clearing the data. noclear and maxvar() can therefore not be specified at the same time{p_end}"
			di ""
			error 198
			exit
		}

		foreach maxlocal in maxvar matsize {

			*Set locals with the max and min values fox maxvar and matsize
			if "`maxlocal'" == "maxvar" {
				local max 32767
				local min 2048
			}
			if "`maxlocal'" == "matsize" {
				local max 11000
				local min 10
			}

			*Test if user set a value for this value
			if "``maxlocal''" != "" {

				*If user specified a value, test that it is between the min and the max
				if !(``maxlocal'' >= `min' & ``maxlocal'' <= `max') {

					di as error "{phang}`maxlocal' must be between `min' and `max' (inclusive) if you are using Stata SE and Stata MP. You entered ``maxlocal''.{p_end}"
					di ""
					*Throw appropriate error if below or above error
					if ``maxlocal'' < `min' error 910
					if ``maxlocal'' > `max' error 912
					exit
				}
			}

			else {
				*User did not specify value, use ieboilstart's defaults:

				if "`maxlocal'" == "maxvar" {
					*Set maxvar to max value allowed as this is often an issue when working with large survey data
					local `maxlocal' `max'
				}
				if "`maxlocal'" == "matsize" {
					*Set to the default as the maximum is rarely requered.
					local `maxlocal' 400
				}
			}
		}

		/*********************************

			Set the settings

		*********************************/

		local setDispLocal "{col 5}{ul:Settings set by this command:}"

		*Set basic memory limits
		if "`clear'" == "" {

			*Setting
			clear all
			local setDispLocal "`setDispLocal'{break}{col 5}clear all"

			**Setting maxvar not allowed in Stata IC.
			if 	(c(MP) == 1 | c(SE) == 1) {

				*Setting
				set maxvar `maxvar'
				local setDispLocal "`setDispLocal'{break}{col 5}set maxvar {col 22}`maxvar'"
			}
		}

		*Setting
		set matsize `matsize'
		local setDispLocal "`setDispLocal'{break}{col 5}set matsize {col 22}`matsize'"

		****************
		*Memory	settings

		*For compatibility with Stata 11 the do file includes and if/else
		*condition testing version number. The memoroy settings introduced
		*in Stata 12 will be applied to Stata version more recent than
		*Stata 11, and set memory will be applied to Stata 11.
		if c(stata_version) >= 12 {

			*Setting
			set niceness 5
			local setDispLocal "`setDispLocal'{break}{col 5}set niceness{col 22}5"

			*These settings cannot be modified with data in memory
			if "`clear'" == "" {

				*Settings
				set min_memory 0
				set max_memory .
				local setDispLocal "`setDispLocal'{break}{col 5}set min_memory {col 22}0{break}{col 5}set max_memory {col 22}."

				*Set segment size to the largest value allowed by the operative system
				if c(bit) == 64 {
					*Setting
					set segmentsize	32m
					local setDispLocal "`setDispLocal'{break}{col 5}set segmentsize	{col 22}32m"
				}
				else {
					*Setting
					set segmentsize	16m
					local setDispLocal "`setDispLocal'{break}{col 5}set segmentsize	{col 22}16m"
				}
			}
		}
		else {

			*If this dofile is generated in Stata 11 then only the old
			*way of setting memory is included. This will be ignored by
			*more recent versios of Stata

			*Setting
			set memory `setmem'
			local setDispLocal "`setDispLocal'{break}{col 5}set memory {col 22}`setmem'"

		}

		*********************
		*Set default settings
		set more 		off , perm
		pause 			on
		set varabbrev 	off
		local setDispLocal "`setDispLocal'{break}{col 5}set more {col 22}off {col 28}, perm"
		local setDispLocal "`setDispLocal'{break}{col 5}pause {col 22}on"
		local setDispLocal "`setDispLocal'{break}{col 5}set varabbrev {col 22}off"

		/*********************************

			Add custom lines of code

		*********************************/

		if `"`custom'"' != "" {

			local setDispLocal `"`setDispLocal'{break} {break}{col 5}{ul:User specified settings:}"'

			*Create a local with the rowlabel input to be tokenized
			local custom_code_lines `custom'

			while `"`custom_code_lines'"' != "" {

				*Parsing name and label pair
				gettoken code_line custom_code_lines : custom_code_lines, parse("@")

				*Removing leadning or trailing spaces
				local code_line = trim(`"`code_line'"')

				*Set custom setting
				local setDispLocal `"`setDispLocal'{break}{col 5}`code_line'"'
				`code_line'

				*Parse char is not removed by gettoken
				local custom_code_lines = subinstr(`"`custom_code_lines'"' ,"@","",1)
			}
		}

		/*********************************

			Create return value and output message

		*********************************/

		if "`quietly'" == "" & "`veryquietly'" == "" {

			noi di ""
			noi di "{phang}{err:DISCLAIMER:} Due to how settings works in Stata, this command can only attempt to harmonize settings as much as possible across users, but no guarantee can be given that all commands will always behave identical unless the exact same version and type of Stata is used.{p_end}"
			noi di ""
			noi di `"`setDispLocal'"'
		}

		if  "`veryquietly'" == "" {

			noi di ""
			noi di "{phang}{err:IMPORTANT:} The most important setting of this command cannot be set inside the command due to technical reasons. The setting has been prepared by this command, and you only need to write \`r(version)' after this command (include the apostrophes).{p_end}"

		}

	}
	end


[iegitaddmd] re-write code to be less messy

Only change relevant to user is that option file() is now called customfile()

*! version 6.2 31JAN2019 DIME Analytics dimeanalytics@worldbank.org

cap program drop   iegitaddmd
	program define iegitaddmd

qui {

		syntax , folder(string) [customfile(string) all skip replace]

		/******************************
		*******************************
			Test user input
		*******************************
		******************************/

		******************************
		*	Test input: folder

		*Test that folder exist
		mata : st_numscalar("r(dirExist)", direxists("`folder'"))

		if (`r(dirExist)' == 0) | (length("`folder'")<=10) {

			noi di as error `"{phang}The "`folder'" folder does not exist or you have not entered the full path. For example, full paths on most Windows computers it starts with {it:C:/} and on most Mac computers with {it:/user/}. Important: Specify the whole file path to the repository folder, not just {it:C:/} or {it:/user/} as that would create the placeholder file in every empty folder on your computer!{p_end}"'
			error 693
			exit
		}

		* File paths can have both forward and/or back slash. We'll standardize them so they're easier to handle
		local folderStd	= subinstr("`folder'","\","/",.)

		******************************
		*	Test input: customfile

		*Test that file exist if not using default file
		if "`customfile'" != "" {
			*File option used test if file exists
			capture confirm file "`customfile'"
			if _rc {
				*File does not exist, throw error
				noi di as error `"{phang}File "`customfile'" was not found. Remember that you must enter the full path. For example, on most Windows computers it starts with {it:C:/} and on most Mac computers with {it:/user/}.{p_end}"'
				error 693
				exit
			}

			* Get the name of file (include path to keep )
			* File paths can have both forward and/or back slash. We'll standardize them so they're easier to handle
			local customFileStd	= subinstr("`customfile'","\","/",.)
			*Find the last slash in the file name
			local lastSlash = strpos(strreverse(`"`customFileStd'"'),"/")
			*Use that position to get the file name. Multiply with minus one as we count from the back
			local customFileName = substr(`"`customFileStd'"', (-1 * `lastSlash') ,.)
			*Used for the recursive call of the command when there is a subfolder
			local customFileRecurse `"customfile(`customFileStd')"'

			*Path and name of thile regardless if customfile were used
			local newPlaceholderFile `"`folderStd'`customFileName'"'
		}
		else {
			*Path and name of thile regardless if customfile were not used
			local newPlaceholderFile `"`folderStd'/README.md"'
		}

		******************************
		*	Test input: skip & replace

		*Options skip and replace cannot be used together
		if "`skip'" != "" & "`replace'" != "" {
			noi di as error `"{phang}The options skip and replace may not be used together.{p_end}"'
			error 198
			exit
		}

		/******************************
		*******************************
			Execute command
		*******************************
		******************************/

		******************************
		*	List all files and folders

		*List files, directories and other files
		local flist : dir `"`folderStd'"' files "*"	, respectcase
		local dlist : dir `"`folderStd'"' dirs  "*" , respectcase
		local olist : dir `"`folderStd'"' other "*"	, respectcase

		******************************
		*	Call command recursively on all sub-folders

		*Use the command on each subfolder to this folder (if any)
		foreach dir of local dlist {
			*Recursive call on each subfolder
			noi iegitaddmd , folder(`"`folderStd'/`dir'"') `all' `customFileRecurse' `skip' `replace'
		}

		******************************
		*	Evaluate if file will be crated in this folder

		*Test if all of those lists are empty, meaning there are
		* no folders or files in this folder
		if `"`flist'`dlist'`olist'"' == "" {

			*If custom file was specified, you copy that one to empt folder
			if "`customFileStd'" != "" copy "`customFileStd'" `"`newPlaceholderFile'"', replace
			*No custom file so write default README.md file
			else writeDefaultPlaceholder `"`newPlaceholderFile'"'
		}

		*If the folder is not empty, test if option all were used.
		else if ("`all'" == "all") {

				* No file with this name exists in this folder, create file without further ado
				if _rc != 0 {
					*If custom file was specified, you copy that one to empt folder
					if "`customFileStd'" != "" copy "`customFileStd'" `"`newPlaceholderFile'"', replace
					*No custom file so write default README.md file
					else writeDefaultPlaceholder `"`newPlaceholderFile'"'
				}

				* File exist, but no sintructions on how to deal with this has been given, throw error
				else if missing("`skip'") & missing("`replace'")  {
					noi di as error `"{phang}The file  `newPlaceholderFile' already exists. Either remove option {it:all} or use either option {it:skip} or {it:replace}. See help file before using either of these options unless you are already familiar with them.{p_end}"'
					error 602
				}

				* File exist, and instruction is to replace, create files and overwrite as needed
				else if !missing("`replace'") {
					*If custom file was specified, you copy that one to empt folder
					if "`customFileStd'" != "" copy "`customFileStd'" `"`newPlaceholderFile'"', replace
					*No custom file so write default README.md file
					else writeDefaultPlaceholder `"`newPlaceholderFile'"'
				}

				* File exist, and instruction is to skip, do nothing
				else if !missing("`skip'") {
						*Nothing is done, this if-block is only included for readability and completion
				}

				* The code should nevere get to this point
				else  {
					noi di as error `"{phang}Coding error in if-chain in "All" block.Please report this error to dimeanalytics@worldbank.org or here: https://github.com/worldbank/ietoolkit/issues/new{p_end}"'
					error 602
				}

		}
}

end

	*Write a README.md file when iegitaddmd finds an empty folder
	cap program drop writeDefaultPlaceholder
	program define   writeDefaultPlaceholder

		args newplaceholderfile

		*Create file
		tempname 	newHandle
		cap file close 	`newHandle'
		file open  	`newHandle' using "`newplaceholderfile'", text write replace

		*Add some text to the file
		file write  `newHandle' ///
			"# Placeholder file" _n _n ///
			"This file has been created automatically by the command **iegitaddmd** from the Stata package [**ietoolkit**](https://worldbank.github.io/ietoolkit) to make GitHub sync this folder. GitHub does not sync empty folders or folders that only contain ignored files, but in research projects it is often important to share the full standardized folder structure along with the actual files. This command is intended to be used with **iefolder**, but it can be used in any folder structure intended to be shared on GitHub." _n _n ///
			"In recently started projects, it is typical to create data folders, script folders (do-files, r-files) and output folders, among others. The output folders are initially empty, but the script files might include a file path to them for later use. If an output folder is empty and another collaborator clones the repository, then the output folder expected by the scripts will not be included by GitHub in the cloned repository, and the script will not run properly." _n _n ///
			"## You should replace the content of this placeholder file" _n _n ///
			"The text in this file should be replaced with text that describes the intended use of this folder. This file is written in markdown (.md) format, which is suitable for GitHub syncing (unlike .doc/.docx). If the file is named *README.md*, GitHub automatically displays its contents when someone opens the containing folder on GitHub.com using a web browser." _n _n ///
			"If you are new to markup languages (markdown, html etc.) then this [Markdown Tutorial](https://www.markdowntutorial.com/) is a great place to start. If you have some experience with markup languages, then this [Markdown Cheat Sheet](https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf) is a great place to start." _n _n ///
			"## Add similar files in other folders" _n _n ///
			"The Stata command **iegitaddmd** does not add anything to folders that already have content unless this is explicitly requested using the option `all`. It is best practice to create a *README.md* file in any folders that have content, for example, in folders whose purpose might not obvious to someone using the repository for the first time. Again, if the file is named *README.md*, then the content of the file will be shown in the browser when someone explores the repository on GitHub.com." _n _n ///
			"Another great use of a *README.md* file is to use it as a documentation on how the folder it sits in and its subfolders are organized,  where its content can be found, and where new content is meant to be saved. For example, if you have a folder called `/Baseline/`, then you can give a short description of the activities conducted during the baseline and where data, scripts and outputs related to it can be found." _n _n ///
			"## Removing this file" _n _n ///
			"Removing this file is not recommended, as GitHub may stop syncing the parent folder unless the folder now has other content. However, even when content is added and the file can be removed without breaking the GitHub functionality, our recommendation is to replace the content with more relevant content rather than deleting it." _n _n

		*Closing the file
		file close 		`newHandle'
end

*! version 6.2 31JAN2019 DIME Analytics dimeanalytics@worldbank.org

cap program drop   iegitaddmd
	program define iegitaddmd

	qui {

		syntax , folder(string) [all file(string) skip replace]

		/******************************

			Test user input

		******************************/

		*Test that folder exist
		mata : st_numscalar("r(dirExist)", direxists("`folder'"))

		if (`r(dirExist)' == 0) | (length("`folder'")<=10) {

			noi di as error `"{phang}The "`folder'" folder does not exist or is too short. You must enter the full path. For example, on most Windows computers it starts with {it:C:/} and on most Mac computers with {it:/user/}. Important: Specify the whole file path to the repository folder, not just {it:C:/} or {it:/user/} as that would create the placeholder file in every empty folder on your computer!{p_end}"'
			error 693
			exit
		}

		* File paths can have both forward and/or back slash. We'll standardize them so they're easier to handle
		local folderStd	= subinstr("`folder'","\","/",.)

		*Test that file exist if not using default file
		if "`file'" != "" {
			*File option used test if file exists
			capture confirm file "`file'"
			if _rc {
				*File does not exist, throw error
				noi di as error `"{phang}File "`file'" was not found. Remember that you must enter the full path. For example, on most Windows computers it starts with {it:C:/} and on most Mac computers with {it:/user/}.{p_end}"'
				error 693
				exit
			}

			* Get the name of file (include path to keep )

			* File paths can have both forward and/or back slash. We'll standardize them so they're easier to handle
			local fileStd	= subinstr("`file'","\","/",.)

			*File path can have both forward and back slash
			local slash = strpos(strreverse("`fileStd'"),"/")

			*Use that position to get the file name. Multiply with minus one as we count from the back
			local userfilename = substr("`fileStd'", (-1 * `slash') ,.)

			*Used for the recursive call of the command when there is a subfolder
			local fileRecurse `"file(`fileStd')"'
		}

		*Options skip and replace cannot be used together
		if "`skip'" != "" & "`replace'" != "" {

			noi di as error `"{phang}The options skip and replace may not be used together.{p_end}"'
			error 198
			exit
		}

		/******************************

			Execute command

		******************************/

		*List files, directories and other files
		local flist : dir `"`folderStd'"' files "*"	, respectcase
		local dlist : dir `"`folderStd'"' dirs "*" 	, respectcase
		local olist : dir `"`folderStd'"' other "*"	, respectcase

		*Test if all of those lists are empty.
		if `"`flist'`dlist'`olist'"' == "" | ("`all'" == "all") {

			** If all those lists are empty then we are in an
			*  empty folder or [all] is specified and we should write the placeholder file

			if "`file'" == "" {

				*Write default README.md file
				writeGitKeep `"`folderStd'"' `skip' `replace'
			}
			else {

				*Use user provided file. First test if it already exists

				cap confirm file `"`folderStd'`userfilename'"'
				if _rc == 0 & "`skip'" == "" & "`replace'" == "" {

					*File exist and neither skip or replace is used
					local fileNameNoSlash = substr("`userfilename'",2,.)

					noi di as error `"{phang}A file with name `fileNameNoSlash' already exist in `folder'. Either remove option {it:all} or use either option {it:skip} or {it:replace}. See help file before using either of these options.{p_end}"'
					error 602
					exit
				}
				else if _rc == 0 & "`skip'" == "skip" {

					*File exists but option skip is used so do nothing, move to next folder

				}
				else if _rc == 0 & "`fileStd'" == `"`folderStd'`userfilename'"' {

					*If the custom template is inside the specified folder, we won't replace it

				}
				else{

					**Either file does not exist or replace is used, so write
					* placeholder file provided by user in folder location
					copy "`fileStd'" `"`folderStd'`userfilename'"', replace

				}
			}
		}

		*Use the command on each subfolder to this folder (if any)
		foreach dir of local dlist {

			*Recursive call on each subfolder
			iegitaddmd , folder(`"`folderStd'/`dir'"') `all' `fileRecurse' `skip' `replace'

		}
	}

end

*Write a README.md file when iegitaddmd finds an empty folder
cap program drop   writeGitKeep
	program define writeGitKeep

		args folder skipreplace

		* Test if README.md already exists, and if it does test if [skip] or [replace] is specified
		cap confirm file `"`folder'/README.md"'
		if _rc == 0 & "`skipreplace'" == "" {

			noi di as error `"{phang}A file with name README.md already exist in `folder'. Either remove option {it:all} or use either option {it:skip} or {it:replace}. See help file before using either of these options{p_end}"'
			error 602
			exit

		}
		else if _rc == 0 & "`skipreplace'" == "skip" {

			*Do nothing as option [skip] is specified, move to next folder

		}
		else {

			*If file does not exist or {replace} is specified, then write the file in this location

			*Create file
			tempname 	newHandle
			cap file close 	`newHandle'
				file open  	`newHandle' using "`folder'/README.md", text write replace

			*Add some text to the file
			file write  `newHandle' ///
				"# Placeholder file" _n _n ///
				"This file has been created automatically by the command **iegitaddmd** from the Stata package [**ietoolkit**](https://worldbank.github.io/ietoolkit) to make GitHub sync this folder. GitHub does not sync empty folders or folders that only contain ignored files, but in research projects it is often important to share the full standardized folder structure along with the actual files. This command is intended to be used with **iefolder**, but it can be used in any folder structure intended to be shared on GitHub." _n _n ///
				"In recently started projects, it is typical to create data folders, script folders (do-files, r-files) and output folders, among others. The output folders are initially empty, but the script files might include a file path to them for later use. If an output folder is empty and another collaborator clones the repository, then the output folder expected by the scripts will not be included by GitHub in the cloned repository, and the script will not run properly." _n _n ///
				"## You should replace the content of this placeholder file" _n _n ///
				"The text in this file should be replaced with text that describes the intended use of this folder. This file is written in markdown (.md) format, which is suitable for GitHub syncing (unlike .doc/.docx). If the file is named *README.md*, GitHub automatically displays its contents when someone opens the containing folder on GitHub.com using a web browser." _n _n ///
				"If you are new to markup languages (markdown, html etc.) then this [Markdown Tutorial](https://www.markdowntutorial.com/) is a great place to start. If you have some experience with markup languages, then this [Markdown Cheat Sheet](https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf) is a great place to start." _n _n ///
				"## Add similar files in other folders" _n _n ///
				"The Stata command **iegitaddmd** does not add anything to folders that already have content unless this is explicitly requested using the option `all`. It is best practice to create a *README.md* file in any folders that have content, for example, in folders whose purpose might not obvious to someone using the repository for the first time. Again, if the file is named *README.md*, then the content of the file will be shown in the browser when someone explores the repository on GitHub.com." _n _n ///
				"Another great use of a *README.md* file is to use it as a documentation on how the folder it sits in and its subfolders are organized,  where its content can be found, and where new content is meant to be saved. For example, if you have a folder called `/Baseline/`, then you can give a short description of the activities conducted during the baseline and where data, scripts and outputs related to it can be found." _n _n ///
				"## Removing this file" _n _n ///
				"Removing this file is not recommended, as GitHub may stop syncing the parent folder unless the folder now has other content. However, even when content is added and the file can be removed without breaking the GitHub functionality, our recommendation is to replace the content with more relevant content rather than deleting it." _n _n

			*Closing the file
			file close 		`newHandle'

		}

end


iekdensity: first part of #210

iekdensity: first part of #210

Working for plotting the figures and for the [color] option, but need to allow the estimation of different treatment effects -- added option [control] for specifying the control group, which serves as comparison for this

*! version DEVELOP 12SEP2019 Matteo Ruzzante mruzzante@worldbank.org

	cap prog drop    iekdensity
		prog define  iekdensity

		syntax 		 varname(numeric) [if] [in] [aw fw iw] , 	/// Specify outcome variable (only fweights, aweights, and iweights are allowed in original [kdensity] command)
																///
			TREATvar(varname numeric)							/// Specify treatment variable
																///
		   [													///
		    color(string)										/// Specify string of colors
																///
		    stat(string)										/// Add lines with (one) statistic
		    statstyle(string)									/// Lines style. Default is dashed pattern.
																///
		    effect												/// Add note with treatment effect (beta, SD, and p-value)
		    control(numlist int max=1)							/// Specify value of control variable -- for comparisons
			effectformat(string)								/// Format of treatment treatment effect. Default is "%9.3f".
		    ABSorb(varname numeric)								/// Strata variable
		    REGressionoptions(string)							/// Allows any normal options for linear regressions
																///
		    KDENSITYoptions(string)								/// Allows kernel options (namely [kernel], [bwidth], [n], and all the [cline_options]) for univariate kernel density estimation
		    GRaphoptions(string)								/// Allows any normal options for twoway graphs
		   ]

/*******************************************************************************
	Prepare settings
*******************************************************************************/

		qui {

			// Set minimum version for this command
			version 11

			// Preserve current dataset
			preserve

			// Remove observations excluded by if and in conditions
			marksample touse, novarlist
			keep if   `touse'

			**************************************************
			* Check if treatment variable is binary or factor
			**************************************************

			// Remove observations with a missing value in treatvar()
			drop if    missing(`treatvar')

			// Create a local of all codes in treatvar()
			levelsof  `treatvar'			, local(treatvar_levels)

			// Saving the name of the value label of the treatvar()
			local 	   treatvar_value_label : value label `treatvar'

			// Counting how many levels there are in treatvar()
			local 	   treatvar_num_groups  : word  count `treatvar_levels'

/*******************************************************************************
	Prepare optional options
*******************************************************************************/
/*------------------------------------------------------------------------------
	Color options
------------------------------------------------------------------------------*/
{
			// If no [color] is specified, keep standard Stata color palette.
			// Otherwise, set user-specified colors
			if	missing("`color'") {

				set scheme s2color

				local 	  treatvar_count = 0

				foreach   treatvar_num of local treatvar_levels {

					if 	 `treatvar_count' == 0 local color_`treatvar_num' "navy"
					if 	 `treatvar_count' == 1 local color_`treatvar_num' "maroon"
					if 	 `treatvar_count' == 2 local color_`treatvar_num' "forest_green"
					if 	 `treatvar_count' == 3 local color_`treatvar_num' "dkorange"
					if   `treatvar_count' == 4 local color_`treatvar_num' "teal"
					if	 `treatvar_count' == 5 local color_`treatvar_num' "cranberry"

					local treatvar_count = `treatvar_count' + 1
				}
			}

			if !missing("`color'") {

				// Check that the number of colors specified matches
				local color_num_groups : word count `color'

				// If it is smaller or larger, we throw an error
				if   `color_num_groups' > `treatvar_num_groups' {

					  noi di as error "{phang}The number of colors you specified in {bf:color()} is higher than the number of categories in the treatment variable {bf:treatvar()}."
					  noi di as error "{phang}Please define one color for each category."
								exit
				}

				if   `color_num_groups' < `treatvar_num_groups' {

					noi di as error "{phang}The number of colors you specified in {bf:color()}  is smaller than the number of categories in the treatment variable {bf:treatvar()}."
					noi di as error "{phang}Please define one color for each category."
							  exit
				}

				else {

					// Put colors in correct local for the graph
					local colorNum = 1

					foreach   treatvarNum of local treatvar_levels {

						local treatvar_`treatvarNum' 		 = word("`treatvar_levels'" , `colorNum')
						di "`treatvar_`treatvarNum''"

						local color_`treatvar_`treatvarNum'' = word("`color'"		    , `colorNum')

						di "`color_`treatvar_`treatvarNum''''"

						local colorNum = `colorNum' + 1
					}
				}
			}
}
/*------------------------------------------------------------------------------
	Stat options
------------------------------------------------------------------------------*/
{
			*******************************************
			* Test that option was correctly specified
			*******************************************

			// If no [stat] is specified, option [statstyle] also shouldn't be
			if missing("`stat'") & !missing("`statstyle'") {

				noi di as error "{phang}{bf:statstyle()} requires the option {bf:stat()} to be specified."
				noi di as error "{phang}If you want to use {bf:statstyle()}, you need to include the option {bf:stat()} too."
						  exit

			}
			// If no [stat] is specified, do not attach any vertical line in the plot
			else if  missing("`stat'") {

				local STATxline ""

			}
			// If [stat] option is specified, add option to graph vertical lines
			else if !missing("`stat'") {

				local statIfList 	 ""
				local statBoldString ""
				local statsList		 mean min max p1 p5 p10 p25 p50 p75 p90 p95 p99

				foreach possibleStat of local statsList 	{

					local statIfList	 `" `statIfList' "`stat'" != "`possibleStat'" & "'
					local statBoldString `"`statBoldString' {bf:`possibleStat'},"'

				}

				// Remove final comma
				local statBoldString = substr("`statBoldString'", 1, length("`statBoldString'") - 1)

				di `"`statIfList'"'

				// Check that the stat is among possible statistics
				if `statIfList' "ok" == "ok" {

					noi di as error "{phang}The {bf:stat()} you selected cannot be shown in the graph."
					noi di as error "{phang}The available statistics are:`statBoldString'."
							  exit
				}

				else {

					// Summarize variable and store locals with defined value
					foreach    varNum of local treatvar_levels {

						sum   `varlist' if `treatvar' == `varNum' , d
						local `varlist'_`varNum' = `r(`stat')'
					}
				}

				if missing("`statstyle'") {

					// In the absence of [statstyle], we only use the 'dash' patttern
					local 	  STATxline ""

					foreach   varNum of local treatvar_levels {

						local STATxline `" `STATxline' xline( ``varlist'_`varNum'' , lcolor(`color_`varNum''%80) lpattern(dash) ) "'
					}
				}

				di "`STATxline'"

				else if !missing("`statstyle'") {

					// Check that [statstyle] is correctly specified
					set graphics off //we set graphics off as the option [nograph] is conflicting with [xline]
					cap kdensity `varlist' , xline( ``varlist'_0' , `statstyle' )

					if _rc != 0 {

						set graphics on

						// If it's not, replot the kernel density, which will automaticallty show the underlying error
						kdensity `varlist' , xline( ``varlist'_0' , `statstyle' )
					}
					else {

						// Store local with [xline] option to be graphed
						local 	  STATxline ""

						foreach   varNum of local treatvar_levels {

							local STATxline `" `STATxline' xline( ``varlist'_`varNum'' , lcolor(`color_`varNum''%80) `statstyle' ) "'
						}
					}

					set graphics on
				}
			}
}
/*------------------------------------------------------------------------------
	Control options
------------------------------------------------------------------------------*/
{
			// If control() is used, test that the value specified exists in the [treatvar]
			local control_correct : list control in treatvar_levels

			if   `control_correct' == 0 {

				  noi di as error "{phang}The code listed in control(`control') is not used in tretavar(`treatvar'). See tabulation of `treatvar' below:"
				  noi tab `testvar', nol
				  error 197
			}
}
/*------------------------------------------------------------------------------
	Treatment effect options
------------------------------------------------------------------------------*/
{
			// If [effectnote], [absorb], or [regressoptions] option is specified, check that [effect] was specified
			if "`effect'" == "" {

				foreach effectOption in effectnote absorb regressoptions {

					if "``effectOption''" != "" {

						noi di as error "{phang}{bf:`effectOption'()} requires the option {bf:effect} to be specified."
						noi di as error "{phang}If you want to use {bf:`effectOption'()}, you need to include the option {bf:effect} too."
								  exit
					}
				}

				// If no [effect] is specified, do not attach any note with the effect
				local EFFECTnote ""
			}

			// If [effect] option is specified, add option to include note
			if "`effect'" != "" {

				// If format is specified, check that it is possible
				if "`effectformat'" != "" {

					// ------------------------------------------------
					// The procedure below is taken from 'iebaltab.ado'

					** Creating a numeric mock variable that we attempt to apply the format
					*  to. This allows us to piggy back on Stata's internal testing to be
					*  sure that the format specified is at least one of the valid numeric
					*  formats in Stata
						tempvar  formattest
						gen 	`formattest' = 1
					cap	format  `formattest' `effectformat'

					if _rc == 120 {

						di as error "{phang}The format specified in effectformat(`effectformat') is not a valid Stata format. See {help format} for a list of valid Stata formats. This command only accept the f, fc, g, gc and e format.{p_end}"
						error 120
					}
					else if _rc != 0 {

						di as error "{phang}Something unexpected happened related to the option format(`format'). Make sure that the format you specified is a valid format. See {help format} for a list of valid Stata formats. If this problem remains, please report this error to mruzzante@worldbank.org.{p_end}"
						error _rc
					}
					else {
						** We know here that the format is one of the numeric formats that Stata allows

						local formatAllowed 0
						local charLast  = substr("`effectformat'", -1, .)
						local char2Last = substr("`effectformat'", -2, .)

						if  "`charLast'" == "f" | "`charLast'" == "e" {
							local formatAllowed 1
						}
						else if "`charLast'" == "g"  {
							if "`char2Last'" == "tg" {
								*format tg not allowed. all other valid formats ending on g are allowed
								local formatAllowed 0
							}
							else {

								*Formats that end in g that is not tg can only be g which is allowed.
								local formatAllowed 1
							}
						}
						else if  "`charLast'" == "c" {
							if "`char2Last'" != "gc" & "`char2Last'" != "fc" {
								*format ends on c but is neither fc nor gc
								local formatAllowed 0
							}
							else {

								*Formats that end in c that are either fc or gc are allowed.
								local formatAllowed 1
							}
						}
						else {
							*format is neither f, fc, g, gc nor e
							local formatAllowed 0
						}
						if `formatAllowed' == 0 {
							di as error "{phang}The format specified in effectformat(`effectformat') is not allowed. Only format f, fc, g, gc and e are allowed. See {help format} for details on Stata formats.{p_end}"
							error 120
						}
						*If format passed all tests, store it in the local used for display formats
						local TEformat  "`effectformat'"
					}

					// ------------------------------------------------
				}

				if "`effectformat'" == "" {

					// Otherwise, we will use "%9.3f"
					local TEformat  "%9.3f"
				}

				// Standard case with binary treatment
				// -----------------------------------
				if `treatvar_num_groups' == 2 {

					if "`treatvar_levels'" == "0 1" & missing("`control'") {

						// Estimate treatment effect
						if "`absorb'" == "" {
							reg  `varlist' `treatvar' [`weight'`exp'] , `regressionoptions'
						}

						// If there are strata, estimate treatment effect controlling for strata
						if "`absorb'" != "" {

							areg `varlist' `treatvar' [`weight'`exp'] , `regressionoptions' abs(`absorb')
						}

						// Store estimated coefficient, standard error, p-value, and number of observations
						mat   results = r(table)
						local beta	  = string( _b[`treatvar'] , "`TEformat'")
						local se	  = string(_se[`treatvar'] , "`TEformat'")
						local pvalue  = results[4, 1]

						local obs	  = e(N)

						// Add level of statistical significance
						local stars     ""

						foreach signLevel in 0.1 0.05 0.01 {

							if `pvalue' < `signLevel' local stars "`stars'*"
						}

						local EFFECTnote " note(Treatment effect = `beta' (`se')`stars'. N = `obs'.) "
					}

					// If the values are not 0/1 and no [control] is specified, signal this to the user
					else if "`treatvar_levels'" != "0 1" {

						if missing("`control'") {

							noi di as error "{phang}The treatment variable in {bf:treatvar()} is not a 0/1 dummy variable."
							noi di as error "{phang}If you want to compute the treatment effect, you need to indicate which value is referred to the control group using the option {bf:control()}."
									  exit
						}

						else if !missing("`control") {

							// When the binary variable is not 0/1, recode variable to be 0/1, depending on the control specified
							tempvar  treatmentAux
							gen	    `treatmentAux' = 0 if `treatvar' == `control'
							replace `treatmentAux' = 1 if `treatvar' != `control'

							if "`absorb'" == "" {
								reg  `varlist' ``treatmentAux'' [`weight'`exp'] , `regressionoptions'
							}

							if "`absorb'" != "" {

								areg `varlist' ``treatmentAux'' [`weight'`exp'] , `regressionoptions' abs(`absorb')
							}

							mat   results = r(table)
							local beta	  = string( _b[``treatmentAux''] , "`TEformat'")
							local se	  = string(_se[``treatmentAux''] , "`TEformat'")
							local pvalue  = results[4, 1]

							local obs	  = e(N)

							local stars     ""

							foreach signLevel in 0.1 0.05 0.01 {

								if `pvalue' < `signLevel' local stars "`stars'*"
							}

							local EFFECTnote " note(Treatment effect = `beta' (`se')`stars'. N = `obs'.) "
						}
					}
				}

				// Case with factor variables
				// --------------------------
				if `treatvar_num_groups'  > 2 {

					// In this case, the control value must be specified
					if  missing("`control'") {

						noi di as error "{phang}The treatment variable in {bf:treatvar()} is a factor variable."
						noi di as error "{phang}If you want to compute the treatment effect, you need to indicate which value is referred to the control group using the option {bf:control()}."
								  exit
					}

					if !missing("`control'") {

						// Generate dummies from factor variable
						gen control = `treatvar' == `control'

						// Local with values other than [control]
						levelsof `treatvar' if `treatvar' != `control', local(treatvar_comparison)

						foreach treatvarNum of local treatvar_comparison {

							// Generate treatment dummies
							gen treat`treatvarNum' = `treatvar' == `treatvarNum'

							// Run regression versus control

						}
					}
				}
			}

}
/*******************************************************************************
	Prepare figure inputs
*******************************************************************************/
{
			// If the treatment variable has no defined label, we leave the value in the legend
			if  missing("`treatvar_value_label'") {

				foreach treatvarNum of local treatvar_levels {

					local label_`treatvarNum' "`treatvarNum'"
				}
			}

			// If the treatmnet variable has labels, we will display it in the legend
			if !missing("`treatvar_value_label'") {

				foreach treatvarNum of local treatvar_levels {

					local label_`treatvarNum' : label (`treatvar') `treatvarNum'
				}
			}

			// Prepare [kdensity] code and legend argument
			local     treatCount = 0
			local kdensityString  "tw"
			local 	legendString `"legend(order(0 "Treatment assignment:" "'

			foreach treatvarNum of local treatvar_levels {

				local treatCount	  = `treatCount' + 1

				local kdensityString `" `kdensityString' (kdensity `varlist' if `treatvar' == `treatvarNum' [`weight'`exp'] , `kdensityoptions' color(`color_`treatvarNum'') ) "'

				local   legendString `"   `legendString' `treatCount' "`label_`treatvarNum''" "'
			}

			// Add final parenthesis and number of rows (this can be changed by the user through graphoptions()
			if   `treatvar_num_groups' == 2 		 local rowString "row(1)"

			local legendString         `" `legendString') `rowString' )"'

			// Record outcome variable label to be shown as x-axis title
			local varLab 				: var label `varlist'
		}
}

/*******************************************************************************
	Plot the figure!
*******************************************************************************/

		`kdensityString' 		///
		 , 						///
		`STATxline' 			///
		`EFFECTnote' 			///
		`legendString'			///
		 ytitle(Density) 		///
		 xtitle(`varLab')		///
		`graphoptions'

		// Restore original dataset
		restore

	// End
	end

	// And now, you shall plot the distributions! ;)

*! version DEVELOP 5SEP2019 Matteo Ruzzante mruzzante@worldbank.org

	cap prog drop    iekdensity
		prog define  iekdensity

		syntax 		 varname(numeric) [if] [in] [aw fw iw] , 	/// Specify outcome variable (only fweights, aweights, and iweights are allowed in original kdensity command)
																///
			TREATvar(varname numeric)							/// Specify treatment variable (must  be binary) -- next step: allow for factor variable
																///
		   [													///
		    treatcolor(string)									/// Choose color for treatment
		    controlcolor(string)								/// Choose color for control -- can be combined in a single option, like 'color(value1 color1 @ value2 color2)'
																///
		    stat(string)										/// Add lines with (one) statistic
		    statstyle(string)									/// Lines style. Default is dashed pattern.
																///
		    effect												/// Add note with treatment effect (beta, SD, and p-value)
		    effectformat(string)								/// Format of treatment treatment effect. Default is "%9.3f".
		    ABSorb(varname numeric)								/// Strata variable
		    REGressionoptions(string)							/// Allows any normal options for linear regressions
																///
		    KDENSITYoptions(string)								/// Allows kernel options (namely [kernel], [bwidth], [n], and all the [cline_options]) for univariate kernel density estimation
		    GRaphoptions(string)								/// Allows any normal options for twoway graphs
		   ]

/*******************************************************************************
	Prepare settings
*******************************************************************************/

		qui {

			// Set minimum version for this command
			version 11

			// Preserve current dataset
			preserve

			// Remove observations excluded by if and in conditions
			marksample touse, novarlist
			keep if   `touse'

/*******************************************************************************
	Prepare optional options
*******************************************************************************/

/*------------------------------------------------------------------------------
	Color options
------------------------------------------------------------------------------*/

			// If no [color] is specified, keep standard Stata color palette.
			// Otherwise, set user-specified colors

			* For treatment group
			if  missing("`treatcolor'") {
				local color_1 maroon
			}
			else {
				local color_1 `treatcolor'
			}

			* For control group
			if  missing("`controlcolor'") {
				local color_0 navy
			}
			else {
				local color_0 `controlcolor'
			}

/*------------------------------------------------------------------------------
	Stat options
------------------------------------------------------------------------------*/

			*******************************************
			* Test that option was correctly specified
			*******************************************

			// If no [stat] is specified, option [statstyle] also shouldn't be
			if missing("`stat'") & !missing("`statstyle'") {

				noi di as error "{bf:statstyle()} requires the option {bf:stat()} to be specified."
				noi di as error "If you want to use {bf:statstyle()}, you need to include the option {bf:stat()} too."
						  exit

			}
			// If no [stat] is specified, do not attach any vertical line in the plot
			else if missing("`stat'") {

				local STATxline ""

			}
			// If [stat] option is specified, add option to graph vertical lines
			else if !missing("`stat'") {

				local statsList 	 mean min max p1 p5 p10 p25 p50 p75 p90 p95 p99

				foreach possibleStat of local statsList {

					local statIfList     `"`statIfList' "`stat'", "'
					local statBoldString `"`statBoldString' {bf:`possibleStat'},"'

				}

				// Check that the stat is among possible statistics
				if !inlist("`stat'", `statIfList') {

					noi di as error "The {bf:stat} you selected cannot be shown in the graph."
					noi di as error "The available statistics are:`statBoldString'."
							  exit
				}
				else {

					// Summarize variable and store locals with defined value
					sum	  `varlist' if `treatvar' == 0 , d
					local `varlist'_0 = `r(`stat')'

					sum	  `varlist' if `treatvar' == 1 , d
					local `varlist'_1 = `r(`stat')'
				}

				if missing("`statstyle'") {

					// In the absence of [statstyle], we only use the 'dash' patttern
					local STATxline `" xline( ``varlist'_0' , lcolor(`color_0'%80) lpattern(dash) ) xline(``varlist'_1' , lcolor(`color_1'%80) lpattern(dash) ) "'

				}
				else if !missing("`statstyle'") {

					// Check that [statstyle] is correctly specified
					set graphics off //we set graphics off as the option [nograph] is conflicting with [xline]
					cap kdensity `varlist' , xline( ``varlist'_0' , `statstyle' )

					if _rc != 0 {

						set graphics on

						// If it's not, replot the kernel density, which will automaticallty show the underlying error
						kdensity `varlist' , xline( ``varlist'_0' , `statstyle' )
					}
					else {

						// Store local with [xline] option to be graphed
						local STATxline `" xline( ``varlist'_0' , lcolor(`color_0'%80) `statstyle' ) xline(``varlist'_1' , lcolor(`color_1'%80) `statstyle' ) "'
					}

					set graphics on
				}

			}

/*------------------------------------------------------------------------------
	Treatment effect options
------------------------------------------------------------------------------*/

				// If [effectnote], [absorb], or [regressoptions] option is specified, check that [effect] was specified
				if "`effect'" == "" {

					foreach effectOption in effectnote absorb regressoptions {

						if "``effectOption''" != "" {

							noi di as error "{bf:`effectOption'()} requires the option {bf:effect} to be specified."
							noi di as error "If you want to use {bf:`effectOption'()}, you need to include the option {bf:effect} too."
									  exit
						}
					}

					// If no [effect] is specified, do not attach any note with the effect
					local EFFECTnote ""
				}

				// If [effect] option is specified, add option to include note
				if "`effect'" != "" {

					// If format is specified, check that it is possible
					if "`effectformat'" != "" {

						// ------------------------------------------------
						// The procedure below is taken from 'iebaltab.ado'

						** Creating a numeric mock variable that we attempt to apply the format
						*  to. This allows us to piggy back on Stata's internal testing to be
						*  sure that the format specified is at least one of the valid numeric
						*  formats in Stata
							tempvar  formattest
							gen 	`formattest' = 1
						cap	format  `formattest' `effectformat'

						if _rc == 120 {

							di as error "{phang}The format specified in effectformat(`effectformat') is not a valid Stata format. See {help format} for a list of valid Stata formats. This command only accept the f, fc, g, gc and e format.{p_end}"
							error 120
						}
						else if _rc != 0 {

							di as error "{phang}Something unexpected happened related to the option format(`format'). Make sure that the format you specified is a valid format. See {help format} for a list of valid Stata formats. If this problem remains, please report this error to mruzzante@worldbank.org.{p_end}"
							error _rc
						}
						else {
							** We know here that the format is one of the numeric formats that Stata allows

							local formatAllowed 0
							local charLast  = substr("`effectformat'", -1, .)
							local char2Last = substr("`effectformat'", -2, .)

							if  "`charLast'" == "f" | "`charLast'" == "e" {
								local formatAllowed 1
							}
							else if "`charLast'" == "g"  {
								if "`char2Last'" == "tg" {
									*format tg not allowed. all other valid formats ending on g are allowed
									local formatAllowed 0
								}
								else {

									*Formats that end in g that is not tg can only be g which is allowed.
									local formatAllowed 1
								}
							}
							else if  "`charLast'" == "c" {
								if "`char2Last'" != "gc" & "`char2Last'" != "fc" {
									*format ends on c but is neither fc nor gc
									local formatAllowed 0
								}
								else {

									*Formats that end in c that are either fc or gc are allowed.
									local formatAllowed 1
								}
							}
							else {
								*format is neither f, fc, g, gc nor e
								local formatAllowed 0
							}
							if `formatAllowed' == 0 {
								di as error "{phang}The format specified in effectformat(`effectformat') is not allowed. Only format f, fc, g, gc and e are allowed. See {help format} for details on Stata formats.{p_end}"
								error 120
							}
							*If format passed all tests, store it in the local used for display formats
							local TEformat  "`effectformat'"
						}

						// ------------------------------------------------
					}

					if "`effectformat'" == "" {

						// Otherwise, we will use "%9.3f"
						local TEformat  "%9.3f"
					}

					// Estimate treatment effect
					if "`absorb'" == "" {
						reg  `varlist' `treatvar' [`weight'`exp'] , `regressionoptions'
					}

					// If there are strata, estimate treatment effect controlling for strata
					if "`absorb'" != "" {

						areg `varlist' `treatvar' [`weight'`exp'] , `regressionoptions' abs(`absorb')
					}

					// Store estimated coefficient, standard error, p-value, and number of observations
					mat   results = r(table)
					local beta	  = string( _b[`treatvar'] , "`TEformat'")
					local se	  = string(_se[`treatvar'] , "`TEformat'")
					local pvalue  = results[4, 1]

					local obs	  = e(N)

					* Add level of statistical significance
					local stars     ""

					foreach signLevel in 0.1 0.05 0.01 {

						if `pvalue' < `signLevel' local stars "`stars'*"
					}

					local EFFECTnote " note(Treatment effect = `beta' (`se')`stars'. N = `obs'.) "
				}
			}

			// Record outcome variable label to be shown as x-axis title
			local varLab : var label `varlist'

/*******************************************************************************
	Plot the figure!
*******************************************************************************/

		tw  (kdensity `varlist' if `treatvar' == 0 [`weight'`exp'] , `kdensityoptions' color(`color_0') ) ///
			(kdensity `varlist' if `treatvar' == 1 [`weight'`exp'] , `kdensityoptions' color(`color_1') ) ///
			, 																							  ///
			`STATxline' 																				  ///
			`EFFECTnote' 																				  ///
			ytitle	(Density) 																			  ///
			xtitle	(`varLab') 																			  ///
			legend	(order(0 "Treatment assignment:" 1 "Control" 2 "Treatment") 						  ///
					 row(1) 																			  ///
					) 																					  ///
			`graphoptions'

		// Restore original dataset
		restore

	// End
	end

	// And now, you shall plot the distributions! ;)