Implement New Showers

In case you want to replace the PYTHIA initial- and final-state showers by your own, it is possible but not trivial. The point is that multiple interactions (MI), initial-state radiation (ISR) and final-state radiation (FSR) in general appear in one single interleaved sequence of decreasing pT values. Therefore shower replacements would have to be able to play the game by such rules, as we will outline further below. Of course, this still leaves the field open exactly how to define what to mean by pT, how to handle recoil effects, how the colour flow is affected, and so on, so there is certainly room for alternative showers.

For the moment we assume you want to keep the MI part of the story unchanged, and make use of the existing beam-remnants (BR) machinery. If you want to replace both MI, ISR, FSR and BR then you had better replace the whole PartonLevel module of the code. If, in addition, you want to produce your own hard processes, then you only need the hadron-level standalone part of the machinery.

In order to write replacement codes for ISR and/or FSR it is useful to be aware of which information has to be shared between the different components, and which input/output structure is required of the relevant methods. For details, nothing beats studying the existing code. However, here we provide an overview, that should serve as a useful introduction.

It should be noted that we here primarily address the problem in its full generality, with interleaved MI, ISR and FSR. There exists an option TimeShower:interleave = off where only MI and ISR would be interleaved and FSR be considered after these two, but still before BR. Most of the aspects described here would apply also for that case. By contrast, resonance decays are only considered after all the four above components, and timelike showers in those decays would never be interleaved with anything else, so are much simpler to administrate.

Therefore the pythia.setShowerPtr( timesDecPtr, timesPtr, spacePtr) method allows two separate pointers to be set to instances of derived TimeShower classes. The first is only required to handle decays, say of Z^0 or Upsilon, with no dependence on beam remnants or ISR. The second, as well as spacePtr, has to handle the interleaved evolution of MI, ISR and FSR. Therefore you are free to implement only the first, and let the PYTHIA default showers take care of the latter two. But, if you wanted to, you could also set timesDecPtr = 0 and only provide a timesPtr, or only a spacePtr. If your timelike shower does both cases, the first two pointers can agree. The only tiny point to take into account then is that init( beamAPtr, beamBPtr) is called twice, a first time to timesDecPtr with beam pointers 0, and a second time to timesPtr with nonvanishing beam pointers.

The event record

Obviously the main place for sharing information is the event record, specifically the Event event member of Pythia, passed around as a reference. It is assumed you already studied how it works, so here we only draw attention to a few aspects of special relevance.

One basic principle is that existing partons should not be overwritten. Instead new partons should be created, even when a parton only receives a slightly shifted momentum and for the rest stays the same. Such "carbon copies" by final-state branchings should be denoted by both daughter indices of the original parton pointing to the copy, and both mother indices of the copy to the original. If the copy instead is intended to represent an earlier step, e.g. in ISR backwards evolution, the role of mothers and daughters is interchanged. The event.copy( iCopy, newStatus) routine can take care of this tedious task; the sign of newStatus tells the program which case to assume.

To make the event record legible it is essential that the status codes are selected appropriately to represent the reason why each new parton is added to the record. Also remember to change the status of a parton to be negative whenever an existing parton is replaced by a set of new daughter partons.

Another important parton property is scale(), which does not appear in the normal event listing, but only if you use the extended Event:listScaleAndVertex = on option. This property is supposed to represent the production scale (in GeV) of a parton. In the current FSR and ISR algorithms it is used to restrict from above the allowed pT values for branchings of this particular parton. Beam remnants and other partons that should not radiate are assigned scale 0.

The subsystems

One aspect that complicates administration is that an event can contain several subsystems, consisting of one MI and its associated ISR and FSR, which to first approximation are assumed to evolve independently, but to second are connected by the interleaved evolution. The partons of a given subsystem therefore do not have to be stored consecutively. Some simple vectors put inside the event record can be used to keep track of the current position, i.e. index iPos for parton event[iPos], of all partons of all systems.

The number of systems is given by sizeSystems(), with systems numbered 0 through sizeSystems() - 1. The size of a given subsystem iSys is given by sizeSystem(iSys), with analogous numbering. The method getInSystem( iSys, iMem) returns the position iPos of the iMem'th member of the iSys'th system. For each system, the slots iMem = 0 and 1 are intended for the incoming partons from beam A and B, respectively. If there are no beams, such as in resonance decays, these should be assigned value 0. Slots 2 onwards are intended for all the outgoing partons. These latter partons are not guaranteed to appear in any particular order.

New systems are created from the hard process and by the MI, not from any of the other components, by the newSystem() method, which returns the iSys code of the new system. Both FSR and ISR modify the position of partons, however. Since an FSR or ISR branching typically implies a new state with one more parton than before, the method addToSystem( iSys, iPos) appends a new slot to the given system and stores the iPos value there. Furthermore, in a branching, several existing partons may also be moved to new slots. The method setInSystem( iSys, iMem, iPos) replaces the current iPos value in the given slot by the provided one. If you do not know the iMem value, replaceInSystem( iSys, iPosOld, iPosNew) will replace any ocurence of iPosOld by iPosNew wherever it is found in the iSys'th system. In a FSR 1 -> 2 branching it is irrelevant which parton position you let overwrite the existing slot and which is added to the end of the system.

Finally, clearSystems() empties the information, and listSystems() provides a simple listing of all the iPos values by system, intended for debugging purposes only.

The system information must be kept up-to-date. The MI component only writes, but both ISR, FSR and BR make extensive use of the existing information. As an example, the introduction of primordial kT in the beam remnants will fail if the information on which final-state partons belong to which system is out-of-date.

Currently the system information is kept throughout the continued history of the event. Resonance decays create new systems, appended to the existing ones. This could be useful during the hadronization stage, to collect the partons that belong to a resonace with preserved mass when a small string collapses to one particle, but is not yet used for that.

The beams

The different subsystems are tied together by them sharing the same initial beam particles, and thereby being restricted by energy-momentum and flavour conservation issues. The information stored in the two beam particles, here called beamA and beamB, is therefore as crucial to keep correct as the above subsystem list.

Both beam objects are of the BeamParticle class. Each such object contains a vector with the partons extracted from it. The number of such partons, beamX.size() (X = A or B), of course is the same as the above number of subsystems in the event record. (The two diverge at the BR step, where further beam remnants are added to the beams without corresponding to new subsystems.) The individual partons are accessed by an overloaded indexing operator to a vector of ResolvedParton objects. The iPos() property corresponds to the iPos one above, i.e. providing the position in the main event record of a parton. In particular, beamA[iSys].iPos() = event.getInSystem( iSys, 0) and beamB[iSys].iPos() = event.getInSystem( iSys, 1). Whereas thus the indices of the two incoming partons to a subsystem are stored in two places, the ones of the outgoing partons only appear in the system part of the Event class.

Just as the subsystems in Event must be updated, so must the information in the two BeamParticle's, e.g. with methods beamX[iSys].iPos( iPosIn) when an incoming parton is replaced by a new one in line iPosIn. Furthermore the new parton identity should be set by beamX[iSys].id( idIn) and the new x energy-momentum fraction by beamX[iSys].x( xIn). The three can be combined in one go by beamX[iSys].update( iPosIn, idIn, xIn).

To be specific, it is assumed that, at each step, the two incoming partons are moving along the +-z axis and are massless. Since the event is constructed in the c.m. frame of the incoming beams this implies that x = 2 E / E_cm. If the x values are not defined accordingly or not kept up-to-date the BR treatment will not conserve energy-momentum.

In return, the BeamParticle objects give access to some useful methods. The beamX.xf( id, x, Q2) returns the standard PDF weight x f_id(x, Q^2). More intererstingly, beamX.xfISR( iSys, id, x, Q2) returns the modified weight required when several subsystems have to share the energy and flavours. Thus iSys is added as an extra argument, and the momentum already assigned to the other subsystems is not available for evolution, i.e. the maximal x is correspondingly smaller than unity. Also flavour issues are handled in a similar spirit.

An additional complication is that a parton can be either valence or sea, and in the latter case the BR treatment also distinguishes companion quarks, i.e. quark-antiquark pairs that are assumed to come from the same original g -> q qbar branching, whether perturbative or not. This can be queried either with the beamX[iSys].companion() method, for detailed information, or with the beamX[iSys].isValence(), beamX[iSys].isUnmatched() and beamX[iSys].isCompanion() metods for yes/no answers whether a parton is valence, unmatched sea or matched sea. This choice should affect the ISR evolution; e.g., a valence quark cannot be constructed back to come from a gluon.

To keep this info up-to-date, the beamX.pickValSeaComp() method should be called whenever a parton of a new flavour has been picked in the ISR backwards evolution, but not if the flavour has not been changed, since then one should not be allowed to switch back and forth between the same quark being considered as valence or as sea. Since the pickValSeaComp() method makes use of the current parton-density values, it should be preceded by a call to beamX.xfISR( iSys, id, x, Q2), where the values in the call are the now finally accepted ones for the newly-found mother. (Such a call is likely to have been made before, during the evolution, but is not likely to be the most recent one, i.e. still in memory, and therefore had better be redone.)

Most of the issues in this section are related to the ISR algorithm, i.e. it is possible to write an FSR algorithm that is completely decoupled. Alternatively the FSR may change the position where an incoming parton is stored, or its assumed momentum, e.g. by recoil effects inside dipoles stretched from the scattered back to the incoming partons. In that case some of the methods above would have to be used also inside the FSR algorithm.

The TimeShower interface

If you want to replace the TimeShower class this would involve replacing the following methods.

method  TimeShower()  
The constructor does not need to do anything.

method  void init( BeamParticle* beamAPtrIn = 0, BeamParticle* beamBPtrIn = 0)  
You have to store your local copy of the pointers to these objects, since they have to be used during the generation, as explained above. The pointers could be zero; e.g. a local copy of TimeShower is created to handle showers in decays such as Upsilon -> q qbar from inside the ParticleDecays class. This is also the place to do initialization of whatever parameters you plan to use, e.g. by reading in them from a user-accessible database like the Settings one.

method  double enhancePTmax()  
Relative to the default pT_max evolution scale of the process, it may still be convenient to vary the matching slightly for the hardest interaction in an event, to probe the sensitivity to such details. The base-class implementation returns the value of the TimeShower:pTmaxFudge parameter.

method  int shower( int iBeg, int iEnd, Event& event, double pTmax)  
This is an all-in-one call for shower evolution, and as such cannot be used for the normal interleaved evolution, where only the routines below are used. It also cannot be used in resonance decays that form part of the hard process, since there the user hooks insert a potential veto step. Currently this routine is therefore only used in the hadron-level decays, e.g. Upsilon -> g g g.
iBeg and iEnd is the position of the first and last parton of a separate system, typically produced by a resonance decay. Such a system only evolves in isolation, and in particular does not relate to the beams.
The pTmax value sets the maximum scale for evolution, but normally you would restrict that further for each individual parton based on its respective scale value.
The routine is expected to return the number of FSR branchings that were generated, but only for non-critical statistics purposes.
Since the real action typically is delegated to the routines below, it may well be that the existing code need not be replaced.

method  void prepare( int iSys, Event& event)  
This method is called immediately after a new interaction (or the products of a resonance decay) has been added, and should then be used to prepare the subsystem of partons for subsequent evolution. In the current code this involves identifying all colour and charge dipole ends: the position of radiating and recoiling partons, maximum pT scales, possible higher-order matrix elements matchings to apply, and so on.
The iSys parameter specifies which parton system is to be prepared. It is used to extract the set of partons to be treated, with rules as described in the above section on subsystems. Specifically, the first two partons represent the incoming state, or are 0 for resonance decays unrelated to the beams, while the rest are not required to be in any particular order.

method  void update( int iSys, Event& event)  
This method is called immediately after a spacelike branching in the iSys'th subsystem. Thus the information for that system is out-of-date, while that of the others is unchanged. If you want, you are free to throw away all information for the affected subsystem and call prepare( iSys, event) to create new one. Alternatively you may choose only to update the information that has changed.

method  double pTnext( Event& event, double pTbegAll, double pTendAll)  
This is the main driver routine for the downwards evolution. A new pT is to be selected based on the current information set up by the routines above, and along with that a branching parton or dipole. The pTbegAll scale is the maximum scale allowed, from which the downwards evolution should be begun (usually respecting the maximum scale of each individual parton). If no emission is found above pTendAll (and above the respective shower cutoff scales) then 0. should be returned and no emissions will be allowed. Both scales can vary from one event to the next: if a scale has already been selected for MI or ISR it makes no sense to look for a scale smaller than that from FSR, since it would not be able to compete, so pTendAll is set correspondingly. As it happens, FSR is tried before ISR and MI in the interleaved evolution, but this is an implementational detail that could well change.
Typically the implementation of this routine would be to set up a loop over all possible radiating objects (dipoles, dipole ends, ...), for each pick its possible branching scale and then pick the one with largest scale as possible winner. At this stage no branching should actually be carried out, since MI, ISR and FSR still have to be compared to assign the winner.

method  bool branch( Event& event)  
This method will be called once FSR has won the competition with MI and ISR to do the next branching. The candidate branching found in the previous step should here be carried out in full. The pre-branching partons should get a negative status code and new replacement ones added to the end of the event record. Also the subsystem information should be updated, and possibly also the beams.
Should some problem be encountered in this procedure, e.g. if some not-previously-considered kinematics requirement fails, it is allowed to return false to indicate that no branching could be carried out.

method  int system()  
This method is not virtual. If a branching is constructed by the previous routine this tiny method should be able to return the number of the selected subsystem iSysSel where it occured, so that the spacelike shower can be told which system to update, if necessary. Therefore iSysSel must be set in branch (or already in pTnext).

method  void list( ostream& os = cout)  
This method is not at all required. In the current implementation it outputs a list of all the dipole ends, with information on the respective dipole. The routine is not called anywhere in the public code, but has been inserted at various places during the development/debug phase.

The SpaceShower interface

If you want to replace the SpaceShower class this would involve replacing the following methods. You will find that much of the story reminds of TimeShower above, and actually some cut-and-paste of text is involved. In some respects the description is simpler, since there are no special cases for resonance decays and non-interleaved evolution. Thus there is no correspondence to the TimeShower::shower(...) routine.

method  SpaceShower()  
The constructor does not need to do anything.

method  void init( BeamParticle* beamAPtrIn, BeamParticle* beamBPtrIn)  
You have to store your local copy of the pointers to these objects, since they have to be used during the generation, as explained above. This is also the place to do initialization of whatever parameters you plan to use, e.g. by reading in them from a user-accessible database like the Settings one.

method  bool limitPTmax( Event& event, double Q2Fac = 0., double Q2Ren = 0.)  
The question is whether the ISR should be allowed to occur at larger scales than the hard process it surrounds. This is process-dependent. For instance, if the hard process is Z^0 production we know that ISR should be allowed to go right up to the kinematical limit. If it is a 2 -> 2 QCD process the ISR should not exceed the scale of the hard process, since if so one would doublecount. The SpaceShower:pTmaxMatch switch allows you to force the behaviour, or else to program your own logic. The current implementation limits pT whenever the final state contains a quark (except top), gluon or photon, since then the danger of doublecounting is there. You may replace by your own logic, or leave as is.
The internal PYTHIA implementation also allows intermediate options, where emissions can go up to the kinematical limit but be dampened above the factorization or renormalization scale. Therefore the (square of the) latter two are provided as optional input parameters.

method  double enhancePTmax()  
When the above method limits pT_max to the scale of the process, it may still be convenient to vary the matching slightly for the hardest interaction in an event, to probe the sensitivity to such details. The base-class implementation returns the value of the SpaceShower:pTmaxFudge parameter.

method  void prepare( int iSys, Event& event, bool limitPTmax = true)  
This method is called immediately after a new interaction has been added, and should then be used to prepare the subsystem of partons for subsequent evolution. In the current code this involves identifying the colour and charge dipole ends: the position of radiating and recoiling partons, maximum pT scales, and possible higher-order matrix elements matchings to apply. Depending on what you have in mind you may choose to store slightly different quantities. You have to use the subsystem information described above to find the positions of the two incoming partons (and the outgoing ones) of the system, and from there the scales at which they were produced.
The limitPTmax input agrees with the output of the previous method for the hardest process, and is always true for subsequent MI, since there an unlimited pT for sure would lead to doublecounting.

method  void update( int iSys, Event& event)  
This method is called immediately after a timelike branching in the iSys'th subsystem. Thus the information for that system may be out-of-date, and to be updated. For the standard PYTHIA showers this routine does not need to do anything, but that may be different in another implementation.

method  double pTnext(( Event& event, double pTbegAll, double pTendAll, int nRadIn = -1)  
This is the main driver routine for the downwards evolution. A new pT is to be selected based on the current information set up by the routines above, and along with that a branching parton or dipole. The pTbegAll scale is the maximum scale allowed, from which the downwards evolution should be begun (usually respecting the maximum scale of each individual parton). If no emission is found above pTendAll (and above the respective shower cutoff scales) then 0. should be returned and no emissions will be allowed. Both scales can vary from one event to the next: if a scale has already been selected for MI or ISR it makes no sense to look for a scale smaller than that from FSR, since it would not be able to compete, so pTendAll is set correspondingly. As it happens, FSR is tried before ISR and MI in the interleaved evolution, but this is an implementational detail that could well change.
Typically the implementation of this routine would be to set up a loop over all possible radiating objects (dipoles, dipole ends, ...), for each pick its possible branching scale and then pick the one with largest scale as possible winner. At this stage no branching should actually be carried out, since MI, ISR and FSR still have to be compared to assign the winner.
The final input nRadIn provides the total number of ISR and FSR emissions already generated in the event, and so allows a special treatment for the very first emission, if desired.

method  bool branch( Event& event)  
This method will be called once FSR has won the competition with MI and ISR to do the next branching. The candidate branching found in the previous step should here be carried out in full. The pre-branching partons should get a negative status code and new replacement ones added to the end of the event record. Also the subsystem information should be updated, and possibly also the beams.
Should some problem be encountered in this procedure, e.g. if some not-previously-considered kinematics requirement fails, it is allowed to return false to indicate that no branching could be carried out.

method  int system()  
This method is not virtual. If a branching is constructed by the previous routine this tiny method should be able to return the number of the selected subsystem iSysSel where it occured, so that the spacelike shower can be told which system to update, if necessary. Therefore iSysSel must be set in branch (or already in pTnext).

method  void list( ostream& os = cout)  
This method is not at all required. In the current implementation it outputs a list of all the dipole ends, with information on the respective dipole. The routine is not called anywhere in the public code, but has been inserted at various places during the development/debug phase.