*! version 1.5.4 Jens Hainmueller, Yiqing Xu 01/29/15 program ebalance , eclass version 11.2 syntax varlist(min=1 numeric fv) [if] [in] [, TARgets(numlist >0 <=3 integer) /// MANUALtargets(numlist) /// BASEwt(varname numeric) /// NORMconst(real 1.0) /// Generate(name) /// WTTReat /// MAXIter(integer 20) /// TOLerance(real .015) /// Keep(string) /// REPlace] qui: marksample touse // exclude obs with missng data tempvar baseweight treated controls /* Initial Checks */ preserve // in case one of the checks fails /* check new variable name */ if "`generate'"=="" { loc generate = "_webal" } cap confirm variable `generate', exact if _rc == 0 { if "`generate'"=="_webal" { cap drop _webal } else { dis as err "variable `generate' already defined" exit } } /* check replace under keep() */ if ("`keep'" == "" & "`replace'" != "") { dis as err "option replace invalid -- keep() is not specified" exit } /* check file already exists */ if ("`keep'" != "" & "`replace'" == "") { confirm new file `keep' confirm new file `keep'.dta } /* check for treatment indicator and colinearity */ if ("`manualtargets'"!="") { // manual mode: single group where treat is omitted if ("`targets'"!="") { dis as err "targets() and manualtargets() are not compatible" exit } if "`wttreat'"!="" { dis as err "wttreat and manualtargets() are not compatible" exit } * check number of observations qui: count if `touse' == 1 if (`r(N)' <= 1) { dis as err "insufficient observations" exit } * create factor variables as temporary variables (so they can be used with other commands) fvrevar `varlist' local fvarlist `r(varlist)' * drop colinear temp vars qui: _rmcoll `fvarlist' if `touse', forcedrop local fvarlist `r(varlist)' * create list of varnames for labeling _rmcoll `varlist' if `touse', expand local lfvarlist `r(varlist)' * create pseudo treatment indicator tempvar D qui: gen `D'=0 if `touse'==1 } else { // auto mode: two groups with treat specified * parse treatment var gettoken D varlist: varlist if ("`varlist'"=="") { disp as err "too few variables specified" exit } * check treatment variable _fv_check_depvar `D' qui: count if `D'!=1 & `D'!=0 & `touse' == 1 if (`r(N)'>0) { dis as err "treatment indicator (`D') must be a logical variable, TRUE (1) or FALSE (0)" exit } qui: sum `D' if `touse' == 1 if (`r(Var)' == 0) { dis as err "treatment indicator (`D') must contain both treatment and control observations" exit } qui: count if `D'==0 & `touse' == 1 if (`r(N)' <= 1) { dis as err "insufficient observations" exit } * create factor variables as temp vars fvrevar `varlist' local fvarlist `r(varlist)' * drop colinear temp vars qui: _rmcoll `fvarlist' if `touse' & `D'==0, force local fvarlist `r(varlist)' * create list of varnames for labeling _rmcoll `varlist' if `touse' & `D'==0 , expand local lfvarlist `r(varlist)' } * remove omitted factor variables from list of varnames for labels foreach var of local lfvarlist { _ms_parse_parts `var' if !`r(omit)' { local list `list' `var' } else { if ("`r(type)'"=="factor") dis as txt "note: `var' omitted because of collinearity" } } * final list of varnames for labeling local lfvarlist `list' /* check baseweight */ if ("`basewt'"!="") { if ("`wttreat'"=="") { /* case without base weights for treated units */ /* drop controls with missing weight */ di "test" qui: count if `basewt'==. & `D' == 0 & `touse' == 1 if (`r(N)'>0) di as txt "note: `r(N)' control units with missing base weights are not assigned weights" qui: replace `touse'=0 if `basewt'==. & `D' == 0 & `touse' == 1 /* if user specified baswt has numbers in it for treated */ qui: count if (`basewt'~=.|`basewt'~=0) & `D' == 1 & `touse' == 1 if (`r(N)'~=0) di as txt "note: base weights for the treated units are set to one unless wttreat is specified" qui: gen `baseweight' = `basewt' if `D' == 0 & `touse' == 1 qui: replace `baseweight' = 1 if `D' == 1 & `touse' == 1 } else { /* case with base weights for treated units */ qui: count if `basewt'==. & `touse' == 1 if ("`manualtargets'"!="") { qui: replace `basewt' = 1 in -1 /* base weight for artificial treated unit in single group case */ } qui: replace `touse'=0 if `basewt'==. & `touse' == 1 /* drop units with missing weight */ if (`r(N)'>0) di as txt "note: `r(N)' units with missing base weights are dropped" qui: gen `baseweight' = `basewt' if `touse' == 1 } } else { if "`wttreat'"!="" { dis as err "weigthing variable required" exit } else { qui: gen `baseweight' = 1 if `touse' == 1 } } *--------------populate values for target moments --------------------------- /* get number of covariates */ local tempvarlist `fvarlist' local xnum1 = 0 local xnum2 = 0 //# of 2nd order moment constraints local xnum3 = 0 //# of 3rd order moment constraints while "`tempvarlist'"~="" { gettoken xx tempvarlist: tempvarlist local ++xnum1 } /* single group mode vs. two group mode */ if ("`manualtargets'"!="") { // single group mode /* check numlist in manualtargets*/ if wordcount("`manualtargets'")>`xnum1' { dis as err "manualtargets() invalid -- numlist has too many elements" exit } if wordcount("`manualtargets'")<`xnum1' { dis as err "manualtargets() invalid -- numlist has too few elements" exit } /* generate one artifical treated in new last row */ mata st_addobs(1) qui: replace `D' =1 in -1 qui: replace `touse'=1 in -1 qui: replace `baseweight' = 1 if `touse' == 1 /* fill in target moments */ tokenize "`manualtargets'" local tempvarlist `fvarlist' local i = 1 while "`tempvarlist'"~="" { gettoken xx tempvarlist: tempvarlist qui: replace `xx' = ``i'' in -1 local ++i } /* create Xnames matrix with (temp varname, label) passing vars to mata algo. */ mata: Xnames1 = J(0,2,"") local tempvarlist `fvarlist' local labelvarlist `lfvarlist' forvalues i=1(1)`xnum1' { gettoken xx tempvarlist: tempvarlist gettoken yy labelvarlist: labelvarlist mata: Xnames1 = Xnames1 \ ("`xx'","`yy'") } mata: Xnames = Xnames1 } else { //two-group mode /* check target numlist */ if "`targets'"=="" { local targets = "1" } if wordcount("`targets'")>`xnum1' { dis as err "targets() invalid -- numlist has too many elements" exit } else { if wordcount("`targets'")<`xnum1' { /* if only one number, expand it to full exp., e.g. 2 means (2, 2, ..., 2) */ if wordcount("`targets'")==1 { local target0="`targets'" local i=1 while (`i++'<`xnum1'){ local targets = "`targets'"+ " " + "`target0'" } } else { dis as err "targets() invalid -- numlist has too few elements" exit } } } /* create Xnames matrix with (temp varname, label) passing vars to mata algo. */ * start with empty matrix for 1st, 2nd, and 3rd order moments foreach name in Xnames1 Xnames2 Xnames3 { mata: `name' = J(0,2,"") } local tempvarlist `fvarlist' local labelvarlist `lfvarlist' tokenize "`targets'" forvalues i=1(1)`xnum1' { /* populate matrix for 1st order moments */ gettoken xx tempvarlist: tempvarlist gettoken yy labelvarlist: labelvarlist mata: Xnames1 = Xnames1 \ ("`xx'","`yy'") if (``i''>= 2) { /* populate matrix for 2nd order moments */ /* exclude high orders for bivariate covars */ qui: sum(`xx') if `D'==1 & `touse' == 1 // less than 2 values among the treated if (r(sd)==0) { dis as txt "note: higher order constraint omitted due to too few values of `xx'" continue } qui: sum(`xx') if `D'==0 & `touse' == 1 // less than 2 values among controls if (r(sd)==0) { dis as txt "note: higher order constraint omitted due to too few values of `xx'" continue } /* gen temp squared terms (degrees of freedom adjusted) */ qui: sum(`xx') [aweight=`baseweight'] if `D'==1 & `touse' == 1 local treat_mean = r(mean) local treat_N = r(N) qui: sum(`xx') [aweight=`baseweight'] if `D'==0 & `touse' == 1 local ctrl_N = r(N) tempvar `xx'_2 qui: gen ``xx'_2' = (`xx' - `treat_mean')^2*`ctrl_N'/(`ctrl_N'-1) if `touse' == 1 & `D'==0 qui: replace ``xx'_2' = (`xx' - `treat_mean')^2*`treat_N'/(`treat_N'-1) if `touse' == 1 & `D'==1 * mata: idxvar2 = idxvar2, (st_varindex(tokens("``xx'_2'"))) mata: Xnames2 = Xnames2 \ ("``xx'_2'","`yy'") local ++xnum2 } if (``i''== 3) { /* populate matrix for 3rd order moments */ /* gen temp cubed terms (degrees of freedom adjusted to be consistent with STATA defination , see Manual "summarize" ) */ tempvar `xx'_3 qui: gen ``xx'_3' = (`xx' - `treat_mean')^3*(`ctrl_N'/(`ctrl_N'-1))^1.5 if `touse' == 1 & `D'==0 qui: replace ``xx'_3' = (`xx' - `treat_mean')^3*(`treat_N'/(`treat_N'-1))^1.5 if `touse' == 1 & `D'==1 mata: Xnames3 = Xnames3 \ ("``xx'_3'","`yy'") local ++xnum3 } } mata: Xnames = Xnames1 \ Xnames2 \ Xnames3 /* complete var matrix for passing to main mata algo */ } /* create variable names for 1st, 2nd, 3rd order moment constraints */ forvalues i=1(1)3 { mata: constrt`i'="" forvalues v=1(1)`xnum`i'' { mata: constrt`i'=constrt`i'+" "+Xnames`i'[`v',2] } mata: st_local("constrt`i'",constrt`i') } mata: constrts=constrt1+constrt2+constrt3 mata: st_local("constrts", constrts) /* check overlap */ local tempvarlist `fvarlist' while "`tempvarlist'"~="" { gettoken xx tempvarlist: tempvarlist qui: sum `xx' if `D'==1 & `touse'==1 local tmax = r(max) local tmin = r(min) qui: sum `xx' if `D'==0 & `touse'==1 local cmax = r(max) local cmin = r(min) if (`tmax'<`cmin' | `cmax'<`tmin') { if ("`manualtargets'"~="") dis as err "target beyond the range of `xx'" else dis as err "no overlap in `xx'" exit } } /********************* Produce initial output ************************* */ di _newline(1) di as res "Data Setup" if ("`manualtargets'"=="") { di as txt "Treatment variable: " as res "`D'" mata: printf("{txt}Covariate adjustment:") mata: printf("%s ", constrt1) if `xnum2'!=0 mata: printf("{txt}(1st order).{res}%s {txt}(2nd order).",constrt2) if `xnum3'!=0 mata: printf("{res}%s {txt}(3rd order).\n",constrt3) } else mata: printf("{txt}Covariate adjustment:{res}%s{txt}\n", constrt1) di _newline(1) /* ********************** Main Algo ************************* */ qui: gen `treated' = 0 qui: replace `treated' = 1 if `D' == 1 & `touse' == 1 qui: gen `controls' = 0 qui: replace `controls' = 1 if `D' == 0 & `touse' == 1 di as res "Optimizing..." mata: eb("`treated'","`controls'",Xnames[,1]',"`baseweight'","`generate'",`maxiter',`tolerance') if (res_converge == 0) { /* report the most demanding variable in case of non-convegence */ if (res_maxdist == 0) dis as txt "note: The algorithm fails to adjust the summation of weights (possibly due to too few control units)" else if (res_maxdist <= `xnum1') di as txt "note: The algorithm fails to adjust the mean (1st order moment) of " as res word("`constrt1'",res_maxdist) else if (res_maxdist <= `xnum1'+`xnum2') di as txt "note: The algorithm fails to adjust the variance (2nd order moment) of " as res word("`constrt2'",res_maxdist-`xnum1') else di as txt "note: The algorithm fails to adjust the 3rd order moment of " as res word("`constrt2'",res_maxdist-`xnum1'-`xnum2') if ("`keep'" != "") dis as txt "note: convergence not achieved; balance table not saved" /* disp tabstat without eb weighting: manual and auto cases */ if ("`manualtargets'"!="") { di _newline(1) if "`basewt'"=="" di as res "Pre-balancing: " as txt "without weighting" else di as res "Pre-balancing: " as txt "`basewt' as the weighting variable" qui: tabstat `fvarlist' [aweight=`baseweight'] if `touse' == 1, c(s) s(mean variance skewness) longstub save tempname Pre Post mat `Pre' = r(StatTotal)' mat rownames `Pre' = `lfvarlist' matlist `Pre', tw(12) format(%9.4g) } else { di _newline(1) if "`basewt'"=="" di as res "Pre-balancing: " as txt "without weighting" else di as res "Pre-balancing: " as txt "`basewt' as the weighting variable" qui: tabstat `fvarlist' [aweight=`baseweight'] if `touse' == 1, c(s) by(`D') s(mean variance skewness) nototal longstub save tempname Pre Post mat `Pre' = r(Stat2)', r(Stat1)' mat colnames `Pre' = mean variance skewness mean variance skewness mat coleq `Pre' = Treat Treat Treat Control Control Control mat rownames `Pre' = `lfvarlist' matlist `Pre', tw(12) lines(eq) showcoleq(c) format(%9.4g) } restore /* return results when convergence is not achieved */ ereturn post, esample(`touse') ereturn scalar convg = res_converge ereturn scalar maxdiff = res_maxdiff ereturn matrix preBal = `Pre' ereturn local cmdline `"`0'"' ereturn local title "Entropy Balancing" ereturn local cmd "ebalance" exit } else restore, not /* after convergence is reached, cancel the previous "preserve" */ label variable `generate' "entropy balancing weights" /* weighting for the treated */ if ("`wttreat'"=="") { qui: replace `generate'= 1 if `D'==1 & `touse'==1 } else { qui: replace `generate'= `baseweight' if `D'==1 & `touse'==1 } /* nomalization adjustment */ if ("`manualtargets'"!="") { qui: drop in -1 /* drop artifical treated unit */ qui: sum `generate' if `touse'==1 qui: replace `generate'= `generate'*`normconst'*`r(N)'/`r(sum)' if `touse'==1 } else { qui: sum `generate' if `D'==1 & `touse'==1 loc trwt = `r(sum)' qui: sum `generate' if `D'==0 & `touse'==1 loc cowt = `r(sum)' qui: replace `generate'= `generate'*`normconst'*`trwt'/`cowt' if `D'==0 & `touse'==1 } /* ****************** Organize output ********************* */ /* show results */ if ("`manualtargets'"!="") { di _newline(1) qui: sum `generate' if `D'==0 & `touse'==1 di as res "No. of units adjusted: " as txt "`r(N)' " as res "total of weights: " as txt round(`r(sum)') di _newline(1) if "`basewt'"=="" di as res "Before: " as txt "without weighting" else di as res "Before: " as txt "`basewt' as the weighting variable" qui: tabstat `fvarlist' [aweight=`baseweight'] if `touse' == 1, c(s) s(mean variance skewness) longstub save tempname Pre Post mat `Pre' = r(StatTotal)' mat rownames `Pre' = `lfvarlist' matlist `Pre', tw(12) format(%9.4g) di _newline(1) di as res "After: " as txt "`generate' as the weighting variable" qui: tabstat `fvarlist' [aweight=`generate'] if `touse' == 1, c(s) s(mean variance skewness) longstub save mat `Post' = r(StatTotal)' mat rownames `Post' = `lfvarlist' matlist `Post', tw(12) format(%9.4g) } else { di _newline(1) qui: sum `generate' if `D'==1 & `touse'==1 di as res "Treated units: " as txt "`r(N)'" _column(24) as res "total of weights: " as txt round(`r(sum)') qui: sum `generate' if `D'==0 & `touse'==1 di as res "Control units: " as txt "`r(N)'" _column(24) as res "total of weights: " as txt round(`r(sum)') di _newline(1) if "`basewt'"=="" di as res "Before: " as txt "without weighting" else di as res "Before: " as txt "`basewt' as the weighting variable" qui: tabstat `fvarlist' [aweight=`baseweight'] if `touse' == 1, c(s) by(`D') s(mean variance skewness) nototal longstub save tempname Pre Post mat `Pre' = r(Stat2)', r(Stat1)' mat colnames `Pre' = mean variance skewness mean variance skewness mat coleq `Pre' = Treat Treat Treat Control Control Control mat rownames `Pre' = `lfvarlist' matlist `Pre', tw(12) lines(eq) showcoleq(c) format(%9.4g) di _newline(1) di as res "After: " as txt "`generate' as the weighting variable" qui: tabstat `fvarlist' [aweight=`generate'] if `touse' == 1, c(s) by(`D') s(mean variance skewness) nototal longstub save mat `Post' = r(Stat2)', r(Stat1)' mat colnames `Post' = mean variance skewness mean variance skewness mat coleq `Post' = Treat Treat Treat Control Control Control mat rownames `Post' = `lfvarlist' matlist `Post', tw(12) lines(eq) showcoleq(c) format(%9.4g) } /* ****************** Save Balance Table ********************* */ if ("`keep'" != "") { preserve cap drop Xname mean_Tr var_Tr skew_Tr mean_Co_Pre var_Co_Pre skew_Co_Pre mean_Co_Post var_Co_Post skew_Co_Post qui: gen Xname="" local tempvarlist `lfvarlist' local i=1 while (`i'<=`xnum1') { gettoken xx tempvarlist: tempvarlist qui: replace Xname = "`xx'" in `i' local ++i } if ("`manualtargets'"!="") { // treated and reweighted control mat colnames `Pre' = mean_Pre var_Pre skew_Pre mat colnames `Post' = mean_Post var_Post skew_Post svmat double `Pre' , names(col) svmat double `Post', names(col) order Xname mean_Pre mean_Post var_Pre var_Post skew_Pre skew_Post qui keep Xname mean_Pre var_Pre skew_Pre mean_Post var_Post skew_Post label variable Xname "covariate names" label variable mean_Pre "mean in raw data" label variable var_Pre "variance in raw data" label variable skew_Pre "skewness in raw data" label variable mean_Post "mean in reweighted data" label variable var_Post "variance in reweighted data" label variable skew_Post "skewness in reweighted data" } else { tempname Post2 // reweighted control only mat `Post2' = `Post'[1..., 4..6] mat colnames `Pre' = mean_Tr var_Tr skew_Tr mean_Co_Pre var_Co_Pre skew_Co_Pre mat colnames `Post2' = mean_Co_Post var_Co_Post skew_Co_Post svmat double `Pre', names(col) svmat double `Post2', names(col) order Xname mean_Tr mean_Co_Pre mean_Co_Post var_Tr var_Co_Pre var_Co_Post qui keep Xname mean_Tr var_Tr skew_Tr mean_Co_Pre var_Co_Pre skew_Co_Pre mean_Co_Post var_Co_Post skew_Co_Post qui: gen sdiff_Pre = (mean_Tr - mean_Co_Pre) / (var_Tr^0.5) qui: gen sdiff_Post = (mean_Tr - mean_Co_Post)/ (var_Tr^0.5) label variable Xname "covariate names" label variable mean_Tr "mean in treated units" label variable var_Tr "variance in treated units" label variable skew_Tr "skewness in treated units" label variable mean_Co_Pre "mean in raw control units" label variable var_Co_Pre "variance in raw control units" label variable skew_Co_Pre "skewness in raw control units" label variable mean_Co_Post "mean in reweighted control units" label variable var_Co_Post "variance in reweighted control units" label variable skew_Co_Post "skewness in reweighted control units" label variable sdiff_Pre "std diff btw treated and raw control group" label variable sdiff_Post "std diff btw treated and reweighted control group" } qui drop if Xname =="" label data "" if ("`replace'" != "") qui save `keep', `replace' else qui save `keep' restore } /* ****************** Return Results ********************* */ /* delete the first items of the two vector*/ mat res_moments = res_moments[2...,1...] mat res_lambdas = res_lambdas[2...,1...] /* adjust row/col names*/ * fill orders if higher moments are specified if (`xnum2'~=0) { forvalues i=1(1)3 { mata: st_matrix("order`i'", J(`xnum`i'',1,`i')) } mat orders = (order1 \ order2) if (`xnum3'~=0) { mat orders = (order1 \ order2 \ order3) } mat res_moments = (orders, res_moments) mat res_lambdas = (orders, res_lambdas) } if (`xnum2'~=0) { mat colnames res_moments = order mconstrnt mat colnames res_lambdas = order lambda } else { mat colnames res_moments = mconstrnt mat colnames res_lambdas = lambda } * rownames mat rownames res_moments = `constrts' mat rownames res_lambdas = `constrts' /* return results*/ ereturn post, esample(`touse') ereturn scalar convg = res_converge ereturn scalar maxdiff = res_maxdiff ereturn matrix postBal = `Post' ereturn matrix preBal = `Pre' ereturn matrix moments res_moments ereturn matrix lambdas res_lambdas ereturn local cmdline `"`0'"' ereturn local title "Entropy Balancing" ereturn local cmd "ebalance" end /* *************************** Mata **************************** */ mata: mata clear /* line search */ void linesearch(todo, ss, Q, cX, M, coefs, nstep, maxdiff, g, H) { pragma unused todo pragma unused g pragma unused H real matrix wr real matrix weightsebal real matrix xxs wr = exp(cX*(coefs-(ss:*nstep)')') weightsebal = wr:*Q xxs = weightsebal'*cX maxdiff = max(abs(xxs-M')) } /* entropy balancing */ void eb(string Tr, string Co, string matrix X, string Baseweight, string Newvar, real Numiter, real Tol) { real matrix coX real matrix trX real matrix baseweight real matrix treatweight real vector moment real matrix coefs // coefficients (lambdas) real scalar rr real scalar cc /* to suppress misleading warnings */ pragma unset coX pragma unset trX pragma unset baseweight pragma unset treatweight pragma unset rr pragma unset cc /* load in data matrix */ st_view(baseweight ,.,Baseweight,Co) st_view(treatweight,.,Baseweight,Tr) // base weight for the treated st_view(trX,.,X,Tr) // take from X, treated moment = quadcolsum(treatweight:*trX) moment = moment:/quadcolsum(treatweight) // column "average" of trX moment = 1, moment moment = moment' moment_mag = editmissing(exp(trunc(log10(abs(moment)))*log(10)),1) //normalized order of magnitude of "moment"; ********** moment = moment:/moment_mag st_view(coX,.,X,Co) // take from X, controls coX = J(rows(coX),1,1), coX // add a vector of (1, 1, .. , 1)' coX = (coX' :/ moment_mag)' //normalized control units by order of magnitude of "moment" /* coefs: start from (0, 0, 0, ...) */ coefs = J(1,cols(coX),0) /* Start algo here */ iter = 1 maxdiff = Tol + 1 /* maxdiff */ while (maxdiff > Tol & iter<=Numiter) { wr = exp(coX*coefs') weightsebal = wr:*baseweight xxs = weightsebal'*coX gradient = xxs - moment' /* gradient */ hessian = coX'*(weightsebal:*coX) /* hessian */ Coefs = coefs nstep = cholsolve(hessian,gradient',1e-8) if (nstep[1]==.) nstep = svsolve(hessian,gradient') coefs = coefs - nstep' /* now check if we should take the full newton step or not */ S = optimize_init() optimize_init_evaluator(S, &linesearch()) optimize_init_argument(S, 1, baseweight) optimize_init_argument(S, 2, coX) optimize_init_argument(S, 3, moment) optimize_init_argument(S, 4, coefs) optimize_init_argument(S, 5, nstep) optimize_init_params(S, 0) optimize_init_conv_warning(S, "on") (void) optimize_evaluate(S) lossnew = optimize_result_value(S) optimize_init_argument(S, 4, Coefs) optimize_init_params(S, 0) (void) optimize_evaluate(S) lossold = optimize_result_value(S) /* if not the full newton then do a line search */ zerostep = 0 // indicator for line search failure if (lossold <= lossnew) { n = 5 /* this sets the number of values searched: should do golden section here but Stata does not have it */ Store = J(n,2,.) for(i=1; i<=n; i++) { optimize_init_params(S, 1/i) (void) optimize_evaluate(S) Store[i,1] = optimize_result_value(S) Store[i,2] = optimize_result_params(S) // stores optimizing value and resulting parameters } minindex(Store[.,1],1,rr,cc) ss = Store[rr[1],2] zerostep = 0 if (ss==0) zerostep = 1 coefs = Coefs - (ss:*nstep)' } maxdiff = max(abs(gradient))/moment[1] // rescale according to weight total of the treated maxdist = maxw = 0 maxindex(abs(gradient),1,maxdist,maxw) //store the most demanding variable in maxdist printf("{txt}Iteration %f: Max Difference = {res}%g", iter, maxdiff) printf("{txt}\n") if (zerostep ==1) { /* printf("{txt} (line search fails)\n") */ break } else { iter = iter + 1 } } /* close while loop */ if (maxdiff<=Tol) { printf("{txt}maximum difference smaller than the tolerance level; {res}convergence achieved\n") converge = 1 } else { if (zerostep==0) printf("{err}algorithm does not converge within specified tolerance; try increasing maxiter(#) or relaxing tolerance\n") else printf("{err}algorithm does not converge within specified tolerance; no solution AND/OR near colinearity of covariates (and possibly their higher orders)\n") converge = 0 } moment = moment :* moment_mag //adjust back order of magnitude /* save results */ st_numscalar("res_converge", converge) st_numscalar("res_maxdiff", maxdiff) st_numscalar("res_maxdist", maxdist[1]-1) //Most demanding constrait; note: the first one is about the the sum of weights st_matrix("res_lambdas", coefs') st_matrix("res_moments", moment) /* save eweights in a new variable */ (void) st_addvar("double", Newvar) st_store(., Newvar, Co, weightsebal) } /* close function */ end