*! parallelise simulate2 *! Version 1.08 - 15.08.2023 *! by Jan Ditzen - www.jan.ditzen.net /* changelog To version 1.01 - 21.10.2019: - bug fixes in program to get exe name - no batch file written anymore; support for MacOS - added options nocls and processors to set max processors for Stata MP - version 15 and simulate support - 17.01.2020 - added seedstream option - 21.01.2020 - fix if used in loops; added global to save seed ($psim2Seed) To version 1.02 - 30.01.2020 - added mata matrices copied over To version 1.03 - 27.02.2020 - improved behaviour for long lines in do files or programs - warning message if no seed set To version 1.04 - 07.10.2020 - added "/" to temppath locals - 03.02.2021 - bug fixed if data appended to frame but frame does not exists To version 1.05 - 02.08.2021 - bug fix in exepath To version 1.06 - 21.01.2022 - bug fix in temppath To version 1.07 - 24.01.2022 - bug fix if program has more than 250 characters To version 1.08 - 18.07.2023 - added option onlydots: display only dots for parallel simulate. better for servers - 31.07.2023 - added options docmd() and globalid() */ program psimulate2 , rclass version 15 _on_colon_parse `0' local after `"`s(after)'"' local 0 `"`s(before)'"' ** parse only part before : , rest doesnt matter syntax [anything(name=exp_list equalok)] /// [fw iw pw aw] [if] [in] [, /// Reps(integer -1) /// SAving(string) /// DOUBle /// not documented noLegend /// Verbose /// SEED(string) /// not documented TRace /// "prefix" options /// simulate2 options SEEDSave(string) /// Parallel(string) /// parallel options NOCls /// do not refresh windows simulate /// use simulate rather than simulate2 seedstream(integer 0) /// first seedstream ONLYDOTS /// show only dots docmd(string) /// alternative command line to call do files globalid(string) /// set id if psimulate run in parallel; default is empty ] local 0 `parallel' syntax [anything(name = instance)] , [ /// exe(string) /// temppath(string) /// PROCessors(integer 1) /// ] if "`instance'" == "" { local instance = 1 } if `seedstream' > 0 { local seedstream = `seedstream' - 1 } if "`temppath'" == "" { local temppath `"`c(tmpdir)'"' } if "`nocls'`onlydots'" == "" { local cls cls } if "`c(mode)'" == "batch" { ** If run in batch mode, do not use cls local cls "" } if "`docmd'" == "" local docmd "do" *** which simulate is used if "`simulate'" == "" { ** make sure even if simulate not used but version < 16, use simulate if `c(stata_version)' < 16 { local simulate simulate local whichsim "simulate" } else { local whichsim "simulate2" *** re-set to version 16 version 16 } } else { local whichsim "simulate" } global psim2_id `globalid' local psim2_id $psim2_id *** remove p2sim files from folder local files: dir `"`temppath'"' files "psim2`psim2_id'_*" foreach file in `files' { erase "`temppath'/`file'" } cap erase "`temppath'/lpsim2`psim2_id'_matafunc.mlib" *** Get exe path if "`exe'" == "" { psim2_getExePath local exepath `"`r(exepath)'"' } else { local exepath "`exe'" } *** Copy mata matrices local matamatsave = 0 cap erase "`temppath'/psim2`psim2_id'_matamat.mmat" *** Process seed options *** 1. neither frame nor dta used, then seed(seed rng seedstream) *** 2. frame used -> save as dta; if rng and stream empty, use mt64s and instance *** 3. dta used -> load as dta; if rng and stream empty, use mt64s and instance *** 4. no seed set; set seed to Stata default seed, rng to mt64s and stream instance local seednote = 0 local resetseed = 0 if "`whichsim'" == "simulate2" { local instseed = `inst' + `seedstream' if "`seed'" == "" { *** case 4. local seednote = 1 local seed `". mt64s \`instseed'"' } else { local 0 `seed' syntax anything , [frame dta start(integer 1)] tokenize `anything' if "`1'" == "." | "`1'" == "_current" { if "`1'" == "_current" { local resetseed = 1 } if "$psim2Seed" != "" { local 1 "$psim2Seed" } else { local 1 `c(rngstate)' local 2 `c(rng_current)' } *local 3 `c(rngstream)' } if wordcount("`frame' `dta'") == 2 { di as err "options frame and dta cannot be combined" exit 184 } local startseednum = `start' if "`frame'`dta'" == "" { if "`2'" == "" { local 2 "mt64s" } if "`3'" == "" { local 3 `"\`instseed'"' } local seed `"`1' `2' \`3'"' } else { tokenize `anything' if "`frame'" != "" { frame `1': save "`temppath'/psim2`psim2_id'_seed", replace local seed `"`temppath'/psim2`psim2_id'_seed `2' `3' `4' , dta"' local seedstartop `"start(\`=\`repscum'+\`startseednum'-1')"' } else if "`dta'" != "" { local seed `"`1' `2' `3' `4' , dta"' local seedstartop `"start(\`=\`repscum'+\`startseednum'-1')"' } } } if "`seedsave'" != "" { local 0 `seedsave' syntax anything , [frame seednumber(integer 1) append] local sseeddest `"`anything'"' local sseedframe "`frame'" local sseedappend "`append'" local seedsave `"`temppath'/psim2`psim2_id'_seedsave_\`inst' , seednumber(\`repscum')"' } } else { local seedsave "" local seed seed(`seed') } *** remove last saved seed from global if "$psim2Seed" != "" { macro drop psim2Seed } *** save dta save "`temppath'/psim2`psim2_id'_start", replace emptyok *** Number of replications for each local repsavg = floor(`reps'/`instance') local repscum = 1 *** check if repsavg > 0 if `repsavg' == 0 { while `repsavg' == 0 { local instance = `instance'-1 local repsavg = floor(`reps'/`instance') } } *** correct here commandline forvalues inst = 1(1)`instance' { local repsi = `repsavg' + (`inst'==1) * (`reps' - `repsavg'*`instance') local instseed = `inst' + `seedstream' if "`whichsim'" == "simulate2" { psim2_WriteDofile `exp_list' , /// /// sim2 options saving("`temppath'/psim2`psim2_id'_results_`inst'", replace) reps(`repsi') /// perindicator(100, perindicpath(`"`temppath'"') performid(`inst')) /// seed("`seed'" "`seedstartop'") seedsave("`seedsave'") seedstream(`seedstream') /// /// writeBatch options id(`inst') processors(`processors') /// startdta(psim2`psim2_id'_start) temppath(`temppath') /// : `after' } else { psim2_WriteDofile `exp_list' , /// /// sim options saving("`temppath'/psim2`psim2_id'_results_`inst'", replace) reps(`repsi') /// `seed' /// /// writeBatch options id(`inst') processors(`processors') simulate /// startdta(psim2`psim2_id'_start) temppath(`temppath') /// : `after' } local repscum = `repscum'+`repsi' } if "`c(os)'" == "Windows" { local winexec_e "/e" } else if "`c(os)'" == "MacOSX" { local winexec_e "-e" } else if "`c(os)'" == "Unix" { local winexec_e "" } local starttime = clock(c(current_time),"hms") if "`c(console)'" == "" { forvalues inst = 1(1)`instance' { * noi disp `"command line to execute: winexec `exepath' `winexec_e' do "`temppath'psim2_DoFile_`inst'.do" "' * local lastcmd `"winexec `exepath' `winexec_e' do "`temppath'psim2_DoFile_`inst'.do" "' winexec `exepath' `winexec_e' `docmd' "`temppath'/psim2`psim2_id'_DoFile_`inst'.do" } } else { forvalues inst = 1(1)`instance' { local line "`line' (`exepath' `docmd' "`temppath'/psim2`psim2_id'_DoFile_`inst'.do" &)" if `inst' != `instance' { local line "`line' ; " } } noi disp "Starting `instance' Instances. No output available, please wait." qui shell `line' } **** Output local sleeptime = 1000 noi disp "Initalising...." local reps_done = 0 while `reps' > `reps_done' { ** Reset reps done local reps_done = 0 sleep `sleeptime' forvalues inst = 1(1)`instance' { if fileexists("`temppath'/psim2`psim2_id'_performance_`inst'.mmat") == 1 { cap qui mata mata matuse "`temppath'/psim2`psim2_id'_performance_`inst'", replace if _rc != 0 { ** build in artifical sleep noi disp "error in saving psim2`psim2_id'_performance_`inst'" sleep 1000 cap qui mata mata matuse "`temppath'/psim2`psim2_id'_performance_`inst'", replace } qui mata st_local("done_`inst'",strofreal(p2sim_performance[1,1])) qui mata st_local("reps_`inst'",strofreal(p2sim_performance[1,2])) ** if simulate is used -999 is returned when done if `reps_`inst'' == -999 { local reps_`inst' = `reps' / `instance' local done_`inst' = `reps_`inst'' } } else { local reps_`inst' = `reps' local done_`inst' = 0 } local reps_done = `reps_done' + `done_`inst'' } *** all times in ms sec. local nowtime = clock(c(current_time),"hms") local avg_run = (`nowtime' - `starttime') /`reps_done' if `avg_run' == . { local avg_run = 0 } ** time in minutes local time_elapsed = (`nowtime' - `starttime') *local exp_time_left = (`reps'-`reps_done')*`avg_run'/`instance' local exp_time_left = `time_elapsed' / `reps_done' * `reps' *** in hour format local exp_finish_time = `nowtime'+`exp_time_left' `cls' if "`onlydots'" == "" { noi disp as text "" noi disp "psimulate2 - parallelise `whichsim'" noi disp as text "" local aftertt = strtrim(`"`after'"'') noi disp as text `"command: `aftertt' "' noi disp as text "" noi disp as text "Timings (hour, minute, sec):" _col(40) "Estimated:" noi disp as text " Average Run: " _col(24) %tcHH:MM:SS.sss `avg_run' _col(40) " Time left (min):" _col(60) %tcHH:MM:SS `exp_time_left' noi disp as text " Time Elapsed:" _col(24) %tcHH:MM:SS `time_elapsed' _col(40) " finishing time:" _col(60) %tcHH:MM:SS `exp_finish_time' noi disp "" if "`whichsim'" == "simulate2" { forvalues inst = 1(1)`instance' { noi disp as text "Instance `inst':" noi disp as text " Done " %9.2f `=`done_`inst''/`reps_`inst''*100' "%" _col(20) "(`done_`inst''/`reps_`inst'')" } noi disp as text "Total" noi disp as text " Done " %9.2f `=`reps_done'/`reps'*100' "%" _col(20) "(`reps_done'/`reps')" } else { noi disp as text " simulate does not allow for process indication. Please wait." } local sleeptime = `avg_run' * `reps' / 100 } else { noi disp "." , _c } *** wait at least 0.25 sec if `sleeptime' < 250 { local sleeptime = 250 } else if `sleeptime' > 60000 { local sleeptime = 59999 } if "`onlydots'" == "" { noi disp "" noi disp as text "Current Time: `c(current_time)' - next refresh in " %tcSS.ss `sleeptime' " sec." if `seednote' == 1 { noi disp as text "No seed set. If psimulate is used in a loop, " noi disp as text "all iterations of the loop will have the Stata default seed." } } sleep `sleeptime' } noi disp "" noi disp "Click on link to open log file: " di as text "Log files " forvalues inst = 1(1)`instance' { if fileexists("psim2`psim2_id'_DoFile_`inst'.log") == 1 { disp as smcl _col(5) "Instance `inst':" _col(20) `"{view psim2_DoFile_`inst'.log: Log File}"' } } *** Save seeds qui { if "`sseeddest'" != "" { clear use "`temppath'/psim2`psim2_id'_seedsave_1" forvalues inst = 2(1)`instance' { append using "`temppath'/psim2`psim2_id'_seedsave_`inst'", force } if "`sseedframe'" == "" { if "`sseedappend'" != "" { append using "`sseeddest'", force } save "`sseeddest'" , replace } else { if "`sseedappend'" != "" { frame `sseeddest': save "`temppath'/psim2`psim2_id'_oldseed", replace append using "`temppath'/psim2`psim2_id'_oldseed" } cap frame drop `sseeddest' frame copy `c(frame)' `sseeddest' } } *** Collect data clear use "`temppath'/psim2`psim2_id'_results_1" forvalues inst = 2(1)`instance' { qui append using "`temppath'/psim2`psim2_id'_results_`inst'", force } if "`saving'" != "" { local 0 `"`saving'"' syntax anything [, frame replace append] if "`frame'" != "" { if "`append'" != "" { *** check if frame exists frame dir if regexm("`r(frames)'","`anything'") == 0 { frame create `anything' } frame `anything': save "`temppath'/psim2`psim2_id'_oldframe", replace emptyok append using "`temppath'/psim2`psim2_id'_oldframe" } cap frame drop `anything' frame copy `c(frame)' `anything' } else { if "`append'" != "" { append using `"`anything'"' } save `"`anything'"', replace } } mata st_local("last_seed1",p2sim_lastseed[1]) return local rngstate "`last_seed1'" mata st_local("last_seed",p2sim_lastseed[2]) return local rngseed_mt64s "`last_seed'" mata st_local("p2sim_lastrng",p2sim_lastrng) return local rng_current "`p2sim_lastrng'" if `resetseed' == 1 { global psim2Seed = "`last_seed1'" } } end cap program drop psim2_WriteDofile program define psim2_WriteDofile _on_colon_parse `0' local after `"`s(after)'"' local 0 `"`s(before)'"' local psim2_id $psim2_id syntax [anything(name=explist equalok)], id(string) startdta(string) temppath(string) processors(integer) seedstream(integer) [simulate] * local sim2_options `options' local path `"`temppath'"' local doFileName "psim2`psim2_id'_DoFile_`id'.do" tempname dofile **** Create do file with programs psim2_programlist , temppath("`temppath'") local pnames "`r(pnames)'" noi disp "pnames: `pnames'" **** Write do file file open `dofile' using `"`path'/`doFileName'"' , w replace *** Add Time Stamp file write `dofile' `"disp "This is psmulate2""' _n file write `dofile' `"disp "File was written `c(current_time)'""' _n if "`pnames'" != "" { file write `dofile' `"include `"`temppath'/psim2`psim2_id'_programs.do"'"' _n } **** If Stata MP, use only one core: if `c(MP)' == 1 { file write `dofile' `"set processors `processors'"' _n } **** Add ados and globals **** ado tokenize `"$S_ADO"' , p(";") while "`1'" != "" { if "`1'" != "BASE" & "`1'" != "SITE" & "`1'" != "." & "`1'" != "BASE" & "`1'" != "PERSONAL" & "`1'" != "PLUS" & "`1'" != "OLDPLACE" & "`1'" != ";" { file write `dofile' `"adopath + "`1'""' _n } macro shift } **** globals local allGlobals : all globals tokenize `"`allGlobals'"' while "`1'" != "" { if "`1'" != "S_ADO" & "`1'" != "S_level" & "`1'" != "F1" & "`1'" != "F2" & "`1'" != "F7" & "`1'" != "F8" & "`1'" != "S_StataSE" & "`1'" != "S_FLAVOR" & "`1'" != "S_OS" & "`1'" != "S_OSDTL" & "`1'" != "S_MACH" { if strtoname("`1'") == "`1'" { file write `dofile' `"global `1' ="$`1'""' _n } } macro shift } **** Mata programs; easy way, check if mata programs exist, if so, add then to libary and **** set new ado path to library in do file mata mata memory if `r(Nf_def)' > 0 { cap lmbuild lpsim2`psim2_id'_matafunc , dir(`temppath') replace if _rc != 0 { sleep 200 cap lmbuild lpsim2`psim2_id'_matafunc , dir(`temppath') replace } file write `dofile' `"adopath + "`temppath'""' _n file write `dofile' `"mata mata mlib index"' _n } **** Mata programs mata mata memory if `r(Nm)' > 0 { mata mata matsave "`temppath'/psim2`psim2_id'_matamat.mmat" *, replace file write `dofile' `"mata mata matuse "`temppath'/psim2`psim2_id'_matamat.mmat", replace"' _n } /* **** do same for matrices local allMatrices: all matrices tokenize `"`allMatrices'"' while "`1'" != "" { file write `dofile' `"matrix `1' =``'1'"' _n macro shift } **** all scalars local allScalar: all scalars tokenize `"`allScalar'"' while "`1'" != "" { file write `dofile' `"scalar `1' ="`1'""' _n macro shift } */ **** Open Dataset file write `dofile' `"use "`temppath'/`startdta'""' _n **** Do cmd if "`simulate'" == "" { file write `dofile' `"simulate2 `explist' , inpsim2 psim2_id(`psim2_id') `options' : `after'"' _n } else { file write `dofile' `"set rng mt64s"' _n file write `dofile' `"set rngstream `=`id'+`seedstream''"' _n file write `dofile' `"simulate `explist' , `options' : `after'"' _n file write `dofile' `"mata p2sim_performance = -999, -999 "' _n file write `dofile' `"mata p2sim_lastrng = "\`c(rng_current)'""' _n file write `dofile' `"mata p2sim_lastseed = "\`c(rngstate)'" , "\`c(rngseed_mt64s)'""' _n file write `dofile' `"cap mata mata matsave "`temppath'/psim2`psim2_id'_performance_`id'" p2sim_performance p2sim_lastseed p2sim_lastrng , replace "' _n } **** Close do file file close `dofile' end cap program drop psim2_getExePath program define psim2_getExePath, rclass local datafiles: dir "`c(sysdir_stata)'" files "*.exe" if wordcount(`"`datafiles'"') == 1 { local datafiles = subinstr(`"`datafiles'"',`"""',"",.) local exepath `c(sysdir_stata)'`datafiles' local exename `"`datafiles)'"' } else { if "`c(os)'" == "Windows" { if `c(SE)' == 1 & `c(MP)' == 0 { dlocal type SE } else if `c(MP)' == 1 { local type MP } else { local type IC } local exepath "`c(sysdir_stata)'Stata`type'-`c(bit)'.exe" local exename "Stata`type'-`c(bit)'.exe" } else if "`c(os)'" == "MacOSX" { if `c(SE)' == 1 & `c(MP)' == 0 { local type "SE" local exepath /usr/local/bin/stata-se } else if `c(MP)' == 1 { local type "MP" loc exepath /usr/local/bin/stata-mp } else { local type "IC" loc exepath /usr/local/bin/stata } } else if "`c(os)'" == "Unix" { local w = c(sysdir_stata) if `c(SE)' == 1 & `c(MP)' == 0 { local type "SE" loc exepath `w'stata-se } else if `c(MP)' == 1 { local type "MP" loc exepath `w'stata-mp } } if fileexists("`exepath'") == 0 { display as smcl "No Stata exe found. Please set a path using {help psimulate2##options:exepath()}." exit } } return local exepath `"`exepath'"' return local exename "`exename'" end program define psim2_programlist, rclass syntax [anything] , temppath(string) local psim2_id $psim2_id log local logname "`r(filename)'" cap log close local linesize `c(linesize)' set linesize 250 log using "`temppath'/psim2`psim2_id'_plog", replace text nomsg program dir log close set linesize `linesize' tempname file nextline file open `file' using `"`temppath'/psim2`psim2_id'_plog.log"' , read file read `file' line while r(eof)==0 { local line `" `macval(line)'"' local count = wordcount("`line'") tokenize `line' capture confirm number `2' if `count' == 2 & _rc != 0 { local pnames "`pnames' `2'" } file read `file' line } file close `file' **** write a new do file with contents of each program if "`pnames'" != "" { *local appendreplace "replace" tempname dofilenew file open `dofilenew' using "`temppath'/psim2`psim2_id'_programs.do" , write text replace foreach prog in `pnames' { local linesize `c(linesize)' set linesize 250 log using "`temppath'/psim2`psim2_id'_tmp_program.log" , text nomsg replace cap noi program list `prog' log close set linesize `linesize' if _rc == 0 { file open `file' using `"`temppath'/psim2`psim2_id'_tmp_program.log"' , read file read `file' line /// Open it the second time and shift one line down to check if line was cut off file open `nextline' using `"`temppath'/psim2`psim2_id'_tmp_program.log"' , read file read `nextline' next file read `nextline' next local inprog = -1 while r(eof)==0 { gettoken 1 2 : line if "`1'" != "." & "`1'" != "" & "`1'" != "=" & "`1'" != "-" { local first = subinstr("`1'",".","",.) capture confirm number `first' *** line starts with a number, contains code if _rc != 0 & `inprog' == -1 { local line = subinstr("`line'",":","",.) file write `dofilenew' `"program define `line'"' _n local inprog = 1 } else if `inprog' == 1 { local rest = subinstr(`"`macval(line)'"',"`1'","",.) //// check if next line start with ">", then this needs to be added to current line gettoken n1 n2: next if regexm(`"`n1'"',">") == 1 { local rest = strtrim(`"`rest'"') *local n2 = strtrim(`"`n2'"') *local n2 = subinstr(`"`n2'"'," ","",1) local n2 = strltrim(`"`n2'"') file write `dofilenew' `"`macval(rest)'`macval(n2)'"' _n /// now shift both files one line down file read `file' line file read `nextline' next } else { file write `dofilenew' `"`macval(rest)'"' _n } } else { local inprog = 0 } } file read `nextline' next file read `file' line } file close `file' file close `nextline' ** add end file write `dofilenew' `"end"' _n file write `dofilenew' `""' _n } } file close `dofilenew' } if "`logname'" != "" { log using "`logname'", append } return clear return local pnames "`pnames'" end