EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
tutor7pp.cpp

The tutor7pp application is a re-implementation of the original tutor7 mortran application in C++ using EGS_AdvancedApplication. It calculates the deposited, transmitted and reflected energy fractions in addition to the pulse height distribution in requested geometry regions. However, unlike the original tutor7 application, which can only calculate these quantites for a slab geometry for a pencil beam incident at a user defined angle, tutor7pp can perform the simulation in any geometry that can be defined using the egspp geometry package for any particle source that can be defined using the egspp source package. In addition, tutor7pp provides the functionality of being able to restart a calculation, run a calculation in parallel and automatically recombine the results, limit the simulation time to a user defined maximum time, or abort the simulation if a user defined statistical uncertainty has been reached. Despite this added functionality, the tutor7pp source code is much shorter than the original tutor7 implementation in mortran. The reader is encouraged to study the guidelines for developing EGSnrc C++ applications provided in the EGS_Application class documentation before reading this example.

To implement the required functionality we derive an application class from EGS_AdvancedApplication

#include "egs_scoring.h"
#include "egs_interface2.h"
#include "egs_functions.h"
#include "egs_input.h"
#include "egs_rndm.h"
#include <cstdlib>
using namespace std;
class APP_EXPORT Tutor7_Application : public EGS_AdvancedApplication {
The various include files are needed to gain access to the declarations of the classes and functions that will be used. We then declare various private data members:
EGS_ScoringArray *score; // scoring array with energies deposited
EGS_ScoringArray *eflu; // scoring array for electron fluence at back of geometry
EGS_ScoringArray *gflu; // scoring array for photon fluence at back of geometry
EGS_ScoringArray **pheight; // pulse height distributions.
int nreg; // number of regions in the geometry
int nph; // number of pulse height objects.
double Etot; // total energy that has entered the geometry
int rr_flag; // used for RR and radiative splitting
EGS_Float current_weight; // the weight of the initial particle that
// is currently being simulated
bool deflect_brems;
EGS_Float *ph_de; // bin widths if the pulse height distributions.
int *ph_regions; // region indices of the ph-distributions
static string revision; // the CVS revision number
Their function is briefly explained by the comments and will become more clear in what follows. We now need a constructor for our application class that takes the command line arguments as input, passes them to the base class constructor and initializes its data
public:
Tutor7_Application(int argc, char **argv) :
EGS_AdvancedApplication(argc,argv), score(0), eflu(0), gflu(0), pheight(0),
nreg(0), nph(0), Etot(0), rr_flag(0), current_weight(1), deflect_brems(false) { };
The call to the EGS_AdvancedApplication constructor checks the arguments passed on the command line and sets up the name of the application, the input file name, the pegs file name, the output file name, the working directory, batch vs interactive run, parallel job execution (i_parallel and n_parallel) and reads in the input file into an EGS_Input object (a pointer to this object is available with the protected data member EGS_Application::input). To demonstrate good coding habits, we declare a destructor for our application that deallocates the memory used
~Tutor7_Application() {
if (score) {
delete score;
}
if (eflu) {
delete eflu;
}
if (gflu) {
delete gflu;
}
if (nph > 0) {
for (int j=0; j<nph; j++) {
delete pheight[j];
}
delete [] pheight;
delete [] ph_regions;
delete [] ph_de;
}
};
We now declare the various virtual functions that we wish to re-implement: the function to describe our user code,
void describeUserCode() const;
the function to initialize the scoring variables,
int initScoring();
the function for scoring the quantities of interest at run time,
int ausgab(int iarg);
the functions needed for restarting simulations and parallel runs,
int outputData();
int readData();
void resetCounter();
int addState(istream &data);
the function to output the results of the simulation,
void outputResults();
the function that makes the current result known to the run control object,
void getCurrentResult(double &sum, double &sum2, double &norm,
double &count);
and the protected function called before the simulation of a new particle
protected:
int startNewShower();
};
This completes our application class declaration.

We now provide implementations of the above functions. First we have to declare the static data member revision:

string Tutor7_Application::revision = " ";
Note that the revision string was automatically updated when the code was under CVS revision control. Since the system has been moved to GitHub, however, the revision number is now blank, pending the implementation of a sensible/automatic numbering scheme. The describeUserCode() function simply outputs the name of our application and the revision numbers of the base application class and our application class using egsInformation:
extern "C" void F77_OBJ_(egs_scale_xcc,EGS_SCALE_XCC)(const int *,const EGS_Float *);
extern "C" void F77_OBJ_(egs_scale_bc,EGS_SCALE_BC)(const int *,const EGS_Float *);
void Tutor7_Application::describeUserCode() const {
"\n ***************************************************"
"\n * *"
"\n * tutor7pp *"
"\n * *"
"\n ***************************************************"
"\n\n");
egsInformation("This is Tutor7_Application %s based on\n"
" EGS_AdvancedApplication %s\n\n",
egsSimplifyCVSKey(revision).c_str(),
egsSimplifyCVSKey(base_revision).c_str());
}
This is useful for determining which version was used to obtain a given result from the logfile produced by the application. The egsSimplifyCVSKey function used above removes the $'s from CVS keys.

The next step is to initialize the scoring variables in initScoring(). We will need a scoring array object to collect energy depositions in all regions of the geometry and the outside region (divided into energy transmitted, defined as the energy of particles exiting the geometry and moving forward, and energy reflected, defined as the energy of particles exiting the geometry and moving backwards) from which we can calculate energy deposition fractions. For this purpose we obtain the number of regions in the geometry

int Tutor7_Application::initScoring() {
nreg = geometry->regions();
and initialize a scoring array with nreg+2 regions pointed to by our class variable score:
score = new EGS_ScoringArray(nreg+2);
We want the user to be able to specify the geometry regions for which to calculate a pulse height distribution (PHD) by including

:start scoring options:
    pulse height regions = 1 5 77
    pulse height bins = 125 50 500
:stop scoring options:

in their input file, with the pulse height regions key specifying all regions for which a PHD calculation is requested and the pulse height bins key the number of bins to use for each PHD region (obviously, the number of integer inputs to these two keys must be the same). To implement this functionality, we interogate the input object , which contains all input found in the input file, for a composite key named scoring options

EGS_Input *options = input->takeInputItem("scoring options");
and if such property exists (i.e. options is not null), check for the pulse height regions and pulse height bins keys:
vector<string> tmp;
int err = scale->getInput("scale xcc",tmp);
//egsInformation("Found 'scale xcc', err=%d tmp.size()=%d\n",err,tmp.size());
if (!err && tmp.size() == 2) {
int imed = EGS_BaseGeometry::getMediumIndex(tmp[0]) + 1;
if (imed > 0) {
EGS_Float fac = atof(tmp[1].c_str());
egsInformation("\n ***** Scaling xcc of medium %d with %g\n",imed,fac);
F77_OBJ_(egs_scale_xcc,EGS_SCALE_XCC)(&imed,&fac);
}
}
delete scale;
}
while ((scale = options->takeInputItem("scale bc"))) {
vector<string> tmp;
int err = scale->getInput("scale bc",tmp);
//egsInformation("Found 'scale xcc', err=%d tmp.size()=%d\n",err,tmp.size());
if (!err && tmp.size() == 2) {
int imed = EGS_BaseGeometry::getMediumIndex(tmp[0]) + 1;
if (imed > 0) {
EGS_Float fac = atof(tmp[1].c_str());
egsInformation("\n ***** Scaling bc of medium %d with %g\n",imed,fac);
F77_OBJ_(egs_scale_bc,EGS_SCALE_BC)(&imed,&fac);
}
}
delete scale;
}
vector<string> choices;
choices.push_back("no");
choices.push_back("yes");
deflect_brems = options->getInput("deflect electron after brems",choices,0);
if (deflect_brems) {
egsInformation("\n *** Using electron deflection in brems events\n\n");
setAusgabCall(AfterBrems,true);
}
int n_rr;
if (!options->getInput("Russian Roulette",n_rr) && n_rr > 1) {
the_egsvr->i_do_rr = n_rr;
setAusgabCall(BeforeBrems,true);
setAusgabCall(AfterBrems,true);
setAusgabCall(BeforeAnnihFlight,true);
setAusgabCall(AfterAnnihFlight,true);
setAusgabCall(BeforeAnnihRest,true);
setAusgabCall(AfterAnnihRest,true);
//setAusgabCall(FluorescentEvent,true);
egsInformation("\nUsing Russian Roulette with survival probability 1/%d\n",n_rr);
}
// The user has provided scoring options input.
// See where she/he wants to score a pulse height distribution
// and how many bins to use for each pulse height distribution
vector<int> regions;
int err = options->getInput("pulse height regions",regions);
vector<int> nbins;
int err1 = options->getInput("pulse height bins",nbins);
If the keys exist (i.e. err and err1 are zero), we check that the number of inputs is the same, or, alternatively there is only a single input to the pulse height bins key (in this case we use the same number of bins for all PHDs), and if this is not satisfied issue a warning so that the user is made aware of a potential mistake in the input file,
if (!err && !err1) {
if (regions.size() != nbins.size() && nbins.size() != 1)
egsWarning("initScoring(): you must input the same "
"number of 'regions' and 'bins' inputs or a single 'bins'"
" input\n");
otherwise we have to initialize scoring arrays for the requested PHDs. We first alocate a temporary array of pointers to scoring objects and set all of them to null:
else {
EGS_ScoringArray **tmp = new EGS_ScoringArray* [nreg+2];
for (int i=0; i<nreg+2; i++) {
tmp[i] = 0;
We then loop over the number of PHD regions
}
for (int j=0; j<regions.size(); j++) {
For each requested PHD region we set the number of bins (possibly warning the user, if the number is less than 1),
int nb = nbins.size() == 1 ? nbins[0] : nbins[j];
if (nb < 1) {
egsWarning("zero bins for region %d?\n",regions[j]);
and check that the region index is valid
}
if (regions[j] < -1 || regions[j] > nreg) {
egsWarning("invalid region index %d\n",regions[j]);
If the number of bins is positive and the region index is valid, we check if there is not already a PHD scoring object for this region initilized and warn the user if there is
}
if (nb > 0 && regions[j] >= 0 && regions[j] < nreg+2) {
int ij = regions[j];
if (tmp[ij]) egsInformation("There is already a "
"PHD object in region %d => ignoring it\n",ij);
(perhaps the user has made a mistake and input the same PHD region twice), otherwise we construct a scoring array with nb bins, increment the number of requested PHDs by one and store the pointer to the newly constructed scoring array in tmp:
else {
tmp[ij] = new EGS_ScoringArray(nb);
++nph;
}
}
When the loop over requested PHD regions is completed, nph has the number of valid PHD requests and the elements of tmp that are not null have the pointers to the scorring array objects allocated for the calculation of the PHDs. We then allocate an array of pointers to scorring array objects with the required size,
}
if (nph > 0) {
pheight = new EGS_ScoringArray* [nph];
an array with region indeces for each requested PHD,
ph_regions = new int [nph];
and an array with bin widths for each PHD
ph_de = new EGS_Float [nph];
We then obtain the maximum source energy
EGS_Float Emax = source->getEmax();
and loop over all regions
int iph = 0;
for (int j=0; j<nreg+2; j++) {
If tmp[j] is not null, we set the iph't element of the just allocated arrays to contain the pointer to the scorring array, the region index of the PHD, and the bin width:
if (tmp[j]) {
pheight[iph] = tmp[j];
ph_regions[iph] = j;
int nbin = pheight[iph]->bins();
ph_de[iph++] = Emax/nbin;
}
}
}
We then delete tmp to avoid memory leaks
delete [] tmp;
}
This completes the initilization of PHD scoring. If the user has provided scoring options input but the input is incorrect (indicated by err and/or err1 not being zero), we warn them so that they can check their input file
}
else egsWarning("initScoring(): you must provide both, 'regions'"
" and 'bins' input\n");
We then delete the scoring options input as it is no longer needed
delete options;
}
return 0;
}
(Note: if we were expecting that someone might want to extend the functionality of our tutor7pp application by deriving from the Tutor7_Application class, we should have introduced a protected data member to point to the scroring options property and should not delete it here. In this case we also should have split the class declaration into a separate header file). This completes the initScoring() implementation.

The implementation of our ausgab() function is very simple: for enertgy deposition events (i.e. iarg<=4) we determine the region index of the top particle on the particle stack and score the energy deposited weighted with the statistical weight of the particle into the corresponding scoring region using the score() method of our scoring array object:

int Tutor7_Application::ausgab(int iarg) {
if (iarg <= 4) {
int np = the_stack->np - 1;
// Note: ir is the region number+1
int ir = the_stack->ir[np]-1;
// If the particle is outside the geometry and headed in the positive
// z-direction, change the region to count it as "transmitted"
// Note: This is only valid for certain source/geometry conditions!
// If those conditions are not met, the reflected and transmitted
// energy fractions will be wrong
if (ir == 0 && the_stack->w[np] > 0) {
ir = nreg+1;
}
EGS_Float aux = the_epcont->edep*the_stack->wt[np];
if (aux > 0) {
score->score(ir,aux);
}
The calculation of requested pulse height distributions will be done in the startNewShower() function (see below).

The outputData() function must store the accumulated results to a data stream (click on outputData() below to see the base class documentation of this virtual function). We use the base class implementation to store the data of the base application and return the generated error code, if an error occured:

// if( the_stack->iq[np] ) score->score(ir,the_epcont->edep*the_stack->wt[np]);
if (ir == nreg+1) {
EGS_ScoringArray *flu = the_stack->iq[np] ? eflu : gflu;
EGS_Float r2 = the_stack->x[np]*the_stack->x[np] + the_stack->y[np]*the_stack->y[np];
int bin = (int)(sqrt(r2)*10.);
if (bin < 200) {
aux = the_stack->wt[np]/the_stack->w[np];
if (aux > 0) {
flu->score(bin,aux);
}
}
}
return 0;
}
int np = the_stack->np-1;
if (iarg == BeforeBrems || iarg == BeforeAnnihRest || (iarg == BeforeAnnihFlight &&
the_stack->latch[np] > 0)) {
the_stack->latch[np] = 0;
rr_flag = 1;
return 0;
}
if (iarg == AfterBrems && deflect_brems) {
EGS_Vector u(the_stack->u[np-1],the_stack->v[np-1],the_stack->w[np-1]);
EGS_Float tau = the_stack->E[np-1]/the_useful->rm - 1;
EGS_Float beta = sqrt(tau*(tau+2))/(tau+1);
EGS_Float eta = 2*rndm->getUniform()-1;
EGS_Float cost = (beta + eta)/(1 + beta*eta);
EGS_Float sint = 1 - cost*cost;
if (sint > 0) {
sint = sqrt(sint);
EGS_Float cphi, sphi;
rndm->getAzimuth(cphi,sphi);
u.rotate(cost,sint,cphi,sphi);
the_stack->u[np-1] = u.x;
the_stack->v[np-1] = u.y;
the_stack->w[np-1] = u.z;
}
}
if (iarg == AfterBrems || iarg == AfterAnnihRest || iarg == AfterAnnihFlight) {
if (iarg == AfterBrems && rr_flag) {
}
rr_flag = 0;
return 0;
}
/*
if( iarg == FluorescentEvent && the_stack->latch[np] > 0 ) {
the_stack->latch[np] = 0; the_stack->wt[np] /= the_egsvr->i_do_rr;
if( np+1+the_egsvr->i_do_rr > MXSTACK )
egsFatal("Stack size exceeded while splitting dluorescent photon!\n");
for(int j=1; j<the_egsvr->i_do_rr; j++) {
EGS_Float cost = 2*rndm->getUniform()-1;
EGS_Float sint = 1 - cost*cost; sint = sint > 0 ? sqrt(sint) : 0;
EGS_Float cphi, sphi; rndm->getAzimuth(cphi,sphi);
the_stack->E[np+j] = the_stack->E[np];
the_stack->wt[np+j] = the_stack->wt[np];
the_stack->iq[np+j] = the_stack->iq[np];
the_stack->ir[np+j] = the_stack->ir[np];
the_stack->dnear[np+j] = the_stack->dnear[np];
the_stack->latch[np+j] = the_stack->latch[np];
the_stack->x[np+j] = the_stack->x[np];
the_stack->y[np+j] = the_stack->y[np];
the_stack->z[np+j] = the_stack->z[np];
the_stack->u[np+j] = sint*cphi;
the_stack->v[np+j] = sint*sphi;
the_stack->w[np+j] = cost;
}
}
*/
return 0;
}
int Tutor7_Application::outputData() {
if (err) {
return err;
We then write our own data to the data stream pointed to by data_out: the total energy deposited so far
(*data_out) << " " << Etot << endl;
the energy fractions accumulated in the scorring array
if (!score->storeState(*data_out)) {
and the pulse height distribution results
return 101;
}
for (int j=0; j<nph; j++) {
if (!pheight[j]->storeState(*data_out)) {
return 102+j;
}
Note that data_out is set up to point to the output stream created by opening the .egsdat file for writing in the base application class.

The function directly innverse to outputData() is readData(). The implementation is therefore basically the same as outputData() except that now we read the data from the stream pointed to by data_in:

}
if (!eflu->storeState(*data_out)) {
return 301;
}
if (!gflu->storeState(*data_out)) {
return 302;
}
return 0;
}
int Tutor7_Application::readData() {
if (err) {
return err;
(*data_in) >> Etot;
if (!score->setState(*data_in)) {
return 101;
}
for (int j=0; j<nph; j++) {
if (!pheight[j]->setState(*data_in)) {
return 102+j;
}

The resetCounter() function, which is needed for combining parallel runs and is called before the loop over available data files, must put our application class into a state with zero particles simulated. We therefore use the base class function to reset the data of the base application class,

}
if (!eflu->setState(*data_in)) {
return 301;
}
if (!gflu->setState(*data_in)) {
return 302;
}
return 0;
}
void Tutor7_Application::resetCounter() {
set the total energy deposited to zero and use the reset() functions of the scoring arrays to reset these:
Etot = 0;
for (int j=0; j<nph; j++) {
pheight[j]->reset();
}

The addState() function is used to combine the results of parallel runs. It is therefore similar to the readData() function except that now the data read from the input stream is added to the existing data instead of replacing the state of the application with these data. We therefore use the base class addState() function,

eflu->reset();
gflu->reset();
}
int Tutor7_Application::addState(istream &data) {
if (err) {
return err;
add the total energy deposited into the geometry,
double etot_tmp;
construct a temporary scoring array object with nreg+2 regions,
data >> etot_tmp;
Etot += etot_tmp;
EGS_ScoringArray tmp(nreg+2);
read the data into this object,
if (!tmp.setState(data)) {
and add it to the existing data
return 101;
}
(*score) += tmp;
We do the same for the pulse height distributions:
for (int j=0; j<nph; j++) {
EGS_ScoringArray tmpj(pheight[j]->bins());
if (!tmpj.setState(data)) {
return 102 + j;
}
(*pheight[j]) += tmpj;
}

The outputResults() function must output the results of a simulation into the logfile (or the standard output, if the application is run interactively). In our implementation we first inform the user about the last statistically independent history simulated and the total energy deposited into the geometry:

EGS_ScoringArray tmp1(200);
if (!tmp1.setState(data)) {
return 301;
}
(*eflu) += tmp1;
if (!tmp1.setState(data)) {
return 302;
}
(*gflu) += tmp1;
return 0;
}
void Tutor7_Application::outputResults() {
egsInformation("\n\n last case = %d Etot = %g\n",
(int)current_case,Etot);
We then use the reportResults() method of the energy fractions scoring array to report the deposited energy fractions:
double norm = ((double)current_case)/Etot;
egsInformation("\n\n======================================================\n");
Because the scoring array object divides the results by the number of statistically independent showers (last_case) but we want the normalization to be fraction of the energy deposited, we set the normalization constant to be last_case/Etot. The string passed as a second argument will be output as a title and the false argument indicates that we want the uncertainties to be absolute instead of relative as percent of the local result. The last argument is a format string used to output the result. If omitted or set to null, a default format string will be used (using a g format for outputing the floating numbers). If the user has requested the calculation of one or more pulse height distributions (i.e. nph>0), we output a title depending on the number of distributions calculated:
egsInformation(" Energy fractions\n");
egsInformation("======================================================\n");
and then in a loop over the number of pulse height distributions calculated
egsInformation("The first and last items in the following list of energy fractions are the reflected and transmitted energy, respectively. These two values are only meaningful if the source is directed in the positive z-direction. The remaining values are the deposited energy fractions in the regions of the geometry, but notice that the identifying index is the region number offset by 1 (ir+1).");
we have a loop over the number of bins for the pulse height distribution in this region, obtain the result from the scoring array and output a data triplet consisting of the midpoint energy of the bin, the result in the bin and the uncertainty of the result:
score->reportResults(norm,
"ir+1 | Reflected, deposited, or transmitted energy fraction",false,
" %d %12.6e +/- %12.6e %c\n");
if (nph > 0) {
if (nph > 1) {
egsInformation("\n\n======================================================\n");
egsInformation(" Pulse height distributions\n"
"======================================================\n\n");
}
else {
egsInformation("\n\n Pulse height distribution in region %d\n"
"======================================================\n\n",
ph_regions[0]);
}
for (int j=0; j<nph; j++) {
if (nph > 1) egsInformation("\nRegion %d\n"
"----------------\n\n",ph_regions[j]);
double f,df;
for (int i=0; i<pheight[j]->bins(); i++) {
pheight[j]->currentResult(i,f,df);
egsInformation("%g %g %g\n",ph_de[j]*(0.5+i),
f/ph_de[j],df/ph_de[j]);
}
}
Because the scoring array object already divides by the number of statistically independent events, we only divide by the bin width to have the results normalized as counts per incident particle per MeV.

The getCurrentResult() function is used by the run control object to provide a single result for our simulation that is the combined result from all jobs in parallel runs. We arbitrarily decide to define the single result of our simulation monitored at run time as the energy fraction reflected (stored in the zeroth region of the score scoring array). Hence, the implementation of this function looks like this:

}

In the startNewShower() function we must collect the energy being imparted into the geometry and inform our scoring objects when the simulation of a new statistically independent event begins. In addition, we implement the scoring of the requested pulse height distributions (PHD) here. We start by collecting the energy entering the geometry

/*
EGS_Float Rmax = 20; EGS_Float dr = Rmax/200;
egsInformation("\n\n Electron/Photon fluence at back of geometry as a function of radial distance\n"
"============================================================================\n");
for(int j=0; j<200; ++j) {
double fe,dfe,fg,dfg;
eflu->currentResult(j,fe,dfe); gflu->currentResult(j,fg,dfg);
EGS_Float r1 = dr*j, r2 = r1 + dr;
EGS_Float A = M_PI*(r2*r2 - r1*r1);
EGS_Float r = j > 0 ? 0.5*(r1 + r2) : 0;
egsInformation("%9.3f %15.6e %15.6e %15.6e %15.6e\n",r,fe/A,dfe/A,fg/A,dfg/A);
}
*/
}
void Tutor7_Application::getCurrentResult(double &sum, double &sum2,
double &norm, double &count) {
count = current_case;
norm = Etot > 0 ? count/Etot : 0;
score->currentScore(0,sum,sum2);
}
int Tutor7_Application::startNewShower() {
Etot += p.E*p.wt;
The parameters of the particle that is about to be simulated are available in the EGS_Application::p protected data member. We then check if the particle about to be simulated is statistically independent from the last particle simulated by comparing the inherited protected data members EGS_Application::current_case and EGS_Application::last_case:
if (res) {
If they are the same, then the two particles belong to the same statistically independent case and we don't need to do anything. This can happen e.g. when using a phase space file or a BEAM simulation source , see e.g. [Walters et.al., Med.Phys. 29 (2002) 2745] for a detailed discussion of the correct implementation of a history-by-history statistical analysis. If the user has requested the calculation of PHDs (i.e. nph>0), we loop over all requested PHDs
return res;
}
if (current_case != last_case) {
if (nph > 0) {
for (int j=0; j<nph; j++) {
inform the scoring array used to collect this PHD that a new statistically independent event is about to begin,
pheight[j]->setHistory(current_case);
obtain the energy deposited in the PHD region in the last statistically independent shower,
int ireg = ph_regions[j];
// In ausgab the scoring array is offset by 1 to include
// the reflected and transmitted as the first and last regions
EGS_Float edep = score->currentScore(ireg+1);
and, if this energy is greater than zero, determine the PHD bin to which such an energy deposition belongs and add a count to this PHD bin
if (edep > 0) {
int ibin = min((int)(edep/(current_weight*ph_de[j])), pheight[j]->bins()-1);
if (ibin >= 0 && ibin < pheight[j]->bins()) {
pheight[j]->score(ibin,1);
}
}
}
Note that in the above we divide by current_weight. This is needed because all energy depositions are weighted with the particle weights that may not be constant and also may be different from unity (e.g. for a collimated point source ). We remember the statistical weight of the particles intiating the showers in current_weight (see below). We then inform the energy deposition scoring array that a new statistically independent event begins
}
score->setHistory(current_case);
and set the last simulated case to the current case
eflu->setHistory(current_case);
gflu->setHistory(current_case);
last_case = current_case;
}
and remember the statistical weight of the incident particle.
current_weight = p.wt;
return 0;
}

This completes the implementation of our Tutor7_Application class. One needs more code compared to an application derived from EGS_SimpleApplication (see e.g. this example). However, a closer examination shows that a significant part of the code is neede to implement the restart/parallel runs/combine results functionality and to provide the user with the capability to specify the regions for PHD calculation in the input file. Without this added functionality, the tutor7pp source code would be not much longer than tutor2pp.

We now need a short main function. We construct an instance of the Tutor7_Application class passing the command line arguments to its constructor

#ifdef BUILD_APP_LIB
APP_LIB(Tutor7_Application);
We then initialize the application
and return the error code if some error occured (e.g. the cross section data for the media in the geometry was not found in the specified PEGS data file, there was a mistake in the specification of the geometry, etc.)
We then run the simulation
and return the error code if some error occured,
otherwise we finish the simulation
That's all.

The final step is to provide a Makefile for our application. We will use the standard makefile for building C++ applications provided with the EGSnrc distribution. To do so, we include the make config files,

include $(EGS_CONFIG)
include $(SPEC_DIR)egspp.spec
include $(SPEC_DIR)egspp_$(my_machine).conf
set the application name
specify that no additional macros are needed to build the EGSnrc mortran back-end,
specify that we are deriving from EGS_AdvancedApplication,
specify the set of mortran sources to be the predefined set of sources for advanced applications,
the additional header files that our application depends upon,
and finally include the standard set of rules for building C++ applications

Here is the complete source code of the tutor7pp application:

/*
###############################################################################
#
# EGSnrc egs++ tutor7pp application
# Copyright (C) 2015 National Research Council Canada
#
# This file is part of EGSnrc.
#
# EGSnrc is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# EGSnrc is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with EGSnrc. If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
#
# Author: Iwan Kawrakow, 2005
#
# Contributors: Frederic Tessier
#
###############################################################################
#
# A relatively simple EGSnrc application using the C++ interface. It
# implements the functionality of the original tutor7 tutorial code written
# in mortran except that now, due to the use of the general geometry and
# source packages included in egspp, any geometry or any source can be used
# in the simulation.
#
# In addition, tutor7pp derives from the EGS_AdvancedApplication class and
# therefore automatically inherits the ability to do restarted and parallel
# simulations, to combine the results of parallel runs or to re-analyze the
# results of single/parallel runs. It also inherits the ability to run for a
# user specified maximum amount of cpu time or to terminate the simulation
# when a user specified uncertainty has been reached.
#
#
# TERMINOLOGY
# -----------
#
# Simulations are split into 'chunks'. For simple simulations (no parallel
# runs, etc.) there is a single simulation chunk with the number of
# histories specified in the input file. For parallel runs the number of
# chunks and number of histories per chunk are determined by a 'run control
# object' (see below).
#
# Each simulation chunk is split into 'batches'. The batches are not required
# for statistical analysis (by using the provided scoring classes it is easy
# to have a history-by-history uncertainty estimation). Instead, simulation
# chunks are split into batches so that the progress of the simulation can be
# reported after the completion of a batch and the current results can be
# stored into a data file. By default there are 10 batches per simulation chunk
# but this can be changed in the input file.
#
# The simulation is controlled via a 'run control object' (RCO) The purpose
# of the run control object is to give to the shower loop the number of
# histories per simulation chunk, number of batches per chunk and to possibly
# terminate the simulation prematurely if certain conditions are met (e.g.
# maximum cpu time allowed is exceeded or the required uncertainty has been
# reached).
#
# egs++ provides 2 run control objects:
#
# 1) simple: the simple RCO always uses a single simulation chunk.
#
# 2) JCF: a JCF object is used by default for parallel runs
# JCF stands for Job Control File as this type of object
# uses a file placed in the user code directory to record
# the number of histories remaining, the number of jobs
# running, etc., in parallel runs. This is explained in
# more details in PIRS-877. A JCF object uses by default
# 10 simulation chunks but this can be changed in the
# input file.
#
# It is possible to use a simple control object for parallel runs by giving
# the -s or --simple command line option. In this case, each parallel job
# will run the number of histories specified in the input file but
# automatically adjust the initial random number seed(s) with the job index.
# This additional possibility has been implemented because several users have
# reported problems with file locking needed for a JCF run control object.
#
# It is also possible to have other RCO's compiled into shared libraries and
# automatically loaded at run time (e.g., one could implement a RCO that
# communicates via TCP/IP with a remote server to obtain the number of
# histories in the next simulation chunk).
#
#
# USAGE
# -----
#
# - Geometry and particle source are specified in an input file as explained
# in PIRS-899 and PIRS-898.
#
# - Run control is specified in a section of the input file delimited by
# :start run control: and :stop run control: labels.
#
# - A simple RCO is used for single job runs.
#
# - A JCF RCO is used by default for parallel runs, unless -s or --simple
# is specified on the command line.
#
# - A simple RCO understands the following keys:
# ncase = number of histories to run
# nbatch = number of batches to use
# statistical accuracy sought = required uncertainty, in %
# max cpu hours allowed = max. processor time allowed
# calculation = first | restart | combine | analyze
#
# All inputs except for ncase are optional (a missing ncase key will result
# in a simulation with 0 particles).
#
# - A JCF object understands all the above keys plus
# nchunk = number of simulation chunks
#
# - The simulation is run using
#
# tutor7pp -i input_file -p pegs_file [-o output_file] [-s] [-P n -j i]
#
# where command line arguments between [] are optional. The -P n option
# specifies the number of parallel jobs n and -j i the index of this job.
# On Linux/Unix systems it is more convenient to use the 'exb' script for
# parallel job submission (see PIRS-877)
#
###############################################################################
*/
#include "egs_scoring.h"
#include "egs_interface2.h"
#include "egs_functions.h"
#include "egs_input.h"
#include "egs_rndm.h"
#include <cstdlib>
using namespace std;
class APP_EXPORT Tutor7_Application : public EGS_AdvancedApplication {
EGS_ScoringArray *score; // scoring array with energies deposited
EGS_ScoringArray *eflu; // scoring array for electron fluence at back of geometry
EGS_ScoringArray *gflu; // scoring array for photon fluence at back of geometry
EGS_ScoringArray **pheight; // pulse height distributions.
int nreg; // number of regions in the geometry
int nph; // number of pulse height objects.
double Etot; // total energy that has entered the geometry
int rr_flag; // used for RR and radiative splitting
EGS_Float current_weight; // the weight of the initial particle that
// is currently being simulated
bool deflect_brems;
EGS_Float *ph_de; // bin widths if the pulse height distributions.
int *ph_regions; // region indices of the ph-distributions
static string revision; // the CVS revision number
public:
Tutor7_Application(int argc, char **argv) :
EGS_AdvancedApplication(argc,argv), score(0), eflu(0), gflu(0), pheight(0),
nreg(0), nph(0), Etot(0), rr_flag(0), current_weight(1), deflect_brems(false) { };
~Tutor7_Application() {
if (score) {
delete score;
}
if (eflu) {
delete eflu;
}
if (gflu) {
delete gflu;
}
if (nph > 0) {
for (int j=0; j<nph; j++) {
delete pheight[j];
}
delete [] pheight;
delete [] ph_regions;
delete [] ph_de;
}
};
void describeUserCode() const;
int initScoring();
int ausgab(int iarg);
int outputData();
int readData();
void resetCounter();
int addState(istream &data);
void outputResults();
void getCurrentResult(double &sum, double &sum2, double &norm,
double &count);
protected:
int startNewShower();
};
string Tutor7_Application::revision = " ";
extern "C" void F77_OBJ_(egs_scale_xcc,EGS_SCALE_XCC)(const int *,const EGS_Float *);
extern "C" void F77_OBJ_(egs_scale_bc,EGS_SCALE_BC)(const int *,const EGS_Float *);
void Tutor7_Application::describeUserCode() const {
"\n ***************************************************"
"\n * *"
"\n * tutor7pp *"
"\n * *"
"\n ***************************************************"
"\n\n");
egsInformation("This is Tutor7_Application %s based on\n"
" EGS_AdvancedApplication %s\n\n",
egsSimplifyCVSKey(revision).c_str(),
egsSimplifyCVSKey(base_revision).c_str());
}
int Tutor7_Application::initScoring() {
// Get the number of regions in the geometry.
nreg = geometry->regions();
score = new EGS_ScoringArray(nreg+2);
//i.e. we always score energy fractions
eflu = new EGS_ScoringArray(200);
gflu = new EGS_ScoringArray(200);
// Initialize with no russian roulette
EGS_Input *options = input->takeInputItem("scoring options");
if (options) {
EGS_Input *scale;
while ((scale = options->takeInputItem("scale xcc"))) {
vector<string> tmp;
int err = scale->getInput("scale xcc",tmp);
//egsInformation("Found 'scale xcc', err=%d tmp.size()=%d\n",err,tmp.size());
if (!err && tmp.size() == 2) {
int imed = EGS_BaseGeometry::getMediumIndex(tmp[0]) + 1;
if (imed > 0) {
EGS_Float fac = atof(tmp[1].c_str());
egsInformation("\n ***** Scaling xcc of medium %d with %g\n",imed,fac);
F77_OBJ_(egs_scale_xcc,EGS_SCALE_XCC)(&imed,&fac);
}
}
delete scale;
}
while ((scale = options->takeInputItem("scale bc"))) {
vector<string> tmp;
int err = scale->getInput("scale bc",tmp);
//egsInformation("Found 'scale xcc', err=%d tmp.size()=%d\n",err,tmp.size());
if (!err && tmp.size() == 2) {
int imed = EGS_BaseGeometry::getMediumIndex(tmp[0]) + 1;
if (imed > 0) {
EGS_Float fac = atof(tmp[1].c_str());
egsInformation("\n ***** Scaling bc of medium %d with %g\n",imed,fac);
F77_OBJ_(egs_scale_bc,EGS_SCALE_BC)(&imed,&fac);
}
}
delete scale;
}
vector<string> choices;
choices.push_back("no");
choices.push_back("yes");
deflect_brems = options->getInput("deflect electron after brems",choices,0);
if (deflect_brems) {
egsInformation("\n *** Using electron deflection in brems events\n\n");
setAusgabCall(AfterBrems,true);
}
int n_rr;
if (!options->getInput("Russian Roulette",n_rr) && n_rr > 1) {
the_egsvr->i_do_rr = n_rr;
setAusgabCall(BeforeBrems,true);
setAusgabCall(AfterBrems,true);
setAusgabCall(BeforeAnnihFlight,true);
setAusgabCall(AfterAnnihFlight,true);
setAusgabCall(BeforeAnnihRest,true);
setAusgabCall(AfterAnnihRest,true);
//setAusgabCall(FluorescentEvent,true);
egsInformation("\nUsing Russian Roulette with survival probability 1/%d\n",n_rr);
}
// The user has provided scoring options input.
// See where she/he wants to score a pulse height distribution
// and how many bins to use for each pulse height distribution
vector<int> regions;
int err = options->getInput("pulse height regions",regions);
vector<int> nbins;
int err1 = options->getInput("pulse height bins",nbins);
if (!err && !err1) {
if (regions.size() != nbins.size() && nbins.size() != 1)
egsWarning("initScoring(): you must input the same "
"number of 'regions' and 'bins' inputs or a single 'bins'"
" input\n");
else {
EGS_ScoringArray **tmp = new EGS_ScoringArray* [nreg+2];
for (int i=0; i<nreg+2; i++) {
tmp[i] = 0;
}
for (int j=0; j<regions.size(); j++) {
int nb = nbins.size() == 1 ? nbins[0] : nbins[j];
if (nb < 1) {
egsWarning("zero bins for region %d?\n",regions[j]);
}
if (regions[j] < -1 || regions[j] > nreg) {
egsWarning("invalid region index %d\n",regions[j]);
}
if (nb > 0 && regions[j] >= 0 && regions[j] < nreg+2) {
int ij = regions[j];
if (tmp[ij]) egsInformation("There is already a "
"PHD object in region %d => ignoring it\n",ij);
else {
tmp[ij] = new EGS_ScoringArray(nb);
++nph;
}
}
}
if (nph > 0) {
pheight = new EGS_ScoringArray* [nph];
ph_regions = new int [nph];
ph_de = new EGS_Float [nph];
EGS_Float Emax = source->getEmax();
int iph = 0;
for (int j=0; j<nreg+2; j++) {
if (tmp[j]) {
pheight[iph] = tmp[j];
ph_regions[iph] = j;
int nbin = pheight[iph]->bins();
ph_de[iph++] = Emax/nbin;
}
}
}
delete [] tmp;
}
}
else egsWarning("initScoring(): you must provide both, 'regions'"
" and 'bins' input\n");
delete options;
}
return 0;
}
int Tutor7_Application::ausgab(int iarg) {
if (iarg <= 4) {
int np = the_stack->np - 1;
// Note: ir is the region number+1
int ir = the_stack->ir[np]-1;
// If the particle is outside the geometry and headed in the positive
// z-direction, change the region to count it as "transmitted"
// Note: This is only valid for certain source/geometry conditions!
// If those conditions are not met, the reflected and transmitted
// energy fractions will be wrong
if (ir == 0 && the_stack->w[np] > 0) {
ir = nreg+1;
}
EGS_Float aux = the_epcont->edep*the_stack->wt[np];
if (aux > 0) {
score->score(ir,aux);
}
// if( the_stack->iq[np] ) score->score(ir,the_epcont->edep*the_stack->wt[np]);
if (ir == nreg+1) {
EGS_ScoringArray *flu = the_stack->iq[np] ? eflu : gflu;
EGS_Float r2 = the_stack->x[np]*the_stack->x[np] + the_stack->y[np]*the_stack->y[np];
int bin = (int)(sqrt(r2)*10.);
if (bin < 200) {
aux = the_stack->wt[np]/the_stack->w[np];
if (aux > 0) {
flu->score(bin,aux);
}
}
}
return 0;
}
int np = the_stack->np-1;
if (iarg == BeforeBrems || iarg == BeforeAnnihRest || (iarg == BeforeAnnihFlight &&
the_stack->latch[np] > 0)) {
the_stack->latch[np] = 0;
rr_flag = 1;
return 0;
}
if (iarg == AfterBrems && deflect_brems) {
EGS_Vector u(the_stack->u[np-1],the_stack->v[np-1],the_stack->w[np-1]);
EGS_Float tau = the_stack->E[np-1]/the_useful->rm - 1;
EGS_Float beta = sqrt(tau*(tau+2))/(tau+1);
EGS_Float eta = 2*rndm->getUniform()-1;
EGS_Float cost = (beta + eta)/(1 + beta*eta);
EGS_Float sint = 1 - cost*cost;
if (sint > 0) {
sint = sqrt(sint);
EGS_Float cphi, sphi;
rndm->getAzimuth(cphi,sphi);
u.rotate(cost,sint,cphi,sphi);
the_stack->u[np-1] = u.x;
the_stack->v[np-1] = u.y;
the_stack->w[np-1] = u.z;
}
}
if (iarg == AfterBrems || iarg == AfterAnnihRest || iarg == AfterAnnihFlight) {
if (iarg == AfterBrems && rr_flag) {
}
rr_flag = 0;
return 0;
}
/*
if( iarg == FluorescentEvent && the_stack->latch[np] > 0 ) {
the_stack->latch[np] = 0; the_stack->wt[np] /= the_egsvr->i_do_rr;
if( np+1+the_egsvr->i_do_rr > MXSTACK )
egsFatal("Stack size exceeded while splitting dluorescent photon!\n");
for(int j=1; j<the_egsvr->i_do_rr; j++) {
EGS_Float cost = 2*rndm->getUniform()-1;
EGS_Float sint = 1 - cost*cost; sint = sint > 0 ? sqrt(sint) : 0;
EGS_Float cphi, sphi; rndm->getAzimuth(cphi,sphi);
the_stack->E[np+j] = the_stack->E[np];
the_stack->wt[np+j] = the_stack->wt[np];
the_stack->iq[np+j] = the_stack->iq[np];
the_stack->ir[np+j] = the_stack->ir[np];
the_stack->dnear[np+j] = the_stack->dnear[np];
the_stack->latch[np+j] = the_stack->latch[np];
the_stack->x[np+j] = the_stack->x[np];
the_stack->y[np+j] = the_stack->y[np];
the_stack->z[np+j] = the_stack->z[np];
the_stack->u[np+j] = sint*cphi;
the_stack->v[np+j] = sint*sphi;
the_stack->w[np+j] = cost;
}
}
*/
return 0;
}
int Tutor7_Application::outputData() {
// We first call the outputData() function of our base class.
// This takes care of saving data related to the source, the random
// number generator, CPU time used, number of histories, etc.
if (err) {
return err;
}
// We then write our own data to the data stream. data_out is
// a pointer to a data stream that has been opened for writing
// in the base class.
(*data_out) << " " << Etot << endl;
if (!score->storeState(*data_out)) {
return 101;
}
for (int j=0; j<nph; j++) {
if (!pheight[j]->storeState(*data_out)) {
return 102+j;
}
}
if (!eflu->storeState(*data_out)) {
return 301;
}
if (!gflu->storeState(*data_out)) {
return 302;
}
return 0;
}
int Tutor7_Application::readData() {
// We first call the readData() function of our base class.
// This takes care of reading data related to the source, the random
// number generator, CPU time used, number of histories, etc.
// (everything that was stored by the base class outputData() method).
if (err) {
return err;
}
// We then read our own data from the data stream.
// data_in is a pointer to an input stream that has been opened
// by the base class.
(*data_in) >> Etot;
if (!score->setState(*data_in)) {
return 101;
}
for (int j=0; j<nph; j++) {
if (!pheight[j]->setState(*data_in)) {
return 102+j;
}
}
if (!eflu->setState(*data_in)) {
return 301;
}
if (!gflu->setState(*data_in)) {
return 302;
}
return 0;
}
void Tutor7_Application::resetCounter() {
// Reset everything in the base class
// Reset our own data to zero.
score->reset();
Etot = 0;
for (int j=0; j<nph; j++) {
pheight[j]->reset();
}
eflu->reset();
gflu->reset();
}
int Tutor7_Application::addState(istream &data) {
// Call first the base class addState() function to read and add
// all data related to source, RNG, CPU time, etc.
if (err) {
return err;
}
// Then read our own data to temporary variables and add to
// our results.
double etot_tmp;
data >> etot_tmp;
Etot += etot_tmp;
EGS_ScoringArray tmp(nreg+2);
if (!tmp.setState(data)) {
return 101;
}
(*score) += tmp;
for (int j=0; j<nph; j++) {
EGS_ScoringArray tmpj(pheight[j]->bins());
if (!tmpj.setState(data)) {
return 102 + j;
}
(*pheight[j]) += tmpj;
}
EGS_ScoringArray tmp1(200);
if (!tmp1.setState(data)) {
return 301;
}
(*eflu) += tmp1;
if (!tmp1.setState(data)) {
return 302;
}
(*gflu) += tmp1;
return 0;
}
void Tutor7_Application::outputResults() {
egsInformation("\n\n last case = %d Etot = %g\n",
(int)current_case,Etot);
double norm = ((double)current_case)/Etot;
egsInformation("\n\n======================================================\n");
egsInformation(" Energy fractions\n");
egsInformation("======================================================\n");
egsInformation("The first and last items in the following list of energy fractions are the reflected and transmitted energy, respectively. These two values are only meaningful if the source is directed in the positive z-direction. The remaining values are the deposited energy fractions in the regions of the geometry, but notice that the identifying index is the region number offset by 1 (ir+1).");
score->reportResults(norm,
"ir+1 | Reflected, deposited, or transmitted energy fraction",false,
" %d %12.6e +/- %12.6e %c\n");
if (nph > 0) {
if (nph > 1) {
egsInformation("\n\n======================================================\n");
egsInformation(" Pulse height distributions\n"
"======================================================\n\n");
}
else {
egsInformation("\n\n Pulse height distribution in region %d\n"
"======================================================\n\n",
ph_regions[0]);
}
for (int j=0; j<nph; j++) {
if (nph > 1) egsInformation("\nRegion %d\n"
"----------------\n\n",ph_regions[j]);
double f,df;
for (int i=0; i<pheight[j]->bins(); i++) {
pheight[j]->currentResult(i,f,df);
egsInformation("%g %g %g\n",ph_de[j]*(0.5+i),
f/ph_de[j],df/ph_de[j]);
}
}
}
/*
EGS_Float Rmax = 20; EGS_Float dr = Rmax/200;
egsInformation("\n\n Electron/Photon fluence at back of geometry as a function of radial distance\n"
"============================================================================\n");
for(int j=0; j<200; ++j) {
double fe,dfe,fg,dfg;
eflu->currentResult(j,fe,dfe); gflu->currentResult(j,fg,dfg);
EGS_Float r1 = dr*j, r2 = r1 + dr;
EGS_Float A = M_PI*(r2*r2 - r1*r1);
EGS_Float r = j > 0 ? 0.5*(r1 + r2) : 0;
egsInformation("%9.3f %15.6e %15.6e %15.6e %15.6e\n",r,fe/A,dfe/A,fg/A,dfg/A);
}
*/
}
void Tutor7_Application::getCurrentResult(double &sum, double &sum2,
double &norm, double &count) {
count = current_case;
norm = Etot > 0 ? count/Etot : 0;
score->currentScore(0,sum,sum2);
}
int Tutor7_Application::startNewShower() {
Etot += p.E*p.wt;
if (res) {
return res;
}
if (current_case != last_case) {
if (nph > 0) {
for (int j=0; j<nph; j++) {
pheight[j]->setHistory(current_case);
int ireg = ph_regions[j];
// In ausgab the scoring array is offset by 1 to include
// the reflected and transmitted as the first and last regions
EGS_Float edep = score->currentScore(ireg+1);
if (edep > 0) {
int ibin = min((int)(edep/(current_weight*ph_de[j])), pheight[j]->bins()-1);
if (ibin >= 0 && ibin < pheight[j]->bins()) {
pheight[j]->score(ibin,1);
}
}
}
}
score->setHistory(current_case);
eflu->setHistory(current_case);
gflu->setHistory(current_case);
last_case = current_case;
}
current_weight = p.wt;
return 0;
}
#ifdef BUILD_APP_LIB
APP_LIB(Tutor7_Application);
#else
APP_MAIN(Tutor7_Application);
#endif