Page MenuHomeHEPForge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
Index: trunk/src/phase_space/phase_space.nw
===================================================================
--- trunk/src/phase_space/phase_space.nw (revision 8877)
+++ trunk/src/phase_space/phase_space.nw (revision 8878)
@@ -1,31092 +1,31061 @@
% -*- ess-noweb-default-code-mode: f90-mode; noweb-default-code-mode: f90-mode; -*-
% WHIZARD code as NOWEB source: phase space
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\chapter{Phase Space}
\includemodulegraph{phase_space}
The abstract representation of a type that parameterizes phase space,
with methods for construction and evaluation.
\begin{description}
\item[phs\_base]
Abstract phase-space representation.
\end{description}
A simple implementation:
\begin{description}
\item[phs\_none]
This implements a non-functional dummy module for the phase space.
A process which uses this module cannot be integrated. The purpose
of this module is to provide a placeholder for processes which do
not require phase-space evaluation. They may still allow for evaluating
matrix elements.
\item[phs\_single]
Parameterize the phase space of a single particle, i.e., the solid
angle. This is useful only for very restricted problems, but it
avoids the complexity of a generic approach in those trivial cases.
\end{description}
The standard implementation is called \emph{wood} phase space. It
consists of several auxiliary modules and the actual implementation
module.
\begin{description}
\item[mappings]
Generate invariant masses and decay angles from given
random numbers (or the inverse operation). Each mapping pertains to a
particular node in a phase-space tree. Different mappings account for
uniform distributions, resonances, zero-mass behavior, and so on.
\item[phs\_trees]
Phase space parameterizations for scattering
processes are defined recursively as if there was an initial particle
decaying. This module sets up a representation in terms of abstract
trees, where each node gets a unique binary number. Each tree is
stored as an array of branches, where integers indicate the
connections. This emulates pointers in a transparent way. Real
pointers would also be possible, but seem to be less efficient for
this particular case.
\item[phs\_forests]
The type defined by this module collects the
decay trees corresponding to a given process and the applicable
mappings. To set this up, a file is read which is either written by
the user or by the \textbf{cascades} module functions. The module
also contains the routines that evaluate phase space, i.e., generate
momenta from random numbers and back.
\item[cascades]
This module is a pseudo Feynman diagram generator with the
particular purpose of finding the phase space parameterizations best
suited for a given process. It uses a model file to set up the
possible vertices, generates all possible diagrams, identifies
resonances and singularities, and simplifies the list by merging
equivalent diagrams and dropping irrelevant ones. This process can be
controlled at several points by user-defined parameters. Note that it
depends on the particular values of particle masses, so it cannot be
done before reading the input file.
\item[phs\_wood]
Make the functionality available in form of an implementation of the
abstract phase-space type.
\item[phs\_fks]
Phase-space parameterization with modifications for the FKS scheme.
\end{description}
\clearpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Abstract phase-space module}
In this module we define an abstract base type (and a trivial test
implementation) for multi-channel phase-space parameterizations.
<<[[phs_base.f90]]>>=
<<File header>>
module phs_base
<<Use kinds>>
<<Use strings>>
use lorentz
use model_data
use flavors
use process_constants
<<Standard module head>>
<<PHS base: public>>
<<PHS base: types>>
<<PHS base: interfaces>>
interface
<<PHS base: sub interfaces>>
end interface
contains
<<PHS base: main procedures>>
end module phs_base
@ %def phs_base
@
<<[[phs_base_sub.f90]]>>=
<<File header>>
submodule (phs_base) phs_base_s
use io_units
use constants, only: TWOPI, TWOPI4
use string_utils, only: split_string
use format_defs, only: FMT_19
use numeric_utils
use diagnostics
use md5
use physics_defs
implicit none
contains
<<PHS base: procedures>>
end submodule phs_base_s
@ %def phs_base_s
@
\subsection{Phase-space channels}
The kinematics configuration may generate multiple parameterizations of phase
space. Some of those have specific properties, such as a resonance in the s
channel.
\subsubsection{Channel properties}
This is the abstract type for the channel properties. We need them as
a data transfer container, so everything is public and transparent.
<<PHS base: public>>=
public :: channel_prop_t
<<PHS base: types>>=
type, abstract :: channel_prop_t
contains
procedure (channel_prop_to_string), deferred :: to_string
generic :: operator (==) => is_equal
procedure (channel_eq), deferred :: is_equal
end type channel_prop_t
@ %def channel_prop_t
<<PHS base: interfaces>>=
abstract interface
function channel_prop_to_string (object) result (string)
import
class(channel_prop_t), intent(in) :: object
type(string_t) :: string
end function channel_prop_to_string
end interface
@ %def channel_prop_to_string
<<PHS base: interfaces>>=
abstract interface
function channel_eq (prop1, prop2) result (flag)
import
class(channel_prop_t), intent(in) :: prop1, prop2
logical :: flag
end function channel_eq
end interface
@ %def channel_prop_to_string
@
Here is a resonance as a channel property. Mass and width are stored
here in physical units.
<<PHS base: public>>=
public :: resonance_t
<<PHS base: types>>=
type, extends (channel_prop_t) :: resonance_t
real(default) :: mass = 0
real(default) :: width = 0
contains
procedure :: to_string => resonance_to_string
procedure :: is_equal => resonance_is_equal
end type resonance_t
@ %def resonance_t
@ Print mass and width.
<<PHS base: sub interfaces>>=
module function resonance_to_string (object) result (string)
class(resonance_t), intent(in) :: object
type(string_t) :: string
end function resonance_to_string
<<PHS base: procedures>>=
module function resonance_to_string (object) result (string)
class(resonance_t), intent(in) :: object
type(string_t) :: string
character(32) :: buffer
string = "resonant: m ="
write (buffer, "(" // FMT_19 // ")") object%mass
string = string // trim (buffer) // " GeV, w ="
write (buffer, "(" // FMT_19 // ")") object%width
string = string // trim (buffer) // " GeV"
end function resonance_to_string
@ %def resonance_to_string
@ Equality.
<<PHS base: sub interfaces>>=
module function resonance_is_equal (prop1, prop2) result (flag)
class(resonance_t), intent(in) :: prop1
class(channel_prop_t), intent(in) :: prop2
logical :: flag
end function resonance_is_equal
<<PHS base: procedures>>=
module function resonance_is_equal (prop1, prop2) result (flag)
class(resonance_t), intent(in) :: prop1
class(channel_prop_t), intent(in) :: prop2
logical :: flag
select type (prop2)
type is (resonance_t)
flag = prop1%mass == prop2%mass .and. prop1%width == prop2%width
class default
flag = .false.
end select
end function resonance_is_equal
@ %def resonance_is_equal
@
This is the limiting case of a resonance, namely an on-shell particle.
We just store the mass in physical units.
<<PHS base: public>>=
public :: on_shell_t
<<PHS base: types>>=
type, extends (channel_prop_t) :: on_shell_t
real(default) :: mass = 0
contains
procedure :: to_string => on_shell_to_string
procedure :: is_equal => on_shell_is_equal
end type on_shell_t
@ %def on_shell_t
@ Print mass and width.
<<PHS base: sub interfaces>>=
module function on_shell_to_string (object) result (string)
class(on_shell_t), intent(in) :: object
type(string_t) :: string
end function on_shell_to_string
<<PHS base: procedures>>=
module function on_shell_to_string (object) result (string)
class(on_shell_t), intent(in) :: object
type(string_t) :: string
character(32) :: buffer
string = "on shell: m ="
write (buffer, "(" // FMT_19 // ")") object%mass
string = string // trim (buffer) // " GeV"
end function on_shell_to_string
@ %def on_shell_to_string
@ Equality.
<<PHS base: sub interfaces>>=
module function on_shell_is_equal (prop1, prop2) result (flag)
class(on_shell_t), intent(in) :: prop1
class(channel_prop_t), intent(in) :: prop2
logical :: flag
end function on_shell_is_equal
<<PHS base: procedures>>=
module function on_shell_is_equal (prop1, prop2) result (flag)
class(on_shell_t), intent(in) :: prop1
class(channel_prop_t), intent(in) :: prop2
logical :: flag
select type (prop2)
type is (on_shell_t)
flag = prop1%mass == prop2%mass
class default
flag = .false.
end select
end function on_shell_is_equal
@ %def on_shell_is_equal
@
\subsubsection{Channel equivalences}
This type describes an equivalence. The current channel is equivalent
to channel [[c]]. The equivalence involves a permutation [[perm]] of
integration dimensions and, within each integration dimension, a
mapping [[mode]].
<<PHS base: types>>=
type :: phs_equivalence_t
integer :: c = 0
integer, dimension(:), allocatable :: perm
integer, dimension(:), allocatable :: mode
contains
<<PHS base: phs equivalence: TBP>>
end type phs_equivalence_t
@ %def phs_equivalence_t
@
The mapping modes are
<<PHS base: types>>=
integer, parameter, public :: &
EQ_IDENTITY = 0, EQ_INVERT = 1, EQ_SYMMETRIC = 2, EQ_INVARIANT = 3
@ %def EQ_IDENTITY EQ_INVERT EQ_SYMMETRIC
@ In particular, if a channel is equivalent to itself in the
[[EQ_SYMMETRIC]] mode, the integrand can be assumed to be symmetric
w.r.t.\ a reflection $x\to 1 - x$ of the correponding integration variable.
These are the associated tags, for output:
<<PHS base: types>>=
character, dimension(0:3), parameter :: TAG = ["+", "-", ":", "x"]
@ %def TAG
@ Write an equivalence.
<<PHS base: phs equivalence: TBP>>=
procedure :: write => phs_equivalence_write
<<PHS base: sub interfaces>>=
module subroutine phs_equivalence_write (object, unit)
class(phs_equivalence_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine phs_equivalence_write
<<PHS base: procedures>>=
module subroutine phs_equivalence_write (object, unit)
class(phs_equivalence_t), intent(in) :: object
integer, intent(in), optional :: unit
integer :: u, j
u = given_output_unit (unit)
write (u, "(5x,'=',1x,I0,1x)", advance = "no") object%c
if (allocated (object%perm)) then
write (u, "(A)", advance = "no") "("
do j = 1, size (object%perm)
if (j > 1) write (u, "(1x)", advance = "no")
write (u, "(I0,A1)", advance = "no") &
object%perm(j), TAG(object%mode(j))
end do
write (u, "(A)") ")"
else
write (u, "(A)")
end if
end subroutine phs_equivalence_write
@ %def phs_equivalence_write
@ Initialize an equivalence. This allocates the [[perm]] and [[mode]]
arrays with equal size.
<<PHS base: phs equivalence: TBP>>=
procedure :: init => phs_equivalence_init
<<PHS base: sub interfaces>>=
module subroutine phs_equivalence_init (eq, n_dim)
class(phs_equivalence_t), intent(out) :: eq
integer, intent(in) :: n_dim
end subroutine phs_equivalence_init
<<PHS base: procedures>>=
module subroutine phs_equivalence_init (eq, n_dim)
class(phs_equivalence_t), intent(out) :: eq
integer, intent(in) :: n_dim
allocate (eq%perm (n_dim), source = 0)
allocate (eq%mode (n_dim), source = EQ_IDENTITY)
end subroutine phs_equivalence_init
@ %def phs_equivalence_init
@
\subsubsection{Channel objects}
The channel entry holds (optionally) specific properties.
[[sf_channel]] is the structure-function channel that corresponds to this
phase-space channel. The structure-function channel may be set up with a
specific mapping that depends on the phase-space channel properties. (The
default setting is to leave the properties empty.)
<<PHS base: public>>=
public :: phs_channel_t
<<PHS base: types>>=
type :: phs_channel_t
class(channel_prop_t), allocatable :: prop
integer :: sf_channel = 1
type(phs_equivalence_t), dimension(:), allocatable :: eq
contains
<<PHS base: phs channel: TBP>>
end type phs_channel_t
@ %def phs_channel_t
@ Output.
<<PHS base: phs channel: TBP>>=
procedure :: write => phs_channel_write
<<PHS base: sub interfaces>>=
module subroutine phs_channel_write (object, unit)
class(phs_channel_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine phs_channel_write
<<PHS base: procedures>>=
module subroutine phs_channel_write (object, unit)
class(phs_channel_t), intent(in) :: object
integer, intent(in), optional :: unit
integer :: u, j
u = given_output_unit (unit)
write (u, "(1x,I0)", advance="no") object%sf_channel
if (allocated (object%prop)) then
write (u, "(1x,A)") char (object%prop%to_string ())
else
write (u, *)
end if
if (allocated (object%eq)) then
do j = 1, size (object%eq)
call object%eq(j)%write (u)
end do
end if
end subroutine phs_channel_write
@ %def phs_channel_write
@ Identify the channel with an s-channel resonance.
Gfortran 7/8/9 bug: has to remain in the main module.
<<PHS base: phs channel: TBP>>=
procedure :: set_resonant => channel_set_resonant
<<PHS base: main procedures>>=
subroutine channel_set_resonant (channel, mass, width)
class(phs_channel_t), intent(inout) :: channel
real(default), intent(in) :: mass, width
allocate (resonance_t :: channel%prop)
select type (prop => channel%prop)
type is (resonance_t)
prop%mass = mass
prop%width = width
end select
end subroutine channel_set_resonant
@ %def channel_set_resonant
@ Identify the channel with an on-shell particle.
Gfortran 7/8/9 bug: has to remain in the main module.
<<PHS base: phs channel: TBP>>=
procedure :: set_on_shell => channel_set_on_shell
<<PHS base: main procedures>>=
subroutine channel_set_on_shell (channel, mass)
class(phs_channel_t), intent(inout) :: channel
real(default), intent(in) :: mass
allocate (on_shell_t :: channel%prop)
select type (prop => channel%prop)
type is (on_shell_t)
prop%mass = mass
end select
end subroutine channel_set_on_shell
@ %def channel_set_on_shell
@
\subsection{Property collection}
We can set up a list of all distinct channel properties for a given
set of channels.
<<PHS base: public>>=
public :: phs_channel_collection_t
<<PHS base: types>>=
type :: prop_entry_t
integer :: i = 0
class(channel_prop_t), allocatable :: prop
type(prop_entry_t), pointer :: next => null ()
end type prop_entry_t
type :: phs_channel_collection_t
integer :: n = 0
type(prop_entry_t), pointer :: first => null ()
contains
<<PHS base: phs channel collection: TBP>>
end type phs_channel_collection_t
@ %def prop_entry_t
@ %def phs_channel_collection_t
@ Finalizer for the list.
<<PHS base: phs channel collection: TBP>>=
procedure :: final => phs_channel_collection_final
<<PHS base: sub interfaces>>=
module subroutine phs_channel_collection_final (object)
class(phs_channel_collection_t), intent(inout) :: object
end subroutine phs_channel_collection_final
<<PHS base: procedures>>=
module subroutine phs_channel_collection_final (object)
class(phs_channel_collection_t), intent(inout) :: object
type(prop_entry_t), pointer :: entry
do while (associated (object%first))
entry => object%first
object%first => entry%next
deallocate (entry)
end do
end subroutine phs_channel_collection_final
@ %def phs_channel_collection_final
@ Output.
<<PHS base: phs channel collection: TBP>>=
procedure :: write => phs_channel_collection_write
<<PHS base: sub interfaces>>=
module subroutine phs_channel_collection_write (object, unit)
class(phs_channel_collection_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine phs_channel_collection_write
<<PHS base: procedures>>=
module subroutine phs_channel_collection_write (object, unit)
class(phs_channel_collection_t), intent(in) :: object
integer, intent(in), optional :: unit
type(prop_entry_t), pointer :: entry
integer :: u
u = given_output_unit (unit)
entry => object%first
do while (associated (entry))
if (allocated (entry%prop)) then
write (u, "(1x,I0,1x,A)") entry%i, char (entry%prop%to_string ())
else
write (u, "(1x,I0)") entry%i
end if
entry => entry%next
end do
end subroutine phs_channel_collection_write
@ %def phs_channel_collection_write
@ Push a new property to the stack if it is not yet included.
Simultaneously, set the [[sf_channel]] entry in the phase-space
channel object to the index of the matching entry, or the new entry if
there was no match.
<<PHS base: phs channel collection: TBP>>=
procedure :: push => phs_channel_collection_push
<<PHS base: sub interfaces>>=
module subroutine phs_channel_collection_push (coll, channel)
class(phs_channel_collection_t), intent(inout) :: coll
type(phs_channel_t), intent(inout) :: channel
end subroutine phs_channel_collection_push
<<PHS base: procedures>>=
module subroutine phs_channel_collection_push (coll, channel)
class(phs_channel_collection_t), intent(inout) :: coll
type(phs_channel_t), intent(inout) :: channel
type(prop_entry_t), pointer :: entry, new
if (associated (coll%first)) then
entry => coll%first
do
if (allocated (entry%prop)) then
if (allocated (channel%prop)) then
if (entry%prop == channel%prop) then
channel%sf_channel = entry%i
return
end if
end if
else if (.not. allocated (channel%prop)) then
channel%sf_channel = entry%i
return
end if
if (associated (entry%next)) then
entry => entry%next
else
exit
end if
end do
allocate (new)
entry%next => new
else
allocate (new)
coll%first => new
end if
coll%n = coll%n + 1
new%i = coll%n
channel%sf_channel = new%i
if (allocated (channel%prop)) then
allocate (new%prop, source = channel%prop)
end if
end subroutine phs_channel_collection_push
@ %def phs_channel_collection_push
@ Return the number of collected distinct channels.
<<PHS base: phs channel collection: TBP>>=
procedure :: get_n => phs_channel_collection_get_n
<<PHS base: sub interfaces>>=
module function phs_channel_collection_get_n (coll) result (n)
class(phs_channel_collection_t), intent(in) :: coll
integer :: n
end function phs_channel_collection_get_n
<<PHS base: procedures>>=
module function phs_channel_collection_get_n (coll) result (n)
class(phs_channel_collection_t), intent(in) :: coll
integer :: n
n = coll%n
end function phs_channel_collection_get_n
@ %def phs_channel_collection_get_n
@ Return a specific channel (property object).
<<PHS base: phs channel collection: TBP>>=
procedure :: get_entry => phs_channel_collection_get_entry
<<PHS base: sub interfaces>>=
module subroutine phs_channel_collection_get_entry (coll, i, prop)
class(phs_channel_collection_t), intent(in) :: coll
integer, intent(in) :: i
class(channel_prop_t), intent(out), allocatable :: prop
end subroutine phs_channel_collection_get_entry
<<PHS base: procedures>>=
module subroutine phs_channel_collection_get_entry (coll, i, prop)
class(phs_channel_collection_t), intent(in) :: coll
integer, intent(in) :: i
class(channel_prop_t), intent(out), allocatable :: prop
type(prop_entry_t), pointer :: entry
integer :: k
if (i > 0 .and. i <= coll%n) then
entry => coll%first
do k = 2, i
entry => entry%next
end do
if (allocated (entry%prop)) then
if (allocated (prop)) deallocate (prop)
allocate (prop, source = entry%prop)
end if
else
call msg_bug ("PHS channel collection: get entry: illegal index")
end if
end subroutine phs_channel_collection_get_entry
@ %def phs_channel_collection_get_entry
@
\subsection{Kinematics configuration}
Here, we store the universal information that is specifically relevant
for phase-space generation. It is a subset of the process data,
supplemented by basic information on phase-space parameterization
channels.
A concrete implementation will contain more data, that describe the
phase space in detail.
MD5 sums: the phase space setup depends on the process, it depends on
the model parameters (the masses, that is), and on the configuration
parameters. (It does not depend on the QCD setup.)
<<PHS base: public>>=
public :: phs_config_t
<<PHS base: types>>=
type, abstract :: phs_config_t
! private
type(string_t) :: id
integer :: n_in = 0
integer :: n_out = 0
integer :: n_tot = 0
integer :: n_state = 0
integer :: n_par = 0
integer :: n_channel = 0
real(default) :: sqrts = 0
logical :: sqrts_fixed = .true.
logical :: lab_is_cm = .true.
logical :: azimuthal_dependence = .false.
integer, dimension(:), allocatable :: dim_flat
logical :: provides_equivalences = .false.
logical :: provides_chains = .false.
logical :: vis_channels = .false.
integer, dimension(:), allocatable :: chain
class(model_data_t), pointer :: model => null ()
type(flavor_t), dimension(:,:), allocatable :: flv
type(phs_channel_t), dimension(:), allocatable :: channel
character(32) :: md5sum_process = ""
character(32) :: md5sum_model_par = ""
character(32) :: md5sum_phs_config = ""
integer :: nlo_type
contains
<<PHS base: phs config: TBP>>
end type phs_config_t
@ %def phs_config_t
@ Finalizer, deferred.
<<PHS base: phs config: TBP>>=
procedure (phs_config_final), deferred :: final
<<PHS base: interfaces>>=
abstract interface
subroutine phs_config_final (object)
import
class(phs_config_t), intent(inout) :: object
end subroutine phs_config_final
end interface
@ %def phs_config_final
@ Output. We provide an implementation for the output of the base-type
contents and an interface for the actual write method.
<<PHS base: phs config: TBP>>=
procedure (phs_config_write), deferred :: write
procedure :: base_write => phs_config_write
<<PHS base: sub interfaces>>=
module subroutine phs_config_write (object, unit, include_id)
class(phs_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
end subroutine phs_config_write
<<PHS base: procedures>>=
module subroutine phs_config_write (object, unit, include_id)
class(phs_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u, i, j
integer :: n_tot_flv
logical :: use_id
n_tot_flv = object%n_tot
u = given_output_unit (unit)
use_id = .true.; if (present (include_id)) use_id = include_id
if (use_id) write (u, "(3x,A,A,A)") "ID = '", char (object%id), "'"
write (u, "(3x,A,I0)") "n_in = ", object%n_in
write (u, "(3x,A,I0)") "n_out = ", object%n_out
write (u, "(3x,A,I0)") "n_tot = ", object%n_tot
write (u, "(3x,A,I0)") "n_state = ", object%n_state
write (u, "(3x,A,I0)") "n_par = ", object%n_par
write (u, "(3x,A,I0)") "n_channel = ", object%n_channel
write (u, "(3x,A," // FMT_19 // ")") "sqrts = ", object%sqrts
write (u, "(3x,A,L1)") "s_fixed = ", object%sqrts_fixed
write (u, "(3x,A,L1)") "lab_is_cm = ", object%lab_is_cm
write (u, "(3x,A,L1)") "azim.dep. = ", object%azimuthal_dependence
if (allocated (object%dim_flat)) then
write (u, "(3x,A,I0)") "flat dim. = ", object%dim_flat
end if
write (u, "(1x,A)") "Flavor combinations:"
do i = 1, object%n_state
write (u, "(3x,I0,':')", advance="no") i
! do j = 1, object%n_tot
do j = 1, n_tot_flv
write (u, "(1x,A)", advance="no") char (object%flv(j,i)%get_name ())
end do
write (u, "(A)")
end do
if (allocated (object%channel)) then
write (u, "(1x,A)") "Phase-space / structure-function channels:"
do i = 1, object%n_channel
write (u, "(3x,I0,':')", advance="no") i
call object%channel(i)%write (u)
end do
end if
if (object%md5sum_process /= "") then
write (u, "(3x,A,A,A)") "MD5 sum (process) = '", &
object%md5sum_process, "'"
end if
if (object%md5sum_model_par /= "") then
write (u, "(3x,A,A,A)") "MD5 sum (model par) = '", &
object%md5sum_model_par, "'"
end if
if (object%md5sum_phs_config /= "") then
write (u, "(3x,A,A,A)") "MD5 sum (phs config) = '", &
object%md5sum_phs_config, "'"
end if
end subroutine phs_config_write
@ %def phs_config_write
@ Similarly, a basic initializer and an interface. The model pointer is taken
as an argument; we may verify that this has the expected model name.
The intent is [[inout]]. We want to be able to set parameters in advance.
<<PHS base: phs config: TBP>>=
procedure :: init => phs_config_init
<<PHS base: sub interfaces>>=
module subroutine phs_config_init (phs_config, data, model)
class(phs_config_t), intent(inout) :: phs_config
type(process_constants_t), intent(in) :: data
class(model_data_t), intent(in), target :: model
end subroutine phs_config_init
<<PHS base: procedures>>=
module subroutine phs_config_init (phs_config, data, model)
class(phs_config_t), intent(inout) :: phs_config
type(process_constants_t), intent(in) :: data
class(model_data_t), intent(in), target :: model
integer :: i, j
phs_config%id = data%id
phs_config%n_in = data%n_in
phs_config%n_out = data%n_out
phs_config%n_tot = data%n_in + data%n_out
phs_config%n_state = data%n_flv
if (data%model_name == model%get_name ()) then
phs_config%model => model
else
call msg_bug ("phs_config_init: model name mismatch")
end if
allocate (phs_config%flv (phs_config%n_tot, phs_config%n_state))
do i = 1, phs_config%n_state
do j = 1, phs_config%n_tot
call phs_config%flv(j,i)%init (data%flv_state(j,i), &
phs_config%model)
end do
end do
phs_config%md5sum_process = data%md5sum
end subroutine phs_config_init
@ %def phs_config_init
@
WK 2018-04-05: This procedure appears to be redundant?
<<XXX PHS base: phs config: TBP>>=
procedure :: set_component_index => phs_config_set_component_index
<<XXX PHS base: procedures>>=
subroutine phs_config_set_component_index (phs_config, index)
class(phs_config_t), intent(inout) :: phs_config
integer, intent(in) :: index
type(string_t), dimension(:), allocatable :: id
type(string_t) :: suffix
integer :: i, n
suffix = var_str ('i') // int2string (index)
call split_string (phs_config%id, var_str ('_'), id)
phs_config%id = var_str ('')
n = size (id) - 1
do i = 1, n
phs_config%id = phs_config%id // id(i) // var_str ('_')
end do
phs_config%id = phs_config%id // suffix
end subroutine phs_config_set_component_index
@ %def phs_config_set_component_index
@ This procedure should complete the phase-space configuration. We
need the [[sqrts]] value as overall scale, which is known only after
the beams have been defined. The procedure should determine the number of
channels, their properties (if any), and allocate and fill the [[channel]]
array accordingly.
<<PHS base: phs config: TBP>>=
procedure (phs_config_configure), deferred :: configure
<<PHS base: interfaces>>=
abstract interface
subroutine phs_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, ignore_mismatch, &
nlo_type, subdir)
import
class(phs_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_config_configure
end interface
@ %def phs_config_configure
@ Manually assign structure-function channel indices to the phase-space
channel objects. (Used by a test routine.)
<<PHS base: phs config: TBP>>=
procedure :: set_sf_channel => phs_config_set_sf_channel
<<PHS base: sub interfaces>>=
module subroutine phs_config_set_sf_channel (phs_config, sf_channel)
class(phs_config_t), intent(inout) :: phs_config
integer, dimension(:), intent(in) :: sf_channel
end subroutine phs_config_set_sf_channel
<<PHS base: procedures>>=
module subroutine phs_config_set_sf_channel (phs_config, sf_channel)
class(phs_config_t), intent(inout) :: phs_config
integer, dimension(:), intent(in) :: sf_channel
phs_config%channel%sf_channel = sf_channel
end subroutine phs_config_set_sf_channel
@ %def phs_config_set_sf_channel
@ Collect new channels not yet in the collection from this phase-space
configuration object. At the same time, assign structure-function channels.
<<PHS base: phs config: TBP>>=
procedure :: collect_channels => phs_config_collect_channels
<<PHS base: sub interfaces>>=
module subroutine phs_config_collect_channels (phs_config, coll)
class(phs_config_t), intent(inout) :: phs_config
type(phs_channel_collection_t), intent(inout) :: coll
end subroutine phs_config_collect_channels
<<PHS base: procedures>>=
module subroutine phs_config_collect_channels (phs_config, coll)
class(phs_config_t), intent(inout) :: phs_config
type(phs_channel_collection_t), intent(inout) :: coll
integer :: c
do c = 1, phs_config%n_channel
call coll%push (phs_config%channel(c))
end do
end subroutine phs_config_collect_channels
@ %def phs_config_collect_channels
@ Compute the MD5 sum. We abuse the [[write]] method. In
type implementations, [[write]] should only display information that is
relevant for the MD5 sum. The data include the process MD5 sum which is taken
from the process constants, and the MD5 sum of the model parameters. This may
change, so it is computed here.
<<PHS base: phs config: TBP>>=
procedure :: compute_md5sum => phs_config_compute_md5sum
<<PHS base: sub interfaces>>=
module subroutine phs_config_compute_md5sum (phs_config, include_id)
class(phs_config_t), intent(inout) :: phs_config
logical, intent(in), optional :: include_id
end subroutine phs_config_compute_md5sum
<<PHS base: procedures>>=
module subroutine phs_config_compute_md5sum (phs_config, include_id)
class(phs_config_t), intent(inout) :: phs_config
logical, intent(in), optional :: include_id
integer :: u
phs_config%md5sum_model_par = phs_config%model%get_parameters_md5sum ()
phs_config%md5sum_phs_config = ""
u = free_unit ()
open (u, status = "scratch", action = "readwrite")
call phs_config%write (u, include_id)
rewind (u)
phs_config%md5sum_phs_config = md5sum (u)
close (u)
end subroutine phs_config_compute_md5sum
@ %def phs_config_compute_md5sum
@ Print an informative message after phase-space configuration.
<<PHS base: phs config: TBP>>=
procedure (phs_startup_message), deferred :: startup_message
procedure :: base_startup_message => phs_startup_message
<<PHS base: sub interfaces>>=
module subroutine phs_startup_message (phs_config, unit)
class(phs_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
end subroutine phs_startup_message
<<PHS base: procedures>>=
module subroutine phs_startup_message (phs_config, unit)
class(phs_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
write (msg_buffer, "(A,3(1x,I0,1x,A))") &
"Phase space:", &
phs_config%n_channel, "channels,", &
phs_config%n_par, "dimensions"
call msg_message (unit = unit)
end subroutine phs_startup_message
@ %def phs_startup_message
@ This procedure should be implemented such that the phase-space
configuration object allocates a phase-space instance of matching type.
<<PHS base: phs config: TBP>>=
procedure (phs_config_allocate_instance), nopass, deferred :: &
allocate_instance
<<PHS base: interfaces>>=
abstract interface
subroutine phs_config_allocate_instance (phs)
import
class(phs_t), intent(inout), pointer :: phs
end subroutine phs_config_allocate_instance
end interface
@ %def phs_config_allocate_instance
@
\subsection{Extract data}
Return the number of MC input parameters.
<<PHS base: phs config: TBP>>=
procedure :: get_n_par => phs_config_get_n_par
<<PHS base: sub interfaces>>=
module function phs_config_get_n_par (phs_config) result (n)
class(phs_config_t), intent(in) :: phs_config
integer :: n
end function phs_config_get_n_par
<<PHS base: procedures>>=
module function phs_config_get_n_par (phs_config) result (n)
class(phs_config_t), intent(in) :: phs_config
integer :: n
n = phs_config%n_par
end function phs_config_get_n_par
@ %def phs_config_get_n_par
@ Return dimensions (parameter indices) for which the phase-space
dimension is flat, so integration and event generation can be simplified.
<<PHS base: phs config: TBP>>=
procedure :: get_flat_dimensions => phs_config_get_flat_dimensions
<<PHS base: sub interfaces>>=
module function phs_config_get_flat_dimensions &
(phs_config) result (dim_flat)
class(phs_config_t), intent(in) :: phs_config
integer, dimension(:), allocatable :: dim_flat
end function phs_config_get_flat_dimensions
<<PHS base: procedures>>=
module function phs_config_get_flat_dimensions &
(phs_config) result (dim_flat)
class(phs_config_t), intent(in) :: phs_config
integer, dimension(:), allocatable :: dim_flat
if (allocated (phs_config%dim_flat)) then
allocate (dim_flat (size (phs_config%dim_flat)))
dim_flat = phs_config%dim_flat
else
allocate (dim_flat (0))
end if
end function phs_config_get_flat_dimensions
@ %def phs_config_get_flat_dimensions
@ Return the number of phase-space channels.
<<PHS base: phs config: TBP>>=
procedure :: get_n_channel => phs_config_get_n_channel
<<PHS base: sub interfaces>>=
module function phs_config_get_n_channel (phs_config) result (n)
class(phs_config_t), intent(in) :: phs_config
integer :: n
end function phs_config_get_n_channel
<<PHS base: procedures>>=
module function phs_config_get_n_channel (phs_config) result (n)
class(phs_config_t), intent(in) :: phs_config
integer :: n
n = phs_config%n_channel
end function phs_config_get_n_channel
@ %def phs_config_get_n_channel
@ Return the structure-function channel that corresponds to the
phase-space channel [[c]]. If the channel array is not allocated (which
happens if there is no structure function), return zero.
<<PHS base: phs config: TBP>>=
procedure :: get_sf_channel => phs_config_get_sf_channel
<<PHS base: sub interfaces>>=
module function phs_config_get_sf_channel (phs_config, c) result (c_sf)
class(phs_config_t), intent(in) :: phs_config
integer, intent(in) :: c
integer :: c_sf
end function phs_config_get_sf_channel
<<PHS base: procedures>>=
module function phs_config_get_sf_channel (phs_config, c) result (c_sf)
class(phs_config_t), intent(in) :: phs_config
integer, intent(in) :: c
integer :: c_sf
if (allocated (phs_config%channel)) then
c_sf = phs_config%channel(c)%sf_channel
else
c_sf = 0
end if
end function phs_config_get_sf_channel
@ %def phs_config_get_sf_channel
@ Return the mass(es) of the incoming particle(s). We take the first flavor
combination in the array, assuming that masses must be degenerate among
flavors.
<<PHS base: phs config: TBP>>=
procedure :: get_masses_in => phs_config_get_masses_in
<<PHS base: sub interfaces>>=
module subroutine phs_config_get_masses_in (phs_config, m)
class(phs_config_t), intent(in) :: phs_config
real(default), dimension(:), intent(out) :: m
end subroutine phs_config_get_masses_in
<<PHS base: procedures>>=
module subroutine phs_config_get_masses_in (phs_config, m)
class(phs_config_t), intent(in) :: phs_config
real(default), dimension(:), intent(out) :: m
integer :: i
do i = 1, phs_config%n_in
m(i) = phs_config%flv(i,1)%get_mass ()
end do
end subroutine phs_config_get_masses_in
@ %def phs_config_get_masses_in
@ Return the MD5 sum of the configuration.
<<PHS base: phs config: TBP>>=
procedure :: get_md5sum => phs_config_get_md5sum
<<PHS base: sub interfaces>>=
module function phs_config_get_md5sum (phs_config) result (md5sum)
class(phs_config_t), intent(in) :: phs_config
character(32) :: md5sum
end function phs_config_get_md5sum
<<PHS base: procedures>>=
module function phs_config_get_md5sum (phs_config) result (md5sum)
class(phs_config_t), intent(in) :: phs_config
character(32) :: md5sum
md5sum = phs_config%md5sum_phs_config
end function phs_config_get_md5sum
@ %def phs_config_get_md5sum
@
\subsection{Phase-space point instance}
The [[phs_t]] object holds the workspace for phase-space generation.
In the base object, we have the MC input parameters [[r]] and the
Jacobian factor [[f]], for each channel, and the incoming and outgoing
momenta.
Note: The [[active_channel]] array is not used yet, all elements are
initialized with [[.true.]]. It should be touched by the integrator if it
decides to drop irrelevant channels.
<<PHS base: public>>=
public :: phs_t
<<PHS base: types>>=
type, abstract :: phs_t
class(phs_config_t), pointer :: config => null ()
logical :: r_defined = .false.
integer :: selected_channel = 0
logical, dimension(:), allocatable :: active_channel
real(default), dimension(:,:), allocatable :: r
real(default), dimension(:), allocatable :: f
real(default), dimension(:), allocatable :: m_in
real(default), dimension(:), allocatable :: m_out
real(default) :: flux = 0
real(default) :: volume = 0
type(lorentz_transformation_t) :: lt_cm_to_lab
logical :: p_defined = .false.
real(default) :: sqrts_hat = 0
type(vector4_t), dimension(:), allocatable :: p
logical :: q_defined = .false.
type(vector4_t), dimension(:), allocatable :: q
contains
<<PHS base: phs: TBP>>
end type phs_t
@ %def phs_t
@ Output. Since phase space may get complicated, we include a
[[verbose]] option for the abstract [[write]] procedure.
<<PHS base: phs: TBP>>=
procedure (phs_write), deferred :: write
<<PHS base: interfaces>>=
abstract interface
subroutine phs_write (object, unit, verbose)
import
class(phs_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine phs_write
end interface
@ %def phs_write
@ This procedure can be called to print the contents of the base type.
<<PHS base: phs: TBP>>=
procedure :: base_write => phs_base_write
<<PHS base: sub interfaces>>=
module subroutine phs_base_write (object, unit)
class(phs_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine phs_base_write
<<PHS base: procedures>>=
module subroutine phs_base_write (object, unit)
class(phs_t), intent(in) :: object
integer, intent(in), optional :: unit
integer :: u, c, i
u = given_output_unit (unit)
write (u, "(1x,A)", advance="no") "Partonic phase space: parameters"
if (object%r_defined) then
write (u, *)
else
write (u, "(1x,A)") "[undefined]"
end if
write (u, "(3x,A,999(1x," // FMT_19 // "))") "m_in =", object%m_in
write (u, "(3x,A,999(1x," // FMT_19 // "))") "m_out =", object%m_out
write (u, "(3x,A," // FMT_19 // ")") "Flux = ", object%flux
write (u, "(3x,A," // FMT_19 // ")") "Volume = ", object%volume
if (allocated (object%f)) then
do c = 1, size (object%r, 2)
write (u, "(1x,A,I0,A)", advance="no") "Channel #", c, ":"
if (c == object%selected_channel) then
write (u, "(1x,A)") "[selected]"
else
write (u, *)
end if
write (u, "(3x,A)", advance="no") "r ="
do i = 1, size (object%r, 1)
write (u, "(1x,F9.7)", advance="no") object%r(i,c)
end do
write (u, *)
write (u, "(3x,A,1x,ES13.7)") "f =", object%f(c)
end do
end if
write (u, "(1x,A)") "Partonic phase space: momenta"
if (object%p_defined) then
write (u, "(3x,A," // FMT_19 // ")") "sqrts = ", object%sqrts_hat
end if
write (u, "(1x,A)", advance="no") "Incoming:"
if (object%p_defined) then
write (u, *)
else
write (u, "(1x,A)") "[undefined]"
end if
if (allocated (object%p)) then
do i = 1, size (object%p)
call vector4_write (object%p(i), u)
end do
end if
write (u, "(1x,A)", advance="no") "Outgoing:"
if (object%q_defined) then
write (u, *)
else
write (u, "(1x,A)") "[undefined]"
end if
if (allocated (object%q)) then
do i = 1, size (object%q)
call vector4_write (object%q(i), u)
end do
end if
if (object%p_defined .and. .not. object%config%lab_is_cm) then
write (u, "(1x,A)") "Transformation c.m -> lab frame"
call lorentz_transformation_write (object%lt_cm_to_lab, u)
end if
end subroutine phs_base_write
@ %def phs_base_write
@ Finalizer. The base type does not need it, but extensions may.
<<PHS base: phs: TBP>>=
procedure (phs_final), deferred :: final
<<PHS base: interfaces>>=
abstract interface
subroutine phs_final (object)
import
class(phs_t), intent(inout) :: object
end subroutine phs_final
end interface
@ %def phs_final
@ Initializer. Everything should be contained in the [[process_data]]
configuration object, so we can require a universal interface.
<<PHS base: phs: TBP>>=
procedure (phs_init), deferred :: init
<<PHS base: interfaces>>=
abstract interface
subroutine phs_init (phs, phs_config)
import
class(phs_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_init
end interface
@ %def phs_init
@ The base version will just allocate the arrays. It should be called
at the beginning of the implementation of [[phs_init]].
<<PHS base: phs: TBP>>=
procedure :: base_init => phs_base_init
<<PHS base: sub interfaces>>=
module subroutine phs_base_init (phs, phs_config)
class(phs_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_base_init
<<PHS base: procedures>>=
module subroutine phs_base_init (phs, phs_config)
class(phs_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
phs%config => phs_config
allocate (phs%active_channel (phs%config%n_channel))
phs%active_channel = .true.
allocate (phs%r (phs%config%n_par, phs%config%n_channel)); phs%r = 0
allocate (phs%f (phs%config%n_channel)); phs%f = 0
allocate (phs%p (phs%config%n_in))
allocate (phs%m_in (phs%config%n_in), &
source = phs_config%flv(:phs_config%n_in, 1)%get_mass ())
allocate (phs%q (phs%config%n_out))
allocate (phs%m_out (phs%config%n_out), &
source = phs_config%flv(phs_config%n_in+1:, 1)%get_mass ())
call phs%compute_flux ()
end subroutine phs_base_init
@ %def phs_base_init
@ Manually select a channel.
<<PHS base: phs: TBP>>=
procedure :: select_channel => phs_base_select_channel
<<PHS base: sub interfaces>>=
module subroutine phs_base_select_channel (phs, channel)
class(phs_t), intent(inout) :: phs
integer, intent(in), optional :: channel
end subroutine phs_base_select_channel
<<PHS base: procedures>>=
module subroutine phs_base_select_channel (phs, channel)
class(phs_t), intent(inout) :: phs
integer, intent(in), optional :: channel
if (present (channel)) then
phs%selected_channel = channel
else
phs%selected_channel = 0
end if
end subroutine phs_base_select_channel
@ %def phs_base_select_channel
@ Set incoming momenta. Assume that array shapes match. If
requested, compute the Lorentz transformation from the c.m.\ to the
lab frame and apply that transformation to the incoming momenta.
In the c.m.\ frame, the sum of three-momenta is zero. In a scattering
process, the $z$ axis is the direction of the first beam, the second
beam is along the negative $z$ axis. The transformation from the
c.m.\ to the lab frame is a rotation from the $z$ axis to the boost
axis followed by a boost, such that the c.m.\ momenta are transformed
into the lab-frame momenta. In a decay process, we just boost along
the flight direction, without rotation.
<<PHS base: phs: TBP>>=
procedure :: set_incoming_momenta => phs_set_incoming_momenta
<<PHS base: sub interfaces>>=
module subroutine phs_set_incoming_momenta (phs, p)
class(phs_t), intent(inout) :: phs
type(vector4_t), dimension(:), intent(in) :: p
end subroutine phs_set_incoming_momenta
<<PHS base: procedures>>=
module subroutine phs_set_incoming_momenta (phs, p)
class(phs_t), intent(inout) :: phs
type(vector4_t), dimension(:), intent(in) :: p
type(vector4_t) :: p0, p1
type(lorentz_transformation_t) :: lt0
integer :: i
phs%p = p
if (phs%config%lab_is_cm) then
phs%sqrts_hat = phs%config%sqrts
phs%p = p
phs%lt_cm_to_lab = identity
else
p0 = sum (p)
if (phs%config%sqrts_fixed) then
phs%sqrts_hat = phs%config%sqrts
else
phs%sqrts_hat = p0 ** 1
end if
lt0 = boost (p0, phs%sqrts_hat)
select case (phs%config%n_in)
case (1)
phs%lt_cm_to_lab = lt0
case (2)
p1 = inverse (lt0) * p(1)
phs%lt_cm_to_lab = lt0 * rotation_to_2nd (3, space_part (p1))
end select
phs%p = inverse (phs%lt_cm_to_lab) * p
end if
phs%p_defined = .true.
end subroutine phs_set_incoming_momenta
@ %def phs_set_incoming_momenta
@ Set outgoing momenta. Assume that array shapes match. The incoming
momenta must be known, so we can apply the Lorentz transformation from
c.m.\ to lab (inverse) to the momenta.
<<PHS base: phs: TBP>>=
procedure :: set_outgoing_momenta => phs_set_outgoing_momenta
<<PHS base: sub interfaces>>=
module subroutine phs_set_outgoing_momenta (phs, q)
class(phs_t), intent(inout) :: phs
type(vector4_t), dimension(:), intent(in) :: q
end subroutine phs_set_outgoing_momenta
<<PHS base: procedures>>=
module subroutine phs_set_outgoing_momenta (phs, q)
class(phs_t), intent(inout) :: phs
type(vector4_t), dimension(:), intent(in) :: q
integer :: i
if (phs%p_defined) then
if (phs%config%lab_is_cm) then
phs%q = q
else
phs%q = inverse (phs%lt_cm_to_lab) * q
end if
phs%q_defined = .true.
end if
end subroutine phs_set_outgoing_momenta
@ %def phs_set_outgoing_momenta
@ Return outgoing momenta. Apply the c.m.\ to lab transformation if
necessary.
<<PHS base: phs: TBP>>=
procedure :: get_outgoing_momenta => phs_get_outgoing_momenta
<<PHS base: sub interfaces>>=
module subroutine phs_get_outgoing_momenta (phs, q)
class(phs_t), intent(in) :: phs
type(vector4_t), dimension(:), intent(out) :: q
end subroutine phs_get_outgoing_momenta
<<PHS base: procedures>>=
module subroutine phs_get_outgoing_momenta (phs, q)
class(phs_t), intent(in) :: phs
type(vector4_t), dimension(:), intent(out) :: q
if (phs%p_defined .and. phs%q_defined) then
if (phs%config%lab_is_cm) then
q = phs%q
else
q = phs%lt_cm_to_lab * phs%q
end if
else
q = vector4_null
end if
end subroutine phs_get_outgoing_momenta
@ %def phs_get_outgoing_momenta
@
<<PHS base: phs: TBP>>=
procedure :: lab_is_cm => phs_lab_is_cm
<<PHS base: sub interfaces>>=
module function phs_lab_is_cm (phs) result (lab_is_cm)
logical :: lab_is_cm
class(phs_t), intent(in) :: phs
end function phs_lab_is_cm
<<PHS base: procedures>>=
module function phs_lab_is_cm (phs) result (lab_is_cm)
logical :: lab_is_cm
class(phs_t), intent(in) :: phs
lab_is_cm = phs%config%lab_is_cm
end function phs_lab_is_cm
@ %def phs_lab_is_cm
@
<<PHS base: phs: TBP>>=
procedure :: get_n_tot => phs_get_n_tot
<<PHS base: sub interfaces>>=
elemental module function phs_get_n_tot (phs) result (n_tot)
integer :: n_tot
class(phs_t), intent(in) :: phs
end function phs_get_n_tot
<<PHS base: procedures>>=
elemental module function phs_get_n_tot (phs) result (n_tot)
integer :: n_tot
class(phs_t), intent(in) :: phs
n_tot = phs%config%n_tot
end function phs_get_n_tot
@ %def phs_get_n_tot
@
<<PHS base: phs: TBP>>=
procedure :: set_lorentz_transformation => phs_set_lorentz_transformation
<<PHS base: sub interfaces>>=
module subroutine phs_set_lorentz_transformation (phs, lt)
class(phs_t), intent(inout) :: phs
type(lorentz_transformation_t), intent(in) :: lt
end subroutine phs_set_lorentz_transformation
<<PHS base: procedures>>=
module subroutine phs_set_lorentz_transformation (phs, lt)
class(phs_t), intent(inout) :: phs
type(lorentz_transformation_t), intent(in) :: lt
phs%lt_cm_to_lab = lt
end subroutine phs_set_lorentz_transformation
@ %def phs_set_lorentz_transformation
@
<<PHS base: phs: TBP>>=
procedure :: get_lorentz_transformation => phs_get_lorentz_transformation
<<PHS base: sub interfaces>>=
module function phs_get_lorentz_transformation (phs) result (lt)
type(lorentz_transformation_t) :: lt
class(phs_t), intent(in) :: phs
end function phs_get_lorentz_transformation
<<PHS base: procedures>>=
module function phs_get_lorentz_transformation (phs) result (lt)
type(lorentz_transformation_t) :: lt
class(phs_t), intent(in) :: phs
lt = phs%lt_cm_to_lab
end function phs_get_lorentz_transformation
@ %def phs_get_lorentz_transformation
@ Return the input parameter array for a channel.
<<PHS base: phs: TBP>>=
procedure :: get_mcpar => phs_get_mcpar
<<PHS base: sub interfaces>>=
module subroutine phs_get_mcpar (phs, c, r)
class(phs_t), intent(in) :: phs
integer, intent(in) :: c
real(default), dimension(:), intent(out) :: r
end subroutine phs_get_mcpar
<<PHS base: procedures>>=
module subroutine phs_get_mcpar (phs, c, r)
class(phs_t), intent(in) :: phs
integer, intent(in) :: c
real(default), dimension(:), intent(out) :: r
if (phs%r_defined) then
r = phs%r(:,c)
else
r = 0
end if
end subroutine phs_get_mcpar
@ %def phs_get_mcpar
@ Return the Jacobian factor for a channel.
<<PHS base: phs: TBP>>=
procedure :: get_f => phs_get_f
<<PHS base: sub interfaces>>=
module function phs_get_f (phs, c) result (f)
class(phs_t), intent(in) :: phs
integer, intent(in) :: c
real(default) :: f
end function phs_get_f
<<PHS base: procedures>>=
module function phs_get_f (phs, c) result (f)
class(phs_t), intent(in) :: phs
integer, intent(in) :: c
real(default) :: f
if (phs%r_defined) then
f = phs%f(c)
else
f = 0
end if
end function phs_get_f
@ %def phs_get_f
@ Return the overall factor, which is the product of the flux factor for the
incoming partons and the phase-space volume for the outgoing partons.
<<PHS base: phs: TBP>>=
procedure :: get_overall_factor => phs_get_overall_factor
<<PHS base: sub interfaces>>=
module function phs_get_overall_factor (phs) result (f)
class(phs_t), intent(in) :: phs
real(default) :: f
end function phs_get_overall_factor
<<PHS base: procedures>>=
module function phs_get_overall_factor (phs) result (f)
class(phs_t), intent(in) :: phs
real(default) :: f
f = phs%flux * phs%volume
end function phs_get_overall_factor
@ %def phs_get_overall_factor
@ Compute flux factor. We do this during initialization (when the
incoming momenta [[p]] are undefined), unless [[sqrts]] is variable. We do
this again once for each phase-space point, but then we skip the calculation
if [[sqrts]] is fixed.
There are three different flux factors.
\begin{enumerate}
\item
For a decaying massive particle, the factor is
\begin{equation}
f = (2\pi)^4 / (2M)
\end{equation}
\item
For a $2\to n$ scattering process with $n>1$, the factor is
\begin{equation}
f = (2\pi)^4 / (2\sqrt{\lambda})
\end{equation}
where for massless incoming particles, $\sqrt{\lambda} = s$.
\item For a $2\to 1$ on-shell production process, the factor includes
an extra $1/(2\pi)^3$ factor and a $1/m^2$ factor from the
phase-space delta function $\delta (x_1x_2 - m^2/s)$, which
originate from the one-particle phase space that we integrate out.
\begin{equation}
f = 2\pi / (2s m^2)
\end{equation}
The delta function is handled by the structure-function
parameterization.
\end{enumerate}
<<PHS base: phs: TBP>>=
procedure :: compute_flux => phs_compute_flux
procedure :: compute_base_flux => phs_compute_flux
<<PHS base: sub interfaces>>=
module subroutine phs_compute_flux (phs)
class(phs_t), intent(inout) :: phs
end subroutine phs_compute_flux
<<PHS base: procedures>>=
module subroutine phs_compute_flux (phs)
class(phs_t), intent(inout) :: phs
real(default) :: s_hat, lda
select case (phs%config%n_in)
case (1)
if (.not. phs%p_defined) then
phs%flux = twopi4 / (2 * phs%m_in(1))
end if
case (2)
if (phs%p_defined) then
if (phs%config%sqrts_fixed) then
return
else
s_hat = sum (phs%p) ** 2
end if
else
if (phs%config%sqrts_fixed) then
s_hat = phs%config%sqrts ** 2
else
return
end if
end if
select case (phs%config%n_out)
case (2:)
lda = lambda (s_hat, phs%m_in(1) ** 2, phs%m_in(2) ** 2)
if (lda > 0) then
phs%flux = conv * twopi4 / (2 * sqrt (lda))
else
phs%flux = 0
end if
case (1)
phs%flux = conv * twopi &
/ (2 * phs%config%sqrts ** 2 * phs%m_out(1) ** 2)
case default
phs%flux = 0
end select
end select
end subroutine phs_compute_flux
@ %def phs_compute_flux
@ Evaluate the phase-space point for a particular channel and compute momenta,
Jacobian, and phase-space volume. This is, of course, deferred to
the implementation.
<<PHS base: phs: TBP>>=
procedure (phs_evaluate_selected_channel), deferred :: &
evaluate_selected_channel
<<PHS base: interfaces>>=
abstract interface
subroutine phs_evaluate_selected_channel (phs, c_in, r_in)
import
class(phs_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), dimension(:), intent(in) :: r_in
end subroutine phs_evaluate_selected_channel
end interface
@ %def phs_evaluate_selected_channel
@ Compute the inverse mappings to completely fill the [[r]] and [[f]] arrays,
for the non-selected channels.
<<PHS base: phs: TBP>>=
procedure (phs_evaluate_other_channels), deferred :: &
evaluate_other_channels
<<PHS base: interfaces>>=
abstract interface
subroutine phs_evaluate_other_channels (phs, c_in)
import
class(phs_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_evaluate_other_channels
end interface
@ %def phs_evaluate_other_channels
@ Inverse evaluation. If all momenta are known, we compute the
inverse mappings to fill the [[r]] and [[f]] arrays.
<<PHS base: phs: TBP>>=
procedure (phs_inverse), deferred :: inverse
<<PHS base: interfaces>>=
abstract interface
subroutine phs_inverse (phs)
import
class(phs_t), intent(inout) :: phs
end subroutine phs_inverse
end interface
@ %def phs_inverse
@
<<PHS base: phs: TBP>>=
procedure :: get_sqrts => phs_get_sqrts
<<PHS base: sub interfaces>>=
module function phs_get_sqrts (phs) result (sqrts)
real(default) :: sqrts
class(phs_t), intent(in) :: phs
end function phs_get_sqrts
<<PHS base: procedures>>=
module function phs_get_sqrts (phs) result (sqrts)
real(default) :: sqrts
class(phs_t), intent(in) :: phs
sqrts = phs%config%sqrts
end function phs_get_sqrts
@ %def phs_get_sqrts
@
\subsubsection{Uniform angular distribution}
These procedures implement the uniform angular distribution, generated
from two parameters $x_1$ and $x_2$:
\begin{equation}
\cos\theta = 1 - 2x_1, \qquad \phi = 2\pi x_2
\end{equation}
We generate a rotation (Lorentz transformation) which rotates the
positive $z$ axis into this point on the unit sphere. This rotation
is applied to the [[p]] momenta, which are assumed to be
back-to-back, on-shell, and with the correct mass.
We do not compute a Jacobian (constant). The uniform distribution is
assumed to be normalized.
<<PHS base: public>>=
public :: compute_kinematics_solid_angle
<<PHS base: sub interfaces>>=
module subroutine compute_kinematics_solid_angle (p, q, x)
type(vector4_t), dimension(2), intent(in) :: p
type(vector4_t), dimension(2), intent(out) :: q
real(default), dimension(2), intent(in) :: x
end subroutine compute_kinematics_solid_angle
<<PHS base: procedures>>=
module subroutine compute_kinematics_solid_angle (p, q, x)
type(vector4_t), dimension(2), intent(in) :: p
type(vector4_t), dimension(2), intent(out) :: q
real(default), dimension(2), intent(in) :: x
real(default) :: ct, st, phi
type(lorentz_transformation_t) :: rot
integer :: i
ct = 1 - 2*x(1)
st = sqrt (1 - ct**2)
phi = twopi * x(2)
rot = rotation (phi, 3) * rotation (ct, st, 2)
do i = 1, 2
q(i) = rot * p(i)
end do
end subroutine compute_kinematics_solid_angle
@ %def compute_kinematics_solid_angle
@ This is the inverse transformation. We assume that the outgoing
momenta are rotated versions of the incoming momenta, back-to-back.
Thus, we determine the angles from $q(1)$ alone. [[p]] is unused.
<<PHS base: public>>=
public :: inverse_kinematics_solid_angle
<<PHS base: sub interfaces>>=
module subroutine inverse_kinematics_solid_angle (p, q, x)
type(vector4_t), dimension(:), intent(in) :: p
type(vector4_t), dimension(2), intent(in) :: q
real(default), dimension(2), intent(out) :: x
end subroutine inverse_kinematics_solid_angle
<<PHS base: procedures>>=
module subroutine inverse_kinematics_solid_angle (p, q, x)
type(vector4_t), dimension(:), intent(in) :: p
type(vector4_t), dimension(2), intent(in) :: q
real(default), dimension(2), intent(out) :: x
real(default) :: ct, phi
ct = polar_angle_ct (q(1))
phi = azimuthal_angle (q(1))
x(1) = (1 - ct) / 2
x(2) = phi / twopi
end subroutine inverse_kinematics_solid_angle
@ %def inverse_kinematics_solid_angle
@
\subsection{Auxiliary stuff}
The [[pacify]] subroutine, which is provided by the Lorentz module,
has the purpose of setting numbers to zero which are (by comparing
with a [[tolerance]] parameter) considered equivalent with zero. This
is useful for numerical checks.
<<PHS base: public>>=
public :: pacify
<<PHS base: interfaces>>=
interface pacify
module procedure pacify_phs
end interface pacify
<<PHS base: sub interfaces>>=
module subroutine pacify_phs (phs)
class(phs_t), intent(inout) :: phs
end subroutine pacify_phs
<<PHS base: procedures>>=
module subroutine pacify_phs (phs)
class(phs_t), intent(inout) :: phs
if (phs%p_defined) then
call pacify (phs%p, 30 * epsilon (1._default) * phs%config%sqrts)
call pacify (phs%lt_cm_to_lab, 30 * epsilon (1._default))
end if
if (phs%q_defined) then
call pacify (phs%q, 30 * epsilon (1._default) * phs%config%sqrts)
end if
end subroutine pacify_phs
@ %def pacify
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_base_ut.f90]]>>=
<<File header>>
module phs_base_ut
use unit_tests
use phs_base_uti
<<Standard module head>>
<<PHS base: public test>>
<<PHS base: public test auxiliary>>
contains
<<PHS base: test driver>>
end module phs_base_ut
@ %def phs_base_ut
@
<<[[phs_base_uti.f90]]>>=
<<File header>>
module phs_base_uti
<<Use kinds>>
<<Use strings>>
use diagnostics
use io_units
use format_defs, only: FMT_19
use physics_defs, only: BORN
use lorentz
use flavors
use model_data
use process_constants
use phs_base
<<Standard module head>>
<<PHS base: public test auxiliary>>
<<PHS base: test declarations>>
<<PHS base: test types>>
contains
<<PHS base: tests>>
<<PHS base: test auxiliary>>
end module phs_base_uti
@ %def phs_base_ut
@ API: driver for the unit tests below.
<<PHS base: public test>>=
public :: phs_base_test
<<PHS base: test driver>>=
subroutine phs_base_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS base: execute tests>>
end subroutine phs_base_test
@ %def phs_base_test
@
\subsubsection{Test process data}
We provide a procedure that initializes a test case for the process
constants. This set of process data contains just the minimal
contents that we need for the phase space. The rest is left
uninitialized.
<<PHS base: public test auxiliary>>=
public :: init_test_process_data
<<PHS base: test auxiliary>>=
subroutine init_test_process_data (id, data)
type(process_constants_t), intent(out) :: data
type(string_t), intent(in), optional :: id
if (present (id)) then
data%id = id
else
data%id = "testproc"
end if
data%model_name = "Test"
data%n_in = 2
data%n_out = 2
data%n_flv = 1
allocate (data%flv_state (data%n_in + data%n_out, data%n_flv))
data%flv_state = 25
end subroutine init_test_process_data
@ %def init_test_process_data
@ This is the variant for a decay process.
<<PHS base: public test auxiliary>>=
public :: init_test_decay_data
<<PHS base: test auxiliary>>=
subroutine init_test_decay_data (id, data)
type(process_constants_t), intent(out) :: data
type(string_t), intent(in), optional :: id
if (present (id)) then
data%id = id
else
data%id = "testproc"
end if
data%model_name = "Test"
data%n_in = 1
data%n_out = 2
data%n_flv = 1
allocate (data%flv_state (data%n_in + data%n_out, data%n_flv))
data%flv_state(:,1) = [25, 6, -6]
end subroutine init_test_decay_data
@ %def init_test_decay_data
@
\subsubsection{Test kinematics configuration}
This is a trivial implementation of the [[phs_config_t]] configuration object.
<<PHS base: public test auxiliary>>=
public :: phs_test_config_t
<<PHS base: test types>>=
type, extends (phs_config_t) :: phs_test_config_t
logical :: create_equivalences = .false.
contains
procedure :: final => phs_test_config_final
procedure :: write => phs_test_config_write
procedure :: configure => phs_test_config_configure
procedure :: startup_message => phs_test_config_startup_message
procedure, nopass :: allocate_instance => phs_test_config_allocate_instance
end type phs_test_config_t
@ %def phs_test_config_t
@ The finalizer is empty.
<<PHS base: test auxiliary>>=
subroutine phs_test_config_final (object)
class(phs_test_config_t), intent(inout) :: object
end subroutine phs_test_config_final
@ %def phs_test_config_final
@ The [[lab_is_cm]] parameter is not tested here; we defer this to the
[[phs_single]] implementation.
<<PHS base: test auxiliary>>=
subroutine phs_test_config_write (object, unit, include_id)
class(phs_test_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u
u = given_output_unit (unit)
write (u, "(1x,A)") "Partonic phase-space configuration:"
call object%base_write (unit)
end subroutine phs_test_config_write
subroutine phs_test_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_test_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
phs_config%n_channel = 2
phs_config%n_par = 2
phs_config%sqrts = sqrts
if (.not. present (nlo_type)) &
phs_config%nlo_type = BORN
if (present (sqrts_fixed)) then
phs_config%sqrts_fixed = sqrts_fixed
end if
if (present (lab_is_cm)) then
phs_config%lab_is_cm = lab_is_cm
end if
if (present (azimuthal_dependence)) then
phs_config%azimuthal_dependence = azimuthal_dependence
end if
if (allocated (phs_config%channel)) deallocate (phs_config%channel)
allocate (phs_config%channel (phs_config%n_channel))
if (phs_config%create_equivalences) then
call setup_test_equivalences (phs_config)
call setup_test_channel_props (phs_config)
end if
call phs_config%compute_md5sum ()
end subroutine phs_test_config_configure
@ %def phs_test_config_write
@ %def phs_test_config_configure
@ If requested, we make up an arbitrary set of equivalences.
<<PHS base: test auxiliary>>=
subroutine setup_test_equivalences (phs_config)
class(phs_test_config_t), intent(inout) :: phs_config
integer :: i
associate (channel => phs_config%channel(1))
allocate (channel%eq (2))
do i = 1, size (channel%eq)
call channel%eq(i)%init (phs_config%n_par)
end do
associate (eq => channel%eq(1))
eq%c = 1; eq%perm = [1, 2]; eq%mode = [EQ_IDENTITY, EQ_SYMMETRIC]
end associate
associate (eq => channel%eq(2))
eq%c = 2; eq%perm = [2, 1]; eq%mode = [EQ_INVARIANT, EQ_IDENTITY]
end associate
end associate
end subroutine setup_test_equivalences
@ %def setup_test_equivalences
@ Ditto, for channel properties.
<<PHS base: test auxiliary>>=
subroutine setup_test_channel_props (phs_config)
class(phs_test_config_t), intent(inout) :: phs_config
associate (channel => phs_config%channel(2))
call channel%set_resonant (140._default, 3.1415_default)
end associate
end subroutine setup_test_channel_props
@ %def setup_test_channel_props
@ Startup message
<<PHS base: test auxiliary>>=
subroutine phs_test_config_startup_message (phs_config, unit)
class(phs_test_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
call phs_config%base_startup_message (unit)
write (msg_buffer, "(A)") "Phase space: Test"
call msg_message (unit = unit)
end subroutine phs_test_config_startup_message
@ %def phs_test_config_startup_message
@ The instance type that matches [[phs_test_config_t]] is [[phs_test_t]].
<<PHS base: test auxiliary>>=
subroutine phs_test_config_allocate_instance (phs)
class(phs_t), intent(inout), pointer :: phs
allocate (phs_test_t :: phs)
end subroutine phs_test_config_allocate_instance
@ %def phs_test_config_allocate_instance
@
\subsubsection{Test kinematics implementation}
This implementation of kinematics generates a simple two-particle
configuration from the incoming momenta. The incoming momenta must be
in the c.m.\ system, all masses equal.
There are two channels: one generates $\cos\theta$ and $\phi$
uniformly, in the other channel we map the $r_1$ parameter which
belongs to $\cos\theta$.
We should store the mass parameter that we need.
<<PHS base: public test auxiliary>>=
public :: phs_test_t
<<PHS base: test types>>=
type, extends (phs_t) :: phs_test_t
real(default) :: m = 0
real(default), dimension(:), allocatable :: x
contains
<<PHS base: phs test: TBP>>
end type phs_test_t
@ %def phs_test_t
@ Output. The specific data are displayed only if [[verbose]] is set.
<<PHS base: phs test: TBP>>=
procedure :: write => phs_test_write
<<PHS base: test auxiliary>>=
subroutine phs_test_write (object, unit, verbose)
class(phs_test_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u
logical :: verb
u = given_output_unit (unit)
verb = .false.; if (present (verbose)) verb = verbose
if (verb) then
write (u, "(1x,A)") "Partonic phase space: data"
write (u, "(3x,A," // FMT_19 // ")") "m = ", object%m
end if
call object%base_write (u)
end subroutine phs_test_write
@ %def phs_test_write
@ The finalizer is empty.
<<PHS base: phs test: TBP>>=
procedure :: final => phs_test_final
<<PHS base: test auxiliary>>=
subroutine phs_test_final (object)
class(phs_test_t), intent(inout) :: object
end subroutine phs_test_final
@ %def phs_test_final
@ Initialization: set the mass value.
<<PHS base: phs test: TBP>>=
procedure :: init => phs_test_init
<<PHS base: test auxiliary>>=
subroutine phs_test_init (phs, phs_config)
class(phs_test_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
call phs%base_init (phs_config)
phs%m = phs%config%flv(1,1)%get_mass ()
allocate (phs%x (phs_config%n_par), source = 0._default)
end subroutine phs_test_init
@ %def phs_test_init
@ Evaluation. In channel 1, we uniformly generate $\cos\theta$ and
$\phi$, with Jacobian normalized to one. In channel 2, we prepend a
mapping $r_1 \to r_1^(1/3)$ with Jacobian $f=3r_1^2$.
The component [[x]] is allocated in the first subroutine, used and deallocated
in the second one.
<<PHS base: phs test: TBP>>=
procedure :: evaluate_selected_channel => phs_test_evaluate_selected_channel
procedure :: evaluate_other_channels => phs_test_evaluate_other_channels
<<PHS base: test auxiliary>>=
subroutine phs_test_evaluate_selected_channel (phs, c_in, r_in)
class(phs_test_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
if (phs%p_defined) then
call phs%select_channel (c_in)
phs%r(:,c_in) = r_in
select case (c_in)
case (1)
phs%x = r_in
case (2)
phs%x(1) = r_in(1) ** (1 / 3._default)
phs%x(2) = r_in(2)
end select
call compute_kinematics_solid_angle (phs%p, phs%q, phs%x)
phs%volume = 1
phs%q_defined = .true.
end if
end subroutine phs_test_evaluate_selected_channel
subroutine phs_test_evaluate_other_channels (phs, c_in)
class(phs_test_t), intent(inout) :: phs
integer, intent(in) :: c_in
integer :: c, n_channel
if (phs%p_defined) then
n_channel = phs%config%n_channel
do c = 1, n_channel
if (c /= c_in) then
call inverse_kinematics_solid_angle (phs%p, phs%q, phs%x)
select case (c)
case (1)
phs%r(:,c) = phs%x
case (2)
phs%r(1,c) = phs%x(1) ** 3
phs%r(2,c) = phs%x(2)
end select
end if
end do
phs%f(1) = 1
if (phs%r(1,2) /= 0) then
phs%f(2) = 1 / (3 * phs%r(1,2) ** (2/3._default))
else
phs%f(2) = 0
end if
phs%r_defined = .true.
end if
end subroutine phs_test_evaluate_other_channels
@ %def phs_test_evaluate_selected_channels
@ %def phs_test_evaluate_other_channels
@ Inverse evaluation.
<<PHS base: phs test: TBP>>=
procedure :: inverse => phs_test_inverse
<<PHS base: test auxiliary>>=
subroutine phs_test_inverse (phs)
class(phs_test_t), intent(inout) :: phs
integer :: c, n_channel
real(default), dimension(:), allocatable :: x
if (phs%p_defined .and. phs%q_defined) then
call phs%select_channel ()
n_channel = phs%config%n_channel
allocate (x (phs%config%n_par))
do c = 1, n_channel
call inverse_kinematics_solid_angle (phs%p, phs%q, x)
select case (c)
case (1)
phs%r(:,c) = x
case (2)
phs%r(1,c) = x(1) ** 3
phs%r(2,c) = x(2)
end select
end do
phs%f(1) = 1
if (phs%r(1,2) /= 0) then
phs%f(2) = 1 / (3 * phs%r(1,2) ** (2/3._default))
else
phs%f(2) = 0
end if
phs%volume = 1
phs%r_defined = .true.
end if
end subroutine phs_test_inverse
@ %def phs_test_inverse
@
\subsubsection{Phase-space configuration data}
Construct and display a test phase-space configuration object.
<<PHS base: execute tests>>=
call test (phs_base_1, "phs_base_1", &
"phase-space configuration", &
u, results)
<<PHS base: test declarations>>=
public :: phs_base_1
<<PHS base: tests>>=
subroutine phs_base_1 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
write (u, "(A)") "* Test output: phs_base_1"
write (u, "(A)") "* Purpose: initialize and display &
&test phase-space configuration data"
write (u, "(A)")
call model%init_test ()
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_base_1"), process_data)
allocate (phs_test_config_t :: phs_data)
call phs_data%init (process_data, model)
call phs_data%write (u)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_base_1"
end subroutine phs_base_1
@ %def phs_base_1
@
\subsubsection{Phase space evaluation}
Compute kinematics for given parameters, also invert the calculation.
<<PHS base: execute tests>>=
call test (phs_base_2, "phs_base_2", &
"phase-space evaluation", &
u, results)
<<PHS base: test declarations>>=
public :: phs_base_2
<<PHS base: tests>>=
subroutine phs_base_2 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
real(default) :: sqrts, E
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(2) :: p, q
write (u, "(A)") "* Test output: phs_base_2"
write (u, "(A)") "* Purpose: test simple two-channel phase space"
write (u, "(A)")
call model%init_test ()
call flv%init (25, model)
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_base_2"), process_data)
allocate (phs_test_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
select type (phs)
type is (phs_test_t)
call phs%init (phs_data)
end select
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
E = sqrts / 2
p(1) = vector4_moving (E, sqrt (E**2 - flv%get_mass ()**2), 3)
p(2) = vector4_moving (E,-sqrt (E**2 - flv%get_mass ()**2), 3)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point in channel 1 &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point in channel 2 &
&for x = 0.125, 0.125"
write (u, "(A)")
call phs%evaluate_selected_channel (2, [0.125_default, 0.125_default])
call phs%evaluate_other_channels (2)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
sqrts = 1000._default
select type (phs_data)
type is (phs_test_config_t)
call phs_data%configure (sqrts)
end select
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_base_2"
end subroutine phs_base_2
@ %def phs_base_2
@
\subsubsection{Phase-space equivalences}
Construct a test phase-space configuration which contains channel
equivalences.
<<PHS base: execute tests>>=
call test (phs_base_3, "phs_base_3", &
"channel equivalences", &
u, results)
<<PHS base: test declarations>>=
public :: phs_base_3
<<PHS base: tests>>=
subroutine phs_base_3 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
write (u, "(A)") "* Test output: phs_base_3"
write (u, "(A)") "* Purpose: construct phase-space configuration data &
&with equivalences"
write (u, "(A)")
call model%init_test ()
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_base_3"), process_data)
allocate (phs_test_config_t :: phs_data)
call phs_data%init (process_data, model)
select type (phs_data)
type is (phs_test_config_t)
phs_data%create_equivalences = .true.
end select
call phs_data%configure (1000._default)
call phs_data%write (u)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_base_3"
end subroutine phs_base_3
@ %def phs_base_3
@
\subsubsection{MD5 sum checks}
Construct a test phase-space configuration, compute and compare MD5 sums.
<<PHS base: execute tests>>=
call test (phs_base_4, "phs_base_4", &
"MD5 sum", &
u, results)
<<PHS base: test declarations>>=
public :: phs_base_4
<<PHS base: tests>>=
subroutine phs_base_4 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
write (u, "(A)") "* Test output: phs_base_4"
write (u, "(A)") "* Purpose: compute and compare MD5 sums"
write (u, "(A)")
call model%init_test ()
write (u, "(A)") "* Model parameters"
write (u, "(A)")
call model%write (unit = u, &
show_parameters = .true., &
show_particles = .false., show_vertices = .false.)
write (u, "(A)")
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_base_4"), process_data)
process_data%md5sum = "test_process_data_m6sum_12345678"
allocate (phs_test_config_t :: phs_data)
call phs_data%init (process_data, model)
call phs_data%compute_md5sum ()
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Modify model parameter"
write (u, "(A)")
call model%set_par (var_str ("ms"), 100._default)
call model%write (show_parameters = .true., &
show_particles = .false., show_vertices = .false.)
write (u, "(A)")
write (u, "(A)") "* PHS configuration"
write (u, "(A)")
call phs_data%compute_md5sum ()
call phs_data%write (u)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_base_4"
end subroutine phs_base_4
@ %def phs_base_4
@
\subsubsection{Phase-space channel collection}
Set up an array of various phase-space channels and collect them in a list.
<<PHS base: execute tests>>=
call test (phs_base_5, "phs_base_5", &
"channel collection", &
u, results)
<<PHS base: test declarations>>=
public :: phs_base_5
<<PHS base: tests>>=
subroutine phs_base_5 (u)
integer, intent(in) :: u
type(phs_channel_t), dimension(:), allocatable :: channel
type(phs_channel_collection_t) :: coll
integer :: i, n
write (u, "(A)") "* Test output: phs_base_5"
write (u, "(A)") "* Purpose: collect channel properties"
write (u, "(A)")
write (u, "(A)") "* Set up an array of channels"
write (u, "(A)")
n = 6
allocate (channel (n))
call channel(2)%set_resonant (75._default, 3._default)
call channel(4)%set_resonant (130._default, 1._default)
call channel(5)%set_resonant (75._default, 3._default)
call channel(6)%set_on_shell (33._default)
do i = 1, n
write (u, "(1x,I0)", advance="no") i
call channel(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Collect distinct properties"
write (u, "(A)")
do i = 1, n
call coll%push (channel(i))
end do
write (u, "(1x,A,I0)") "n = ", coll%get_n ()
write (u, "(A)")
call coll%write (u)
write (u, "(A)")
write (u, "(A)") "* Channel array with collection index assigned"
write (u, "(A)")
do i = 1, n
write (u, "(1x,I0)", advance="no") i
call channel(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call coll%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_base_5"
end subroutine phs_base_5
@ %def phs_base_5
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\clearpage
\section{Dummy phase space}
This module implements a dummy phase space module for cases where the
program structure demands the existence of a phase-space module, but
no phase space integration is performed.
<<[[phs_none.f90]]>>=
<<File header>>
module phs_none
<<Use kinds>>
<<Use strings>>
use phs_base, only: phs_config_t, phs_t
<<Standard module head>>
<<PHS none: public>>
<<PHS none: types>>
interface
<<PHS none: sub interfaces>>
end interface
contains
<<PHS none: main procedures>>
end module phs_none
@ %def phs_none
@
<<[[phs_none_sub.f90]]>>=
<<File header>>
submodule (phs_none) phs_none_s
use io_units, only: given_output_unit
use diagnostics, only: msg_message, msg_fatal
implicit none
contains
<<PHS none: procedures>>
end submodule phs_none_s
@ %def phs_none_s
@
\subsection{Configuration}
Nothing to configure, but we provide the type and methods.
<<PHS none: public>>=
public :: phs_none_config_t
<<PHS none: types>>=
type, extends (phs_config_t) :: phs_none_config_t
contains
<<PHS none: phs none config: TBP>>
end type phs_none_config_t
@ %def phs_none_config_t
@ The finalizer is empty.
<<PHS none: phs none config: TBP>>=
procedure :: final => phs_none_config_final
<<PHS none: sub interfaces>>=
module subroutine phs_none_config_final (object)
class(phs_none_config_t), intent(inout) :: object
end subroutine phs_none_config_final
<<PHS none: procedures>>=
module subroutine phs_none_config_final (object)
class(phs_none_config_t), intent(inout) :: object
end subroutine phs_none_config_final
@ %def phs_none_final
@ Output. No contents, just an informative line.
<<PHS none: phs none config: TBP>>=
procedure :: write => phs_none_config_write
<<PHS none: sub interfaces>>=
module subroutine phs_none_config_write (object, unit, include_id)
class(phs_none_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
end subroutine phs_none_config_write
<<PHS none: procedures>>=
module subroutine phs_none_config_write (object, unit, include_id)
class(phs_none_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u
u = given_output_unit (unit)
write (u, "(1x,A)") &
"Partonic phase-space configuration: non-functional dummy"
end subroutine phs_none_config_write
@ %def phs_none_config_write
@ Configuration: we have to implement this method, but it obviously
does nothing.
<<PHS none: phs none config: TBP>>=
procedure :: configure => phs_none_config_configure
<<PHS none: sub interfaces>>=
module subroutine phs_none_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_none_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_none_config_configure
<<PHS none: procedures>>=
module subroutine phs_none_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_none_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_none_config_configure
@ %def phs_none_config_configure
@ Startup message, after configuration is complete.
<<PHS none: phs none config: TBP>>=
procedure :: startup_message => phs_none_config_startup_message
<<PHS none: sub interfaces>>=
module subroutine phs_none_config_startup_message (phs_config, unit)
class(phs_none_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
end subroutine phs_none_config_startup_message
<<PHS none: procedures>>=
module subroutine phs_none_config_startup_message (phs_config, unit)
class(phs_none_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
call msg_message ("Phase space: none")
end subroutine phs_none_config_startup_message
@ %def phs_none_config_startup_message
@ Allocate an instance: the actual phase-space object.
Gfortran 7/8/9 bug: has to remain in the main module.
<<PHS none: phs none config: TBP>>=
procedure, nopass :: allocate_instance => phs_none_config_allocate_instance
<<PHS none: main procedures>>=
subroutine phs_none_config_allocate_instance (phs)
class(phs_t), intent(inout), pointer :: phs
allocate (phs_none_t :: phs)
end subroutine phs_none_config_allocate_instance
@ %def phs_none_config_allocate_instance
@
\subsection{Kinematics implementation}
This is considered as empty, but we have to implement the minimal set of methods.
<<PHS none: public>>=
public :: phs_none_t
<<PHS none: types>>=
type, extends (phs_t) :: phs_none_t
contains
<<PHS none: phs none: TBP>>
end type phs_none_t
@ %def phs_none_t
@ Output.
<<PHS none: phs none: TBP>>=
procedure :: write => phs_none_write
<<PHS none: sub interfaces>>=
module subroutine phs_none_write (object, unit, verbose)
class(phs_none_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine phs_none_write
<<PHS none: procedures>>=
module subroutine phs_none_write (object, unit, verbose)
class(phs_none_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u
u = given_output_unit (unit)
write (u, "(A)") "Partonic phase space: none"
end subroutine phs_none_write
@ %def phs_none_write
@ The finalizer is empty.
<<PHS none: phs none: TBP>>=
procedure :: final => phs_none_final
<<PHS none: sub interfaces>>=
module subroutine phs_none_final (object)
class(phs_none_t), intent(inout) :: object
end subroutine phs_none_final
<<PHS none: procedures>>=
module subroutine phs_none_final (object)
class(phs_none_t), intent(inout) :: object
end subroutine phs_none_final
@ %def phs_none_final
@ Initialization, trivial.
<<PHS none: phs none: TBP>>=
procedure :: init => phs_none_init
<<PHS none: sub interfaces>>=
module subroutine phs_none_init (phs, phs_config)
class(phs_none_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_none_init
<<PHS none: procedures>>=
module subroutine phs_none_init (phs, phs_config)
class(phs_none_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
call phs%base_init (phs_config)
end subroutine phs_none_init
@ %def phs_none_init
@ Evaluation. This must not be called at all.
<<PHS none: phs none: TBP>>=
procedure :: evaluate_selected_channel => phs_none_evaluate_selected_channel
procedure :: evaluate_other_channels => phs_none_evaluate_other_channels
<<PHS none: sub interfaces>>=
module subroutine phs_none_evaluate_selected_channel (phs, c_in, r_in)
class(phs_none_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
end subroutine phs_none_evaluate_selected_channel
module subroutine phs_none_evaluate_other_channels (phs, c_in)
class(phs_none_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_none_evaluate_other_channels
<<PHS none: procedures>>=
module subroutine phs_none_evaluate_selected_channel (phs, c_in, r_in)
class(phs_none_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
call msg_fatal &
("Phase space: attempt to evaluate with the 'phs_none' method")
end subroutine phs_none_evaluate_selected_channel
module subroutine phs_none_evaluate_other_channels (phs, c_in)
class(phs_none_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_none_evaluate_other_channels
@ %def phs_none_evaluate_selected_channel
@ %def phs_none_evaluate_other_channels
@ Inverse evaluation, likewise.
<<PHS none: phs none: TBP>>=
procedure :: inverse => phs_none_inverse
<<PHS none: sub interfaces>>=
module subroutine phs_none_inverse (phs)
class(phs_none_t), intent(inout) :: phs
end subroutine phs_none_inverse
<<PHS none: procedures>>=
module subroutine phs_none_inverse (phs)
class(phs_none_t), intent(inout) :: phs
call msg_fatal ("Phase space: attempt to evaluate inverse " // &
"with the 'phs_none' method")
end subroutine phs_none_inverse
@ %def phs_none_inverse
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_none_ut.f90]]>>=
<<File header>>
module phs_none_ut
use unit_tests
use phs_none_uti
<<Standard module head>>
<<PHS none: public test>>
contains
<<PHS none: test driver>>
end module phs_none_ut
@ %def phs_none_ut
@
<<[[phs_none_uti.f90]]>>=
<<File header>>
module phs_none_uti
<<Use kinds>>
<<Use strings>>
use flavors
use lorentz
use model_data
use process_constants
use phs_base
use phs_none
use phs_base_ut, only: init_test_process_data, init_test_decay_data
<<Standard module head>>
<<PHS none: test declarations>>
contains
<<PHS none: tests>>
end module phs_none_uti
@ %def phs_none_ut
@ API: driver for the unit tests below.
<<PHS none: public test>>=
public :: phs_none_test
<<PHS none: test driver>>=
subroutine phs_none_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS none: execute tests>>
end subroutine phs_none_test
@ %def phs_none_test
@
\subsubsection{Phase-space configuration data}
Construct and display a test phase-space configuration object. Also
check the [[azimuthal_dependence]] flag.
<<PHS none: execute tests>>=
call test (phs_none_1, "phs_none_1", &
"phase-space configuration dummy", &
u, results)
<<PHS none: test declarations>>=
public :: phs_none_1
<<PHS none: tests>>=
subroutine phs_none_1 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
real(default) :: sqrts
write (u, "(A)") "* Test output: phs_none_1"
write (u, "(A)") "* Purpose: display &
&phase-space configuration data"
write (u, "(A)")
allocate (phs_none_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts, azimuthal_dependence=.false.)
call phs_data%write (u)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_none_1"
end subroutine phs_none_1
@ %def phs_none_1
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\clearpage
\section{Single-particle phase space}
This module implements the phase space for a single particle, i.e., the solid
angle, in a straightforward parameterization with a single channel. The
phase-space implementation may be used either for $1\to 2$ decays or for $2\to
2$ scattering processes, so the number of incoming particles is the only free
parameter in the configuration. In the latter case, we should restrict its
use to non-resonant s-channel processes, because there is no mapping of the
scattering angle.
(We might extend this later to account for generic $2\to 2$ situations, e.g.,
account for a Coulomb singularity or detect an s-channel resonance structure
that requires matching structure-function mappings.)
This is derived from the [[phs_test]] implementation in the
[[phs_base]] module above, even more simplified, but intended for
actual use.
<<[[phs_single.f90]]>>=
<<File header>>
module phs_single
<<Use kinds>>
<<Use strings>>
use lorentz
use phs_base
<<Standard module head>>
<<PHS single: public>>
<<PHS single: types>>
interface
<<PHS single: sub interfaces>>
end interface
contains
<<PHS single: main procedures>>
end module phs_single
@ %def phs_single
@
<<[[phs_single_sub.f90]]>>=
<<File header>>
submodule (phs_single) phs_single_s
use io_units
use constants
use numeric_utils
use diagnostics
use physics_defs
implicit none
contains
<<PHS single: procedures>>
end submodule phs_single_s
@ %def phs_single_s
@
\subsection{Configuration}
<<PHS single: public>>=
public :: phs_single_config_t
<<PHS single: types>>=
type, extends (phs_config_t) :: phs_single_config_t
contains
<<PHS single: phs single config: TBP>>
end type phs_single_config_t
@ %def phs_single_config_t
@ The finalizer is empty.
<<PHS single: phs single config: TBP>>=
procedure :: final => phs_single_config_final
<<PHS single: sub interfaces>>=
module subroutine phs_single_config_final (object)
class(phs_single_config_t), intent(inout) :: object
end subroutine phs_single_config_final
<<PHS single: procedures>>=
module subroutine phs_single_config_final (object)
class(phs_single_config_t), intent(inout) :: object
end subroutine phs_single_config_final
@ %def phs_single_final
@ Output.
<<PHS single: phs single config: TBP>>=
procedure :: write => phs_single_config_write
<<PHS single: sub interfaces>>=
module subroutine phs_single_config_write (object, unit, include_id)
class(phs_single_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
end subroutine phs_single_config_write
<<PHS single: procedures>>=
module subroutine phs_single_config_write (object, unit, include_id)
class(phs_single_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u
u = given_output_unit (unit)
write (u, "(1x,A)") "Partonic phase-space configuration (single-particle):"
call object%base_write (unit)
end subroutine phs_single_config_write
@ %def phs_single_config_write
@ Configuration: there is only one channel and two parameters. The
second parameter is the azimuthal angle, which may be a flat dimension.
<<PHS single: phs single config: TBP>>=
procedure :: configure => phs_single_config_configure
<<PHS single: sub interfaces>>=
module subroutine phs_single_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_single_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_single_config_configure
<<PHS single: procedures>>=
module subroutine phs_single_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_single_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
if (.not. present (nlo_type)) &
phs_config%nlo_type = BORN
if (phs_config%n_out == 2) then
phs_config%n_channel = 1
phs_config%n_par = 2
phs_config%sqrts = sqrts
if (present (sqrts_fixed)) phs_config%sqrts_fixed = sqrts_fixed
if (present (lab_is_cm)) phs_config%lab_is_cm = lab_is_cm
if (present (azimuthal_dependence)) then
phs_config%azimuthal_dependence = azimuthal_dependence
if (.not. azimuthal_dependence) then
allocate (phs_config%dim_flat (1))
phs_config%dim_flat(1) = 2
end if
end if
if (allocated (phs_config%channel)) deallocate (phs_config%channel)
allocate (phs_config%channel (1))
call phs_config%compute_md5sum ()
else
call msg_fatal ("Single-particle phase space requires n_out = 2")
end if
end subroutine phs_single_config_configure
@ %def phs_single_config_configure
@ Startup message, after configuration is complete.
<<PHS single: phs single config: TBP>>=
procedure :: startup_message => phs_single_config_startup_message
<<PHS single: sub interfaces>>=
module subroutine phs_single_config_startup_message (phs_config, unit)
class(phs_single_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
end subroutine phs_single_config_startup_message
<<PHS single: procedures>>=
module subroutine phs_single_config_startup_message (phs_config, unit)
class(phs_single_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
call phs_config%base_startup_message (unit)
write (msg_buffer, "(A,2(1x,I0,1x,A))") &
"Phase space: single-particle"
call msg_message (unit = unit)
end subroutine phs_single_config_startup_message
@ %def phs_single_config_startup_message
@ Allocate an instance: the actual phase-space object.
Gfortran 7/8/9 bug, has to remain in the main module.
<<PHS single: phs single config: TBP>>=
procedure, nopass :: allocate_instance => phs_single_config_allocate_instance
<<PHS single: main procedures>>=
subroutine phs_single_config_allocate_instance (phs)
class(phs_t), intent(inout), pointer :: phs
allocate (phs_single_t :: phs)
end subroutine phs_single_config_allocate_instance
@ %def phs_single_config_allocate_instance
@
\subsection{Kinematics implementation}
We generate $\cos\theta$ and $\phi$ uniformly, covering the solid angle.
Note: The incoming momenta must be in the c.m. system.
<<PHS single: public>>=
public :: phs_single_t
<<PHS single: types>>=
type, extends (phs_t) :: phs_single_t
contains
<<PHS single: phs single: TBP>>
end type phs_single_t
@ %def phs_single_t
@ Output. The [[verbose]] setting is irrelevant, we just display the contents
of the base object.
<<PHS single: phs single: TBP>>=
procedure :: write => phs_single_write
<<PHS single: sub interfaces>>=
module subroutine phs_single_write (object, unit, verbose)
class(phs_single_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine phs_single_write
<<PHS single: procedures>>=
module subroutine phs_single_write (object, unit, verbose)
class(phs_single_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u
u = given_output_unit (unit)
call object%base_write (u)
end subroutine phs_single_write
@ %def phs_single_write
@ The finalizer is empty.
<<PHS single: phs single: TBP>>=
procedure :: final => phs_single_final
<<PHS single: sub interfaces>>=
module subroutine phs_single_final (object)
class(phs_single_t), intent(inout) :: object
end subroutine phs_single_final
<<PHS single: procedures>>=
module subroutine phs_single_final (object)
class(phs_single_t), intent(inout) :: object
end subroutine phs_single_final
@ %def phs_single_final
@ Initialization. We allocate arrays ([[base_init]]) and adjust the
phase-space volume. The massless two-particle phase space volume is
\begin{equation}
\Phi_2 = \frac{1}{4(2\pi)^5} = 2.55294034614 \times 10^{-5}
\end{equation}
For a decay with nonvanishing masses ($m_3$, $m_4$), there is a correction
factor
\begin{equation}
\Phi_2(m) / \Phi_2(0) = \frac{1}{\hat s}
\lambda^{1/2}(\hat s, m_3^2, m_4^2).
\end{equation}
For a scattering process with nonvanishing masses, the correction
factor is
\begin{equation}
\Phi_2(m) / \Phi_2(0) = \frac{1}{\hat s ^ 2}
\lambda^{1/2}(\hat s, m_1^2, m_2^2)\,
\lambda^{1/2}(\hat s, m_3^2, m_4^2).
\end{equation}
If the energy is fixed, this is constant. Otherwise, we have to account for
varying $\hat s$.
<<PHS single: phs single: TBP>>=
procedure :: init => phs_single_init
<<PHS single: sub interfaces>>=
module subroutine phs_single_init (phs, phs_config)
class(phs_single_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_single_init
<<PHS single: procedures>>=
module subroutine phs_single_init (phs, phs_config)
class(phs_single_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
call phs%base_init (phs_config)
phs%volume = 1 / (4 * twopi5)
call phs%compute_factor ()
end subroutine phs_single_init
@ %def phs_single_init
@ Compute the correction factor for nonzero masses. We do this during
initialization (when the incoming momenta [[p]] are undefined), unless
[[sqrts]] is variable. We do this again once for each phase-space point, but
then we skip the calculation if [[sqrts]] is fixed.
<<PHS single: phs single: TBP>>=
procedure :: compute_factor => phs_single_compute_factor
<<PHS single: sub interfaces>>=
module subroutine phs_single_compute_factor (phs)
class(phs_single_t), intent(inout) :: phs
end subroutine phs_single_compute_factor
<<PHS single: procedures>>=
module subroutine phs_single_compute_factor (phs)
class(phs_single_t), intent(inout) :: phs
real(default) :: s_hat
select case (phs%config%n_in)
case (1)
if (.not. phs%p_defined) then
if (sum (phs%m_out) < phs%m_in(1)) then
s_hat = phs%m_in(1) ** 2
phs%f(1) = 1 / s_hat &
* sqrt (lambda (s_hat, phs%m_out(1)**2, phs%m_out(2)**2))
else
print *, "m_in = ", phs%m_in
print *, "m_out = ", phs%m_out
call msg_fatal ("Decay is kinematically forbidden")
end if
end if
case (2)
if (phs%config%sqrts_fixed) then
if (phs%p_defined) return
s_hat = phs%config%sqrts ** 2
else
if (.not. phs%p_defined) return
s_hat = sum (phs%p) ** 2
end if
if (sum (phs%m_in)**2 < s_hat .and. sum (phs%m_out)**2 < s_hat) then
phs%f(1) = 1 / s_hat * &
( lambda (s_hat, phs%m_in (1)**2, phs%m_in (2)**2) &
* lambda (s_hat, phs%m_out(1)**2, phs%m_out(2)**2) ) &
** 0.25_default
else
phs%f(1) = 0
end if
end select
end subroutine phs_single_compute_factor
@ %def phs_single_compute_factor
@ Evaluation. We uniformly generate $\cos\theta$ and
$\phi$, with Jacobian normalized to one.
There is only a single channel, so the second subroutine does nothing.
Note: the current implementation works for elastic scattering only.
<<PHS single: phs single: TBP>>=
procedure :: evaluate_selected_channel => phs_single_evaluate_selected_channel
procedure :: evaluate_other_channels => phs_single_evaluate_other_channels
<<PHS single: sub interfaces>>=
module subroutine phs_single_evaluate_selected_channel (phs, c_in, r_in)
class(phs_single_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
end subroutine phs_single_evaluate_selected_channel
module subroutine phs_single_evaluate_other_channels (phs, c_in)
class(phs_single_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_single_evaluate_other_channels
<<PHS single: procedures>>=
module subroutine phs_single_evaluate_selected_channel (phs, c_in, r_in)
class(phs_single_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
if (phs%p_defined) then
call phs%select_channel (c_in)
phs%r(:,c_in) = r_in
select case (phs%config%n_in)
case (2)
if (all (phs%m_in == phs%m_out)) then
call compute_kinematics_solid_angle (phs%p, phs%q, r_in)
else
call msg_bug ("PHS single: inelastic scattering not implemented")
end if
case (1)
call compute_kinematics_solid_angle (phs%decay_p (), phs%q, r_in)
end select
call phs%compute_factor ()
phs%q_defined = .true.
phs%r_defined = .true.
end if
end subroutine phs_single_evaluate_selected_channel
module subroutine phs_single_evaluate_other_channels (phs, c_in)
class(phs_single_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_single_evaluate_other_channels
@ %def phs_single_evaluate_selected_channel
@ %def phs_single_evaluate_other_channels
@ Auxiliary: split a decaying particle at rest into the decay products,
aligned along the $z$ axis.
<<PHS single: phs single: TBP>>=
procedure :: decay_p => phs_single_decay_p
<<PHS single: sub interfaces>>=
module function phs_single_decay_p (phs) result (p)
class(phs_single_t), intent(in) :: phs
type(vector4_t), dimension(2) :: p
end function phs_single_decay_p
<<PHS single: procedures>>=
module function phs_single_decay_p (phs) result (p)
class(phs_single_t), intent(in) :: phs
type(vector4_t), dimension(2) :: p
real(default) :: k
real(default), dimension(2) :: E
k = sqrt (lambda (phs%m_in(1) ** 2, phs%m_out(1) ** 2, phs%m_out(2) ** 2)) &
/ (2 * phs%m_in(1))
E = sqrt (phs%m_out ** 2 + k ** 2)
p(1) = vector4_moving (E(1), k, 3)
p(2) = vector4_moving (E(2),-k, 3)
end function phs_single_decay_p
@ %def phs_single_decay_p
@ Inverse evaluation.
<<PHS single: phs single: TBP>>=
procedure :: inverse => phs_single_inverse
<<PHS single: sub interfaces>>=
module subroutine phs_single_inverse (phs)
class(phs_single_t), intent(inout) :: phs
end subroutine phs_single_inverse
<<PHS single: procedures>>=
module subroutine phs_single_inverse (phs)
class(phs_single_t), intent(inout) :: phs
real(default), dimension(:), allocatable :: x
if (phs%p_defined .and. phs%q_defined) then
call phs%select_channel ()
allocate (x (phs%config%n_par))
call inverse_kinematics_solid_angle (phs%p, phs%q, x)
phs%r(:,1) = x
call phs%compute_factor ()
phs%r_defined = .true.
end if
end subroutine phs_single_inverse
@ %def phs_single_inverse
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_single_ut.f90]]>>=
<<File header>>
module phs_single_ut
use unit_tests
use phs_single_uti
<<Standard module head>>
<<PHS single: public test>>
contains
<<PHS single: test driver>>
end module phs_single_ut
@ %def phs_single_ut
@
<<[[phs_single_uti.f90]]>>=
<<File header>>
module phs_single_uti
<<Use kinds>>
<<Use strings>>
use flavors
use lorentz
use model_data
use process_constants
use phs_base
use phs_single
use phs_base_ut, only: init_test_process_data, init_test_decay_data
<<Standard module head>>
<<PHS single: test declarations>>
contains
<<PHS single: tests>>
end module phs_single_uti
@ %def phs_single_ut
@ API: driver for the unit tests below.
<<PHS single: public test>>=
public :: phs_single_test
<<PHS single: test driver>>=
subroutine phs_single_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS single: execute tests>>
end subroutine phs_single_test
@ %def phs_single_test
@
\subsubsection{Phase-space configuration data}
Construct and display a test phase-space configuration object. Also
check the [[azimuthal_dependence]] flag.
<<PHS single: execute tests>>=
call test (phs_single_1, "phs_single_1", &
"phase-space configuration", &
u, results)
<<PHS single: test declarations>>=
public :: phs_single_1
<<PHS single: tests>>=
subroutine phs_single_1 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
real(default) :: sqrts
write (u, "(A)") "* Test output: phs_single_1"
write (u, "(A)") "* Purpose: initialize and display &
&phase-space configuration data"
write (u, "(A)")
call model%init_test ()
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_single_1"), process_data)
allocate (phs_single_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts, azimuthal_dependence=.false.)
call phs_data%write (u)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_single_1"
end subroutine phs_single_1
@ %def phs_single_1
@
\subsubsection{Phase space evaluation}
Compute kinematics for given parameters, also invert the calculation.
<<PHS single: execute tests>>=
call test (phs_single_2, "phs_single_2", &
"phase-space evaluation", &
u, results)
<<PHS single: test declarations>>=
public :: phs_single_2
<<PHS single: tests>>=
subroutine phs_single_2 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
real(default) :: sqrts, E
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(2) :: p, q
write (u, "(A)") "* Test output: phs_single_2"
write (u, "(A)") "* Purpose: test simple two-channel phase space"
write (u, "(A)")
call model%init_test ()
call flv%init (25, model)
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_single_2"), process_data)
allocate (phs_single_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
E = sqrts / 2
p(1) = vector4_moving (E, sqrt (E**2 - flv%get_mass ()**2), 3)
p(2) = vector4_moving (E,-sqrt (E**2 - flv%get_mass ()**2), 3)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_single_2"
end subroutine phs_single_2
@ %def phs_single_2
@
\subsubsection{Phase space for non-c.m. system}
Compute kinematics for given parameters, also invert the calculation.
Since this will involve cancellations, we call [[pacify]] to eliminate
numerical noise.
<<PHS single: execute tests>>=
call test (phs_single_3, "phs_single_3", &
"phase-space evaluation in lab frame", &
u, results)
<<PHS single: test declarations>>=
public :: phs_single_3
<<PHS single: tests>>=
subroutine phs_single_3 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
real(default) :: sqrts, E
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(2) :: p, q
type(lorentz_transformation_t) :: lt
write (u, "(A)") "* Test output: phs_single_3"
write (u, "(A)") "* Purpose: test simple two-channel phase space"
write (u, "(A)") "* without c.m. kinematics assumption"
write (u, "(A)")
call model%init_test ()
call flv%init (25, model)
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_single_3"), process_data)
allocate (phs_single_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts, lab_is_cm=.false., sqrts_fixed=.false.)
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta in lab system"
write (u, "(A)")
lt = boost (0.1_default, 1) * boost (0.3_default, 3)
E = sqrts / 2
p(1) = lt * vector4_moving (E, sqrt (E**2 - flv%get_mass ()**2), 3)
p(2) = lt * vector4_moving (E,-sqrt (E**2 - flv%get_mass ()**2), 3)
call vector4_write (p(1), u)
call vector4_write (p(2), u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call pacify (phs)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Extract outgoing momenta in lab system"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
call vector4_write (q(1), u)
call vector4_write (q(2), u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call pacify (phs)
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_single_3"
end subroutine phs_single_3
@ %def phs_single_3
@
\subsubsection{Decay Phase space evaluation}
Compute kinematics for given parameters, also invert the calculation. This
time, implement a decay process.
<<PHS single: execute tests>>=
call test (phs_single_4, "phs_single_4", &
"decay phase-space evaluation", &
u, results)
<<PHS single: test declarations>>=
public :: phs_single_4
<<PHS single: tests>>=
subroutine phs_single_4 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(1) :: p
type(vector4_t), dimension(2) :: q
write (u, "(A)") "* Test output: phs_single_4"
write (u, "(A)") "* Purpose: test simple two-channel phase space"
write (u, "(A)")
call model%init_test ()
call model%set_par (var_str ("ff"), 0.4_default)
call model%set_par (var_str ("mf"), &
model%get_real (var_str ("ff")) * model%get_real (var_str ("ms")))
call flv%init (25, model)
write (u, "(A)") "* Initialize a decay and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_decay_data (var_str ("phs_single_4"), process_data)
allocate (phs_single_config_t :: phs_data)
call phs_data%init (process_data, model)
call phs_data%configure (flv%get_mass ())
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
p(1) = vector4_at_rest (flv%get_mass ())
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs_data%configure (flv%get_mass ())
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_single_4"
end subroutine phs_single_4
@ %def phs_single_4
@
\clearpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Flat RAMBO phase space}
This module implements the flat \texttt{RAMBO} phase space for
massless and massive particles using the minimal d.o.f $3n - 4$ in a
straightforward parameterization with a single channel.
We generate $n$ mass systems $M_i$ with $M_0 = \sqrt{s}$ and $M_{n} =
0$. We let each mass system decay $1 \rightarrow 2$ in a four-momentum
conserving way. The four-momenta of the two particles are generated
back-to-back where we map the d.o.f. to energy, azimuthal and polar
angle. The particle momenta are then boosted to CMS by an appriopriate
boost using the kinematics of the parent mass system.
<<[[phs_rambo.f90]]>>=
<<File header>>
module phs_rambo
<<Use kinds>>
<<Use strings>>
use lorentz
use phs_base
<<Standard module head>>
<<PHS rambo: types>>
<<PHS rambo: public>>
interface
<<PHS rambo: sub interfaces>>
end interface
contains
<<PHS rambo: main procedures>>
end module phs_rambo
@ %def phs_rambo
@
<<[[phs_rambo_sub.f90]]>>=
<<File header>>
submodule (phs_rambo) phs_rambo_s
use io_units
use constants
use numeric_utils
use format_defs, only: FMT_19
use permutations, only: factorial
use diagnostics
use physics_defs
implicit none
<<PHS rambo: parameters>>
contains
<<PHS rambo: procedures>>
end submodule phs_rambo_s
@ %def phs_rambo_s
@
\subsection{Configuration}
<<PHS rambo: public>>=
public :: phs_rambo_config_t
<<PHS rambo: types>>=
type, extends (phs_config_t) :: phs_rambo_config_t
contains
<<PHS rambo: phs rambo config: TBP>>
end type phs_rambo_config_t
@ %def phs_rambo_config_t
@ The finalizer is empty.
<<PHS rambo: phs rambo config: TBP>>=
procedure :: final => phs_rambo_config_final
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_config_final (object)
class(phs_rambo_config_t), intent(inout) :: object
end subroutine phs_rambo_config_final
<<PHS rambo: procedures>>=
module subroutine phs_rambo_config_final (object)
class(phs_rambo_config_t), intent(inout) :: object
end subroutine phs_rambo_config_final
@ %def phs_rambo_final
@ Output.
<<PHS rambo: phs rambo config: TBP>>=
procedure :: write => phs_rambo_config_write
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_config_write (object, unit, include_id)
class(phs_rambo_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
end subroutine phs_rambo_config_write
<<PHS rambo: procedures>>=
module subroutine phs_rambo_config_write (object, unit, include_id)
class(phs_rambo_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u
u = given_output_unit (unit)
write (u, "(1x,A)") "Partonic, flat phase-space configuration (RAMBO):"
call object%base_write (unit)
end subroutine phs_rambo_config_write
@ %def phs_rambo_config_write
@ Configuration: there is only one channel and $3n - 4$ parameters.
<<PHS rambo: phs rambo config: TBP>>=
procedure :: configure => phs_rambo_config_configure
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_rambo_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_rambo_config_configure
<<PHS rambo: procedures>>=
module subroutine phs_rambo_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_rambo_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
if (.not. present (nlo_type)) &
phs_config%nlo_type = BORN
if (phs_config%n_out < 2) then
call msg_fatal ("RAMBO phase space requires n_out >= 2")
end if
phs_config%n_channel = 1
phs_config%n_par = 3 * phs_config%n_out - 4
phs_config%sqrts = sqrts
if (present (sqrts_fixed)) phs_config%sqrts_fixed = sqrts_fixed
if (present (lab_is_cm)) phs_config%lab_is_cm = lab_is_cm
if (allocated (phs_config%channel)) deallocate (phs_config%channel)
allocate (phs_config%channel (1))
call phs_config%compute_md5sum ()
end subroutine phs_rambo_config_configure
@ %def phs_rambo_config_configure
@ Startup message, after configuration is complete.
<<PHS rambo: phs rambo config: TBP>>=
procedure :: startup_message => phs_rambo_config_startup_message
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_config_startup_message (phs_config, unit)
class(phs_rambo_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
end subroutine phs_rambo_config_startup_message
<<PHS rambo: procedures>>=
module subroutine phs_rambo_config_startup_message (phs_config, unit)
class(phs_rambo_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
call phs_config%base_startup_message (unit)
write (msg_buffer, "(A,2(1x,I0,1x,A))") &
"Phase space: flat (RAMBO)"
call msg_message (unit = unit)
end subroutine phs_rambo_config_startup_message
@ %def phs_rambo_config_startup_message
@ Allocate an instance: the actual phase-space object.
Gfortran 7/8/9 bug, has to remain in the main module.
<<PHS rambo: phs rambo config: TBP>>=
procedure, nopass :: allocate_instance => phs_rambo_config_allocate_instance
<<PHS rambo: main procedures>>=
subroutine phs_rambo_config_allocate_instance (phs)
class(phs_t), intent(inout), pointer :: phs
allocate (phs_rambo_t :: phs)
end subroutine phs_rambo_config_allocate_instance
@ %def phs_rambo_config_allocate_instance
@
\subsection{Kinematics implementation}
We generate $n - 2$ mass systems $M_i$ with $M_0 = \sqrt{s}$ and $M_n = 0$...
Note: The incoming momenta must be in the c.m. system.
<<PHS rambo: public>>=
public :: phs_rambo_t
<<PHS rambo: types>>=
type, extends (phs_t) :: phs_rambo_t
real(default), dimension(:), allocatable :: k
real(default), dimension(:), allocatable :: m
contains
<<PHS rambo: phs rambo: TBP>>
end type phs_rambo_t
@ %def phs_rambo_t
@ Output.
<<PHS rambo: phs rambo: TBP>>=
procedure :: write => phs_rambo_write
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_write (object, unit, verbose)
class(phs_rambo_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine phs_rambo_write
<<PHS rambo: procedures>>=
module subroutine phs_rambo_write (object, unit, verbose)
class(phs_rambo_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u
u = given_output_unit (unit)
call object%base_write (u)
write (u, "(1X,A)") "Intermediate masses (massless):"
write (u, "(3X,999(" // FMT_19 // "))") object%k
write (u, "(1X,A)") "Intermediate masses (massive):"
write (u, "(3X,999(" // FMT_19 // "))") object%m
end subroutine phs_rambo_write
@ %def phs_rambo_write
@ The finalizer is empty.
<<PHS rambo: phs rambo: TBP>>=
procedure :: final => phs_rambo_final
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_final (object)
class(phs_rambo_t), intent(inout) :: object
end subroutine phs_rambo_final
<<PHS rambo: procedures>>=
module subroutine phs_rambo_final (object)
class(phs_rambo_t), intent(inout) :: object
end subroutine phs_rambo_final
@ %def phs_rambo_final
@ Initialization. We allocate arrays ([[base_init]]) and adjust the
phase-space volume.
The energy dependent factor of $s^{n - 2}$ is applied later.
<<PHS rambo: phs rambo: TBP>>=
procedure :: init => phs_rambo_init
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_init (phs, phs_config)
class(phs_rambo_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_rambo_init
<<PHS rambo: procedures>>=
module subroutine phs_rambo_init (phs, phs_config)
class(phs_rambo_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
call phs%base_init (phs_config)
associate (n => phs%config%n_out)
select case (n)
case (1)
if (sum (phs%m_out) > phs%m_in (1)) then
print *, "m_in = ", phs%m_in
print *, "m_out = ", phs%m_out
call msg_fatal &
("[phs_rambo_init] Decay is kinematically forbidden.")
end if
end select
allocate (phs%k(n), source = 0._default)
allocate (phs%m(n), source = 0._default)
phs%volume = 1. / (twopi)**(3 * n) &
* (pi / 2.)**(n - 1) / (factorial(n - 1) * factorial(n - 2))
end associate
end subroutine phs_rambo_init
@ %def phs_rambo_init
@ Evaluation. There is only one channel for RAMBO, so the second subroutine does nothing.
Note: the current implementation works for elastic scattering only.
<<PHS rambo: phs rambo: TBP>>=
procedure :: evaluate_selected_channel => phs_rambo_evaluate_selected_channel
procedure :: evaluate_other_channels => phs_rambo_evaluate_other_channels
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_evaluate_selected_channel (phs, c_in, r_in)
class(phs_rambo_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
end subroutine phs_rambo_evaluate_selected_channel
module subroutine phs_rambo_evaluate_other_channels (phs, c_in)
class(phs_rambo_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_rambo_evaluate_other_channels
<<PHS rambo: procedures>>=
module subroutine phs_rambo_evaluate_selected_channel (phs, c_in, r_in)
class(phs_rambo_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
type(vector4_t), dimension(2) :: p_rest, p_boosted
type(vector4_t) :: q
real(default), dimension(2) :: r_angle
integer :: i
if (.not. phs%p_defined) return
call phs%select_channel (c_in)
phs%r(:,c_in) = r_in
associate (n => phs%config%n_out, m => phs%m)
call phs%generate_intermediates (r_in(:n - 2))
q = sum (phs%p)
do i = 2, n
r_angle(1) = r_in(n - 5 + 2 * i)
r_angle(2) = r_in(n - 4 + 2 * i)
call phs%decay_intermediate (i, r_angle, p_rest)
p_boosted = boost(q, m(i - 1)) * p_rest
q = p_boosted(1)
phs%q(i - 1) = p_boosted(2)
end do
phs%q(n) = q
end associate
phs%q_defined = .true.
phs%r_defined = .true.
end subroutine phs_rambo_evaluate_selected_channel
module subroutine phs_rambo_evaluate_other_channels (phs, c_in)
class(phs_rambo_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_rambo_evaluate_other_channels
@ %def phs_rambo_evaluate_selected_channel
@ %def phs_rambo_evaluate_other_channels
@ Decay intermediate mass system $M_{i - 1}$ into a on-shell particle with mass
$m_{i - 1}$ and subsequent intermediate mass system with fixed $M_i$.
<<PHS rambo: phs rambo: TBP>>=
procedure, private :: decay_intermediate => phs_rambo_decay_intermediate
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_decay_intermediate (phs, i, r_angle, p)
class(phs_rambo_t), intent(in) :: phs
integer, intent(in) :: i
real(default), dimension(2), intent(in) :: r_angle
type(vector4_t), dimension(2), intent(out) :: p
end subroutine phs_rambo_decay_intermediate
<<PHS rambo: procedures>>=
module subroutine phs_rambo_decay_intermediate (phs, i, r_angle, p)
class(phs_rambo_t), intent(in) :: phs
integer, intent(in) :: i
real(default), dimension(2), intent(in) :: r_angle
type(vector4_t), dimension(2), intent(out) :: p
real(default) :: k_abs, cos_theta, phi
type(vector3_t):: k
real(default), dimension(2) :: E
cos_theta = 2. * r_angle(1) - 1.
phi = twopi * r_angle(2)
if (phi > pi) phi = phi - twopi
k_abs = sqrt (lambda (phs%m(i - 1)**2, phs%m(i)**2, phs%m_out(i - 1)**2)) &
/ (2. * phs%m(i - 1))
k = k_abs * [cos(phi) * sqrt(1. - cos_theta**2), &
sin(phi) * sqrt(1. - cos_theta**2), cos_theta]
E(1) = sqrt (phs%m(i)**2 + k_abs**2)
E(2) = sqrt (phs%m_out(i - 1)**2 + k_abs**2)
p(1) = vector4_moving (E(1), -k)
p(2) = vector4_moving (E(2), k)
end subroutine phs_rambo_decay_intermediate
@ %def phs_rambo_decay_intermediate
@ Generate intermediate masses.
<<PHS rambo: parameters>>=
integer, parameter :: BISECT_MAX_ITERATIONS = 1000
real(default), parameter :: BISECT_MIN_PRECISION = tiny_10
<<PHS rambo: phs rambo: TBP>>=
procedure, private :: generate_intermediates => &
phs_rambo_generate_intermediates
procedure, private :: invert_intermediates => phs_rambo_invert_intermediates
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_generate_intermediates (phs, r)
class(phs_rambo_t), intent(inout) :: phs
real(default), dimension(:), intent(in) :: r
end subroutine phs_rambo_generate_intermediates
module subroutine phs_rambo_invert_intermediates (phs)
class(phs_rambo_t), intent(inout) :: phs
end subroutine phs_rambo_invert_intermediates
<<PHS rambo: procedures>>=
module subroutine phs_rambo_generate_intermediates (phs, r)
class(phs_rambo_t), intent(inout) :: phs
real(default), dimension(:), intent(in) :: r
integer :: i, j
associate (n => phs%config%n_out, k => phs%k, m => phs%m)
m(1) = invariant_mass (sum (phs%p))
m(n) = phs%m_out (n)
call calculate_k (r)
do i = 2, n - 1
m(i) = k(i) + sum (phs%m_out (i:n))
end do
! Massless volume times reweighting for massive volume
phs%f(1) = k(1)**(2 * n - 4) &
* 8. * rho(m(n - 1), phs%m_out(n), phs%m_out(n - 1))
do i = 2, n - 1
phs%f(1) = phs%f(1) * &
rho(m(i - 1), m(i), phs%m_out(i - 1)) / &
rho(k(i - 1), k(i), 0._default) * &
M(i) / K(i)
end do
end associate
contains
subroutine calculate_k (r)
real(default), dimension(:), intent(in) :: r
real(default), dimension(:), allocatable :: u
integer :: i
associate (n => phs%config%n_out, k => phs%k, m => phs%m)
k = 0
k(1) = m(1) - sum(phs%m_out(1:n))
allocate (u(2:n - 1), source=0._default)
call solve_for_u (r, u)
do i = 2, n - 1
k(i) = sqrt (u(i) * k(i - 1)**2)
end do
end associate
end subroutine calculate_k
subroutine solve_for_u (r, u)
real(default), dimension(phs%config%n_out - 2), intent(in) :: r
real(default), dimension(2:phs%config%n_out - 1), intent(out) :: u
integer :: i, j
real(default) :: f, f_mid, xl, xr, xmid
associate (n => phs%config%n_out)
do i = 2, n - 1
xl = 0
xr = 1
if (r(i - 1) == 1 .or. r(i - 1) == 0) then
u(i) = r(i - 1)
else
do j = 1, BISECT_MAX_ITERATIONS
xmid = (xl + xr) / 2.
f = f_rambo (xl, n - i) - r(i - 1)
f_mid = f_rambo (xmid, n - i) - r(i - 1)
if (f * f_mid > 0) then
xl = xmid
else
xr = xmid
end if
if (abs(xl - xr) < BISECT_MIN_PRECISION) exit
end do
u(i) = xmid
end if
end do
end associate
end subroutine solve_for_u
real(default) function f_rambo(u, n)
real(default), intent(in) :: u
integer, intent(in) :: n
f_rambo = (n + 1) * u**n - n * u**(n + 1)
end function f_rambo
real(default) function rho (M1, M2, m)
real(default), intent(in) :: M1, M2, m
real(default) :: MP, MM
rho = sqrt ((M1**2 - (M2 + m)**2) * (M1**2 - (M2 - m)**2))
! MP = (M1 - (M2 + m)) * (M1 + (M2 + m))
! MM = (M1 - (M2 - m)) * (M1 + (M2 - m))
! rho = sqrt (MP) * sqrt (MM)
rho = rho / (8._default * M1**2)
end function rho
end subroutine phs_rambo_generate_intermediates
module subroutine phs_rambo_invert_intermediates (phs)
class(phs_rambo_t), intent(inout) :: phs
real(default) :: u
integer :: i
associate (n => phs%config%n_out, k => phs%k, m => phs%m)
k = m
do i = 1, n - 1
k(i) = k(i) - sum (phs%m_out(i:n))
end do
do i = 2, n - 1
u = (k(i) / k(i - 1))**2
phs%r(i - 1, 1) = (n + 1 - i) * u**(n - i) &
- (n - i) * u**(n + 1 - i)
end do
end associate
end subroutine phs_rambo_invert_intermediates
@ %def phs_rambo_generate_intermediates
@ Inverse evaluation.
<<PHS rambo: phs rambo: TBP>>=
procedure :: inverse => phs_rambo_inverse
<<PHS rambo: sub interfaces>>=
module subroutine phs_rambo_inverse (phs)
class(phs_rambo_t), intent(inout) :: phs
end subroutine phs_rambo_inverse
<<PHS rambo: procedures>>=
module subroutine phs_rambo_inverse (phs)
class(phs_rambo_t), intent(inout) :: phs
type(vector4_t), dimension(:), allocatable :: q
type(vector4_t) :: p
type(lorentz_transformation_t) :: L
real(default) :: phi, cos_theta
integer :: i
if (.not. (phs%p_defined .and. phs%q_defined)) return
call phs%select_channel ()
associate (n => phs%config%n_out, m => phs%m)
allocate(q(n))
m(1) = invariant_mass (sum (phs%p))
q(1) = vector4_at_rest (m(1))
q(n) = phs%q(n)
do i = 2, n - 1
q(i) = q(i) + sum (phs%q(i:n))
m(i) = invariant_mass (q(i))
end do
call phs%invert_intermediates ()
do i = 2, n
L = inverse (boost (q(i - 1), m(i - 1)))
p = L * phs%q(i - 1)
phi = azimuthal_angle (p); cos_theta = polar_angle_ct (p)
phs%r(n - 5 + 2 * i, 1) = (cos_theta + 1.) / 2.
phs%r(n - 4 + 2 * i, 1) = phi / twopi
end do
end associate
phs%r_defined = .true.
end subroutine phs_rambo_inverse
@ %def phs_rambo_inverse
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_rambo_ut.f90]]>>=
<<File header>>
module phs_rambo_ut
use unit_tests
use phs_rambo_uti
<<Standard module head>>
<<PHS rambo: public test>>
contains
<<PHS rambo: test driver>>
end module phs_rambo_ut
@ %def phs_rambo_ut
@
<<[[phs_rambo_uti.f90]]>>=
<<File header>>
module phs_rambo_uti
<<Use kinds>>
<<Use strings>>
use flavors
use lorentz
use model_data
use process_constants
use phs_base
use phs_rambo
use phs_base_ut, only: init_test_process_data, init_test_decay_data
<<Standard module head>>
<<PHS rambo: test declarations>>
contains
<<PHS rambo: tests>>
end module phs_rambo_uti
@ %def phs_rambo_ut
@ API: driver for the unit tests below.
<<PHS rambo: public test>>=
public :: phs_rambo_test
<<PHS rambo: test driver>>=
subroutine phs_rambo_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS rambo: execute tests>>
end subroutine phs_rambo_test
@ %def phs_rambo_test
@
\subsubsection{Phase-space configuration data}
Construct and display a test phase-space configuration object. Also
check the [[azimuthal_dependence]] flag.
<<PHS rambo: execute tests>>=
call test (phs_rambo_1, "phs_rambo_1", &
"phase-space configuration", &
u, results)
<<PHS rambo: test declarations>>=
public :: phs_rambo_1
<<PHS rambo: tests>>=
subroutine phs_rambo_1 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
real(default) :: sqrts
write (u, "(A)") "* Test output: phs_rambo_1"
write (u, "(A)") "* Purpose: initialize and display &
&phase-space configuration data"
write (u, "(A)")
call model%init_test ()
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_rambo_1"), process_data)
allocate (phs_rambo_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_rambo_1"
end subroutine phs_rambo_1
@ %def phs_rambo_1
@
\subsubsection{Phase space evaluation}
Compute kinematics for given parameters, also invert the calculation.
<<PHS rambo: execute tests>>=
call test (phs_rambo_2, "phs_rambo_2", &
"phase-space evaluation", &
u, results)
<<PHS rambo: test declarations>>=
public :: phs_rambo_2
<<PHS rambo: tests>>=
subroutine phs_rambo_2 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
real(default) :: sqrts, E
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(2) :: p, q
write (u, "(A)") "* Test output: phs_rambo_2"
write (u, "(A)") "* Purpose: test simple two-channel phase space"
write (u, "(A)")
call model%init_test ()
call flv%init (25, model)
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_rambo_2"), process_data)
allocate (phs_rambo_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
E = sqrts / 2
p(1) = vector4_moving (E, sqrt (E**2 - flv%get_mass ()**2), 3)
p(2) = vector4_moving (E,-sqrt (E**2 - flv%get_mass ()**2), 3)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_rambo_2"
end subroutine phs_rambo_2
@ %def phs_rambo_2
@
\subsubsection{Phase space for non-c.m. system}
Compute kinematics for given parameters, also invert the calculation.
Since this will involve cancellations, we call [[pacify]] to eliminate
numerical noise.
<<PHS rambo: execute tests>>=
call test (phs_rambo_3, "phs_rambo_3", &
"phase-space evaluation in lab frame", &
u, results)
<<PHS rambo: test declarations>>=
public :: phs_rambo_3
<<PHS rambo: tests>>=
subroutine phs_rambo_3 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
real(default) :: sqrts, E
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(2) :: p, q
type(lorentz_transformation_t) :: lt
write (u, "(A)") "* Test output: phs_rambo_3"
write (u, "(A)") "* Purpose: phase-space evaluation in lab frame"
write (u, "(A)")
call model%init_test ()
call flv%init (25, model)
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_rambo_3"), process_data)
allocate (phs_rambo_config_t :: phs_data)
call phs_data%init (process_data, model)
sqrts = 1000._default
call phs_data%configure (sqrts, lab_is_cm=.false., sqrts_fixed=.false.)
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta in lab system"
write (u, "(A)")
lt = boost (0.1_default, 1) * boost (0.3_default, 3)
E = sqrts / 2
p(1) = lt * vector4_moving (E, sqrt (E**2 - flv%get_mass ()**2), 3)
p(2) = lt * vector4_moving (E,-sqrt (E**2 - flv%get_mass ()**2), 3)
call vector4_write (p(1), u)
call vector4_write (p(2), u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call pacify (phs)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Extract outgoing momenta in lab system"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
call vector4_write (q(1), u)
call vector4_write (q(2), u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call pacify (phs)
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_rambo_3"
end subroutine phs_rambo_3
@ %def phs_rambo_3
@
\subsubsection{Decay Phase space evaluation}
Compute kinematics for given parameters, also invert the calculation. This
time, implement a decay process.
<<PHS rambo: execute tests>>=
call test (phs_rambo_4, "phs_rambo_4", &
"decay phase-space evaluation", &
u, results)
<<PHS rambo: test declarations>>=
public :: phs_rambo_4
<<PHS rambo: tests>>=
subroutine phs_rambo_4 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(1) :: p
type(vector4_t), dimension(2) :: q
write (u, "(A)") "* Test output: phs_rambo_4"
write (u, "(A)") "* Purpose: test simple two-channel phase space"
write (u, "(A)")
call model%init_test ()
call model%set_par (var_str ("ff"), 0.4_default)
call model%set_par (var_str ("mf"), &
model%get_real (var_str ("ff")) * model%get_real (var_str ("ms")))
call flv%init (25, model)
write (u, "(A)") "* Initialize a decay and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_decay_data (var_str ("phs_rambo_4"), process_data)
allocate (phs_rambo_config_t :: phs_data)
call phs_data%init (process_data, model)
call phs_data%configure (flv%get_mass ())
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
p(1) = vector4_at_rest (flv%get_mass ())
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.5, 0.125"
write (u, "(A)")
call phs%evaluate_selected_channel (1, [0.5_default, 0.125_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs_data%configure (flv%get_mass ())
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_rambo_4"
end subroutine phs_rambo_4
@ %def phs_rambo_4
@
\clearpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Resonance Handler}
For various purposes (e.g., shower histories), we should extract the set of
resonances and resonant channels from a phase-space tree set. A few methods
do kinematics calculations specifically for those resonance data.
<<[[resonances.f90]]>>=
<<File header>>
module resonances
<<Use kinds>>
<<Use strings>>
use lorentz
use model_data, only: model_data_t
use flavors, only: flavor_t
<<Standard module head>>
<<Resonances: public>>
<<Resonances: parameters>>
<<Resonances: types>>
interface
<<Resonances: sub interfaces>>
end interface
end module resonances
@ %def resonances
@
<<[[resonances_sub.f90]]>>=
<<File header>>
submodule (resonances) resonances_s
<<Use debug>>
use string_utils, only: str
use format_utils, only: write_indent
use constants, only: one
use io_units
use diagnostics
implicit none
contains
<<Resonances: procedures>>
end submodule resonances_s
@ %def resonances_s
@
\subsection{Decay products (contributors)}
This stores the indices of the particles that contribute to a resonance, i.e.,
the decay products.
<<Resonances: public>>=
public :: resonance_contributors_t
<<Resonances: types>>=
type :: resonance_contributors_t
integer, dimension(:), allocatable :: c
contains
<<Resonances: resonance contributors: TBP>>
end type resonance_contributors_t
@ %def resonance_contributors_t
@ Equality (comparison)
<<Resonances: resonance contributors: TBP>>=
procedure, private :: resonance_contributors_equal
generic :: operator(==) => resonance_contributors_equal
<<Resonances: sub interfaces>>=
elemental module function resonance_contributors_equal &
(c1, c2) result (equal)
logical :: equal
class(resonance_contributors_t), intent(in) :: c1, c2
end function resonance_contributors_equal
<<Resonances: procedures>>=
elemental module function resonance_contributors_equal &
(c1, c2) result (equal)
logical :: equal
class(resonance_contributors_t), intent(in) :: c1, c2
equal = allocated (c1%c) .and. allocated (c2%c)
if (equal) equal = size (c1%c) == size (c2%c)
if (equal) equal = all (c1%c == c2%c)
end function resonance_contributors_equal
@ %def resonance_contributors_equal
@ Assignment
<<Resonances: resonance contributors: TBP>>=
procedure, private :: resonance_contributors_assign
generic :: assignment(=) => resonance_contributors_assign
<<Resonances: sub interfaces>>=
pure module subroutine resonance_contributors_assign &
(contributors_out, contributors_in)
class(resonance_contributors_t), intent(inout) :: contributors_out
class(resonance_contributors_t), intent(in) :: contributors_in
end subroutine resonance_contributors_assign
<<Resonances: procedures>>=
pure module subroutine resonance_contributors_assign &
(contributors_out, contributors_in)
class(resonance_contributors_t), intent(inout) :: contributors_out
class(resonance_contributors_t), intent(in) :: contributors_in
if (allocated (contributors_out%c)) deallocate (contributors_out%c)
if (allocated (contributors_in%c)) then
allocate (contributors_out%c (size (contributors_in%c)))
contributors_out%c = contributors_in%c
end if
end subroutine resonance_contributors_assign
@ %def resonance_contributors_assign
@
\subsection{Resonance info object}
This data structure augments the set of resonance contributors by a flavor
object, such that we can perform calculations that take into
account the particle properties, including mass and width.
Avoiding nameclash with similar but different [[resonance_t]] of
[[phs_base]]:
<<Resonances: public>>=
public :: resonance_info_t
<<Resonances: types>>=
type :: resonance_info_t
type(flavor_t) :: flavor
type(resonance_contributors_t) :: contributors
contains
<<Resonances: resonance info: TBP>>
end type resonance_info_t
@ %def resonance_info_t
@
<<Resonances: resonance info: TBP>>=
procedure :: copy => resonance_info_copy
<<Resonances: sub interfaces>>=
module subroutine resonance_info_copy (resonance_in, resonance_out)
class(resonance_info_t), intent(in) :: resonance_in
type(resonance_info_t), intent(out) :: resonance_out
end subroutine resonance_info_copy
<<Resonances: procedures>>=
module subroutine resonance_info_copy (resonance_in, resonance_out)
class(resonance_info_t), intent(in) :: resonance_in
type(resonance_info_t), intent(out) :: resonance_out
resonance_out%flavor = resonance_in%flavor
if (allocated (resonance_in%contributors%c)) then
associate (c => resonance_in%contributors%c)
allocate (resonance_out%contributors%c (size (c)))
resonance_out%contributors%c = c
end associate
end if
end subroutine resonance_info_copy
@ %def resonance_info_copy
@
<<Resonances: resonance info: TBP>>=
procedure :: write => resonance_info_write
<<Resonances: sub interfaces>>=
module subroutine resonance_info_write (resonance, unit, verbose)
class(resonance_info_t), intent(in) :: resonance
integer, optional, intent(in) :: unit
logical, optional, intent(in) :: verbose
end subroutine resonance_info_write
<<Resonances: procedures>>=
module subroutine resonance_info_write (resonance, unit, verbose)
class(resonance_info_t), intent(in) :: resonance
integer, optional, intent(in) :: unit
logical, optional, intent(in) :: verbose
integer :: u, i
logical :: verb
u = given_output_unit (unit); if (u < 0) return
verb = .true.; if (present (verbose)) verb = verbose
if (verb) then
write (u, '(A)', advance='no') "Resonance contributors: "
else
write (u, '(1x)', advance="no")
end if
if (allocated (resonance%contributors%c)) then
do i = 1, size(resonance%contributors%c)
write (u, '(I0,1X)', advance='no') resonance%contributors%c(i)
end do
else if (verb) then
write (u, "(A)", advance="no") "[not allocated]"
end if
if (resonance%flavor%is_defined ()) call resonance%flavor%write (u)
write (u, '(A)')
end subroutine resonance_info_write
@ %def resonance_info_write
@ Create a resonance-info object. The particle info may be available
in term of a flavor object or as a PDG code; in the latter case we
have to require a model data object that provides mass and width information.
<<Resonances: resonance info: TBP>>=
procedure, private :: resonance_info_init_pdg
procedure, private :: resonance_info_init_flv
generic :: init => resonance_info_init_pdg, resonance_info_init_flv
<<Resonances: sub interfaces>>=
module subroutine resonance_info_init_pdg &
(resonance, mom_id, pdg, model, n_out)
class(resonance_info_t), intent(out) :: resonance
integer, intent(in) :: mom_id
integer, intent(in) :: pdg, n_out
class(model_data_t), intent(in), target :: model
end subroutine resonance_info_init_pdg
module subroutine resonance_info_init_flv (resonance, mom_id, flv, n_out)
class(resonance_info_t), intent(out) :: resonance
integer, intent(in) :: mom_id
type(flavor_t), intent(in) :: flv
integer, intent(in) :: n_out
end subroutine resonance_info_init_flv
<<Resonances: procedures>>=
module subroutine resonance_info_init_pdg &
(resonance, mom_id, pdg, model, n_out)
class(resonance_info_t), intent(out) :: resonance
integer, intent(in) :: mom_id
integer, intent(in) :: pdg, n_out
class(model_data_t), intent(in), target :: model
type(flavor_t) :: flv
if (debug_on) call msg_debug (D_PHASESPACE, "resonance_info_init_pdg")
call flv%init (pdg, model)
call resonance%init (mom_id, flv, n_out)
end subroutine resonance_info_init_pdg
module subroutine resonance_info_init_flv (resonance, mom_id, flv, n_out)
class(resonance_info_t), intent(out) :: resonance
integer, intent(in) :: mom_id
type(flavor_t), intent(in) :: flv
integer, intent(in) :: n_out
integer :: i
logical, dimension(n_out) :: contrib
integer, dimension(n_out) :: tmp
if (debug_on) call msg_debug (D_PHASESPACE, "resonance_info_init_flv")
resonance%flavor = flv
do i = 1, n_out
tmp(i) = i
end do
contrib = btest (mom_id, tmp - 1)
allocate (resonance%contributors%c (count (contrib)))
resonance%contributors%c = pack (tmp, contrib)
end subroutine resonance_info_init_flv
@ %def resonance_info_init
@
<<Resonances: resonance info: TBP>>=
procedure, private :: resonance_info_equal
generic :: operator(==) => resonance_info_equal
<<Resonances: sub interfaces>>=
elemental module function resonance_info_equal (r1, r2) result (equal)
logical :: equal
class(resonance_info_t), intent(in) :: r1, r2
end function resonance_info_equal
<<Resonances: procedures>>=
elemental module function resonance_info_equal (r1, r2) result (equal)
logical :: equal
class(resonance_info_t), intent(in) :: r1, r2
equal = r1%flavor == r2%flavor .and. r1%contributors == r2%contributors
end function resonance_info_equal
@ %def resonance_info_equal
@ With each resonance region we associate a Breit-Wigner function
\begin{equation*}
P = \frac{M_{res}^4}{(s - M_{res}^2)^2 + \Gamma_{res}^2 M_{res}^2},
\end{equation*}
where $s$ denotes the invariant mass of the outgoing momenta originating
from this resonance. Note that the $M_{res}^4$ in the nominator makes
the mapping a dimensionless quantity.
<<Resonances: resonance info: TBP>>=
procedure :: mapping => resonance_info_mapping
<<Resonances: sub interfaces>>=
module function resonance_info_mapping (resonance, s) result (bw)
real(default) :: bw
class(resonance_info_t), intent(in) :: resonance
real(default), intent(in) :: s
end function resonance_info_mapping
<<Resonances: procedures>>=
module function resonance_info_mapping (resonance, s) result (bw)
real(default) :: bw
class(resonance_info_t), intent(in) :: resonance
real(default), intent(in) :: s
real(default) :: m, gamma
if (resonance%flavor%is_defined ()) then
m = resonance%flavor%get_mass ()
gamma = resonance%flavor%get_width ()
bw = m**4 / ((s - m**2)**2 + gamma**2 * m**2)
else
bw = one
end if
end function resonance_info_mapping
@ %def resonance_info_mapping
@ Used for building a resonance tree below.
<<Resonances: resonance info: TBP>>=
procedure, private :: get_n_contributors => resonance_info_get_n_contributors
procedure, private :: contains => resonance_info_contains
<<Resonances: sub interfaces>>=
elemental module function resonance_info_get_n_contributors &
(resonance) result (n)
class(resonance_info_t), intent(in) :: resonance
integer :: n
end function resonance_info_get_n_contributors
elemental module function resonance_info_contains &
(resonance, c) result (flag)
class(resonance_info_t), intent(in) :: resonance
integer, intent(in) :: c
logical :: flag
end function resonance_info_contains
<<Resonances: procedures>>=
elemental module function resonance_info_get_n_contributors &
(resonance) result (n)
class(resonance_info_t), intent(in) :: resonance
integer :: n
if (allocated (resonance%contributors%c)) then
n = size (resonance%contributors%c)
else
n = 0
end if
end function resonance_info_get_n_contributors
elemental module function resonance_info_contains &
(resonance, c) result (flag)
class(resonance_info_t), intent(in) :: resonance
integer, intent(in) :: c
logical :: flag
if (allocated (resonance%contributors%c)) then
flag = any (resonance%contributors%c == c)
else
flag = .false.
end if
end function resonance_info_contains
@ %def resonance_info_get_n_contributors
@ %def resonance_info_contains
@
\subsection{Resonance history object}
This data structure stores a set of resonances, i.e., the resonances that
appear in a particular Feynman graph or, in the context of phase space, phase
space diagram.
<<Resonances: public>>=
public :: resonance_history_t
<<Resonances: types>>=
type :: resonance_history_t
type(resonance_info_t), dimension(:), allocatable :: resonances
integer :: n_resonances = 0
contains
<<Resonances: resonance history: TBP>>
end type resonance_history_t
@ %def resonance_history_t
@ Clear the resonance history. Assuming that there are no
pointer-allocated parts, a straightforward [[intent(out)]] will do.
<<Resonances: resonance history: TBP>>=
procedure :: clear => resonance_history_clear
<<Resonances: sub interfaces>>=
module subroutine resonance_history_clear (res_hist)
class(resonance_history_t), intent(out) :: res_hist
end subroutine resonance_history_clear
<<Resonances: procedures>>=
module subroutine resonance_history_clear (res_hist)
class(resonance_history_t), intent(out) :: res_hist
end subroutine resonance_history_clear
@ %def resonance_history_clear
@
<<Resonances: resonance history: TBP>>=
procedure :: copy => resonance_history_copy
<<Resonances: sub interfaces>>=
module subroutine resonance_history_copy (res_hist_in, res_hist_out)
class(resonance_history_t), intent(in) :: res_hist_in
type(resonance_history_t), intent(out) :: res_hist_out
end subroutine resonance_history_copy
<<Resonances: procedures>>=
module subroutine resonance_history_copy (res_hist_in, res_hist_out)
class(resonance_history_t), intent(in) :: res_hist_in
type(resonance_history_t), intent(out) :: res_hist_out
integer :: i
res_hist_out%n_resonances = res_hist_in%n_resonances
allocate (res_hist_out%resonances (size (res_hist_in%resonances)))
do i = 1, size (res_hist_in%resonances)
call res_hist_in%resonances(i)%copy (res_hist_out%resonances(i))
end do
end subroutine resonance_history_copy
@ %def resonance_history_copy
@
<<Resonances: resonance history: TBP>>=
procedure :: write => resonance_history_write
<<Resonances: sub interfaces>>=
module subroutine resonance_history_write (res_hist, unit, verbose, indent)
class(resonance_history_t), intent(in) :: res_hist
integer, optional, intent(in) :: unit
logical, optional, intent(in) :: verbose
integer, optional, intent(in) :: indent
end subroutine resonance_history_write
<<Resonances: procedures>>=
module subroutine resonance_history_write (res_hist, unit, verbose, indent)
class(resonance_history_t), intent(in) :: res_hist
integer, optional, intent(in) :: unit
logical, optional, intent(in) :: verbose
integer, optional, intent(in) :: indent
integer :: u, i
u = given_output_unit (unit); if (u < 0) return
call write_indent (u, indent)
write(u, '(A,I0,A)') "Resonance history with ", &
res_hist%n_resonances, " resonances:"
do i = 1, res_hist%n_resonances
call write_indent (u, indent)
write (u, "(2x)", advance="no")
call res_hist%resonances(i)%write (u, verbose)
end do
end subroutine resonance_history_write
@ %def resonance_history_write
@ Assignment. Indirectly calls type-bound assignment for the contributors.
Strictly speaking, this is redundant. But NAGfor 6.208 intrinsic assignment
crashes under certain conditions.
<<Resonances: resonance history: TBP>>=
procedure, private :: resonance_history_assign
generic :: assignment(=) => resonance_history_assign
<<Resonances: sub interfaces>>=
module subroutine resonance_history_assign (res_hist_out, res_hist_in)
class(resonance_history_t), intent(out) :: res_hist_out
class(resonance_history_t), intent(in) :: res_hist_in
end subroutine resonance_history_assign
<<Resonances: procedures>>=
module subroutine resonance_history_assign (res_hist_out, res_hist_in)
class(resonance_history_t), intent(out) :: res_hist_out
class(resonance_history_t), intent(in) :: res_hist_in
if (allocated (res_hist_in%resonances)) then
res_hist_out%resonances = res_hist_in%resonances
res_hist_out%n_resonances = res_hist_in%n_resonances
end if
end subroutine resonance_history_assign
@ %def resonance_history_assign
@ Equality. If this turns out to slow down the program, we should
change the implementation or use hash codes.
<<Resonances: resonance history: TBP>>=
procedure, private :: resonance_history_equal
generic :: operator(==) => resonance_history_equal
<<Resonances: sub interfaces>>=
elemental module function resonance_history_equal (rh1, rh2) result (equal)
logical :: equal
class(resonance_history_t), intent(in) :: rh1, rh2
end function resonance_history_equal
<<Resonances: procedures>>=
elemental module function resonance_history_equal (rh1, rh2) result (equal)
logical :: equal
class(resonance_history_t), intent(in) :: rh1, rh2
integer :: i
equal = .false.
if (rh1%n_resonances == rh2%n_resonances) then
do i = 1, rh1%n_resonances
if (.not. rh1%resonances(i) == rh2%resonances(i)) then
return
end if
end do
equal = .true.
end if
end function resonance_history_equal
@ %def resonance_history_equal
@ Check if a resonance history is a strict superset of another one. This is
true if the first one is nonempty and the second one is empty.
Otherwise, we check if each entry of the second argument appears in
the first one.
<<Resonances: resonance history: TBP>>=
procedure, private :: resonance_history_contains
generic :: operator(.contains.) => resonance_history_contains
<<Resonances: sub interfaces>>=
elemental module function resonance_history_contains &
(rh1, rh2) result (flag)
logical :: flag
class(resonance_history_t), intent(in) :: rh1, rh2
end function resonance_history_contains
<<Resonances: procedures>>=
elemental module function resonance_history_contains &
(rh1, rh2) result (flag)
logical :: flag
class(resonance_history_t), intent(in) :: rh1, rh2
integer :: i
if (rh1%n_resonances > rh2%n_resonances) then
flag = .true.
do i = 1, rh2%n_resonances
flag = flag .and. any (rh1%resonances == rh2%resonances(i))
end do
else
flag = .false.
end if
end function resonance_history_contains
@ %def resonance_history_contains
@ Number of entries for dynamically extending the resonance-info array.
<<Resonances: parameters>>=
integer, parameter :: n_max_resonances = 10
@
<<Resonances: resonance history: TBP>>=
procedure :: add_resonance => resonance_history_add_resonance
<<Resonances: sub interfaces>>=
module subroutine resonance_history_add_resonance (res_hist, resonance)
class(resonance_history_t), intent(inout) :: res_hist
type(resonance_info_t), intent(in) :: resonance
end subroutine resonance_history_add_resonance
<<Resonances: procedures>>=
module subroutine resonance_history_add_resonance (res_hist, resonance)
class(resonance_history_t), intent(inout) :: res_hist
type(resonance_info_t), intent(in) :: resonance
type(resonance_info_t), dimension(:), allocatable :: tmp
integer :: n, i
if (debug_on) call msg_debug &
(D_PHASESPACE, "resonance_history_add_resonance")
if (.not. allocated (res_hist%resonances)) then
n = 0
allocate (res_hist%resonances (1))
else
n = res_hist%n_resonances
allocate (tmp (n))
do i = 1, n
call res_hist%resonances(i)%copy (tmp(i))
end do
deallocate (res_hist%resonances)
allocate (res_hist%resonances (n+1))
do i = 1, n
call tmp(i)%copy (res_hist%resonances(i))
end do
deallocate (tmp)
end if
call resonance%copy (res_hist%resonances(n+1))
res_hist%n_resonances = n + 1
if (debug_on) call msg_debug &
(D_PHASESPACE, "res_hist%n_resonances", res_hist%n_resonances)
end subroutine resonance_history_add_resonance
@ %def resonance_history_add_resonance
@
<<Resonances: resonance history: TBP>>=
procedure :: remove_resonance => resonance_history_remove_resonance
<<Resonances: sub interfaces>>=
module subroutine resonance_history_remove_resonance (res_hist, i_res)
class(resonance_history_t), intent(inout) :: res_hist
integer, intent(in) :: i_res
end subroutine resonance_history_remove_resonance
<<Resonances: procedures>>=
module subroutine resonance_history_remove_resonance (res_hist, i_res)
class(resonance_history_t), intent(inout) :: res_hist
integer, intent(in) :: i_res
type(resonance_info_t), dimension(:), allocatable :: tmp_1, tmp_2
integer :: i, j, n
n = res_hist%n_resonances
res_hist%n_resonances = n - 1
if (res_hist%n_resonances == 0) then
deallocate (res_hist%resonances)
else
if (i_res > 1) allocate (tmp_1(1:i_res-1))
if (i_res < n) allocate (tmp_2(i_res+1:n))
if (allocated (tmp_1)) then
do i = 1, i_res - 1
call res_hist%resonances(i)%copy (tmp_1(i))
end do
end if
if (allocated (tmp_2)) then
do i = i_res + 1, n
call res_hist%resonances(i)%copy (tmp_2(i))
end do
end if
deallocate (res_hist%resonances)
allocate (res_hist%resonances (res_hist%n_resonances))
j = 1
if (allocated (tmp_1)) then
do i = 1, i_res - 1
call tmp_1(i)%copy (res_hist%resonances(j))
j = j + 1
end do
deallocate (tmp_1)
end if
if (allocated (tmp_2)) then
do i = i_res + 1, n
call tmp_2(i)%copy (res_hist%resonances(j))
j = j + 1
end do
deallocate (tmp_2)
end if
end if
end subroutine resonance_history_remove_resonance
@ %def resonance_history_remove_resonance
@
<<Resonances: resonance history: TBP>>=
procedure :: add_offset => resonance_history_add_offset
<<Resonances: sub interfaces>>=
module subroutine resonance_history_add_offset (res_hist, n)
class(resonance_history_t), intent(inout) :: res_hist
integer, intent(in) :: n
end subroutine resonance_history_add_offset
<<Resonances: procedures>>=
module subroutine resonance_history_add_offset (res_hist, n)
class(resonance_history_t), intent(inout) :: res_hist
integer, intent(in) :: n
integer :: i_res
do i_res = 1, res_hist%n_resonances
associate (contributors => res_hist%resonances(i_res)%contributors%c)
contributors = contributors + n
end associate
end do
end subroutine resonance_history_add_offset
@ %def resonance_history_add_offset
@
<<Resonances: resonance history: TBP>>=
procedure :: contains_leg => resonance_history_contains_leg
<<Resonances: sub interfaces>>=
module function resonance_history_contains_leg &
(res_hist, i_leg) result (val)
logical :: val
class(resonance_history_t), intent(in) :: res_hist
integer, intent(in) :: i_leg
end function resonance_history_contains_leg
<<Resonances: procedures>>=
module function resonance_history_contains_leg &
(res_hist, i_leg) result (val)
logical :: val
class(resonance_history_t), intent(in) :: res_hist
integer, intent(in) :: i_leg
integer :: i_res
val = .false.
do i_res = 1, res_hist%n_resonances
if (any (res_hist%resonances(i_res)%contributors%c == i_leg)) then
val = .true.
exit
end if
end do
end function resonance_history_contains_leg
@ %def resonance_history_contains_leg
@
<<Resonances: resonance history: TBP>>=
procedure :: mapping => resonance_history_mapping
<<Resonances: sub interfaces>>=
module function resonance_history_mapping &
(res_hist, p, i_gluon) result (p_map)
real(default) :: p_map
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), intent(in), dimension(:) :: p
integer, intent(in), optional :: i_gluon
end function resonance_history_mapping
<<Resonances: procedures>>=
module function resonance_history_mapping &
(res_hist, p, i_gluon) result (p_map)
real(default) :: p_map
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), intent(in), dimension(:) :: p
integer, intent(in), optional :: i_gluon
integer :: i_res
real(default) :: s
p_map = one
do i_res = 1, res_hist%n_resonances
associate (res => res_hist%resonances(i_res))
s = compute_resonance_mass (p, res%contributors%c, i_gluon)**2
p_map = p_map * res%mapping (s)
end associate
end do
end function resonance_history_mapping
@ %def resonance_history_mapping
@ This predicate is true if all resonances in the history have exactly
[[n]] contributors. For instance, if $n=2$, all resonances have a
two-particle decay.
<<Resonances: resonance history: TBP>>=
procedure :: only_has_n_contributors => &
resonance_history_only_has_n_contributors
<<Resonances: sub interfaces>>=
module function resonance_history_only_has_n_contributors &
(res_hist, n) result (value)
logical :: value
class(resonance_history_t), intent(in) :: res_hist
integer, intent(in) :: n
end function resonance_history_only_has_n_contributors
<<Resonances: procedures>>=
module function resonance_history_only_has_n_contributors &
(res_hist, n) result (value)
logical :: value
class(resonance_history_t), intent(in) :: res_hist
integer, intent(in) :: n
integer :: i_res
value = .true.
do i_res = 1, res_hist%n_resonances
associate (res => res_hist%resonances(i_res))
value = value .and. size (res%contributors%c) == n
end associate
end do
end function resonance_history_only_has_n_contributors
@ %def resonance_history_only_has_n_contributors
@
<<Resonances: resonance history: TBP>>=
procedure :: has_flavor => resonance_history_has_flavor
<<Resonances: sub interfaces>>=
module function resonance_history_has_flavor &
(res_hist, flv) result (has_flv)
logical :: has_flv
class(resonance_history_t), intent(in) :: res_hist
type(flavor_t), intent(in) :: flv
end function resonance_history_has_flavor
<<Resonances: procedures>>=
module function resonance_history_has_flavor &
(res_hist, flv) result (has_flv)
logical :: has_flv
class(resonance_history_t), intent(in) :: res_hist
type(flavor_t), intent(in) :: flv
integer :: i
has_flv = .false.
do i = 1, res_hist%n_resonances
has_flv = has_flv .or. res_hist%resonances(i)%flavor == flv
end do
end function resonance_history_has_flavor
@ %def resonance_history_has_flavor
@
\subsection{Kinematics}
Evaluate the distance from a resonance. The distance is given by
$|p^2-m^2|/(m\Gamma)$. For $\Gamma\ll m$, this is the relative
distance from the resonance peak in units of the half-width.
<<Resonances: resonance info: TBP>>=
procedure :: evaluate_distance => resonance_info_evaluate_distance
<<Resonances: sub interfaces>>=
module subroutine resonance_info_evaluate_distance (res_info, p, dist)
class(resonance_info_t), intent(in) :: res_info
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(out) :: dist
end subroutine resonance_info_evaluate_distance
<<Resonances: procedures>>=
module subroutine resonance_info_evaluate_distance (res_info, p, dist)
class(resonance_info_t), intent(in) :: res_info
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(out) :: dist
real(default) :: m, w
type(vector4_t) :: q
m = res_info%flavor%get_mass ()
w = res_info%flavor%get_width ()
q = sum (p(res_info%contributors%c))
dist = abs (q**2 - m**2) / (m * w)
end subroutine resonance_info_evaluate_distance
@ %def resonance_info_evaluate_distance
@
Evaluate the array of distances from a resonance history. We assume that the
array has been allocated with correct size, namely the number of resonances in
this history.
<<Resonances: resonance history: TBP>>=
procedure :: evaluate_distances => resonance_history_evaluate_distances
<<Resonances: sub interfaces>>=
module subroutine resonance_history_evaluate_distances (res_hist, p, dist)
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), dimension(:), intent(in) :: p
real(default), dimension(:), intent(out) :: dist
end subroutine resonance_history_evaluate_distances
<<Resonances: procedures>>=
module subroutine resonance_history_evaluate_distances (res_hist, p, dist)
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), dimension(:), intent(in) :: p
real(default), dimension(:), intent(out) :: dist
integer :: i
do i = 1, res_hist%n_resonances
call res_hist%resonances(i)%evaluate_distance (p, dist(i))
end do
end subroutine resonance_history_evaluate_distances
@ %def resonance_history_evaluate_distances
@ Use the distance to determine a Gaussian turnoff factor for a
resonance. The factor is given by a Gaussian function
$e^{-d^2/\sigma^2}$, where $\sigma$ is the [[gw]] parameter multiplied
by the resonance width, and $d$ is the distance (see above). So, for
$d=\sigma$, the factor is $0.37$, and for $d=2\sigma$ we get $0.018$.
If the [[gw]] factor is less or equal to zero, return $1$.
<<Resonances: resonance info: TBP>>=
procedure :: evaluate_gaussian => resonance_info_evaluate_gaussian
<<Resonances: sub interfaces>>=
module function resonance_info_evaluate_gaussian &
(res_info, p, gw) result (factor)
class(resonance_info_t), intent(in) :: res_info
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: gw
real(default) :: factor
end function resonance_info_evaluate_gaussian
<<Resonances: procedures>>=
module function resonance_info_evaluate_gaussian &
(res_info, p, gw) result (factor)
class(resonance_info_t), intent(in) :: res_info
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: gw
real(default) :: factor
real(default) :: dist, w
if (gw > 0) then
w = res_info%flavor%get_width ()
call res_info%evaluate_distance (p, dist)
factor = exp (- (dist / (gw * w)) **2)
else
factor = 1
end if
end function resonance_info_evaluate_gaussian
@ %def resonance_info_evaluate_gaussian
@ The Gaussian factor of the history is the product of all factors.
<<Resonances: resonance history: TBP>>=
procedure :: evaluate_gaussian => resonance_history_evaluate_gaussian
<<Resonances: sub interfaces>>=
module function resonance_history_evaluate_gaussian &
(res_hist, p, gw) result (factor)
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: gw
real(default) :: factor
end function resonance_history_evaluate_gaussian
<<Resonances: procedures>>=
module function resonance_history_evaluate_gaussian &
(res_hist, p, gw) result (factor)
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: gw
real(default), dimension(:), allocatable :: dist
real(default) :: factor
integer :: i
factor = 1
do i = 1, res_hist%n_resonances
factor = factor * res_hist%resonances(i)%evaluate_gaussian (p, gw)
end do
end function resonance_history_evaluate_gaussian
@ %def resonance_history_evaluate_gaussian
@
Use the distances to determine whether the resonance history can qualify as
on-shell. The criterion is whether the distance is greater than the number of
width values as given by [[on_shell_limit]].
<<Resonances: resonance info: TBP>>=
procedure :: is_on_shell => resonance_info_is_on_shell
<<Resonances: sub interfaces>>=
module function resonance_info_is_on_shell (res_info, p, on_shell_limit) &
result (flag)
class(resonance_info_t), intent(in) :: res_info
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: on_shell_limit
logical :: flag
end function resonance_info_is_on_shell
<<Resonances: procedures>>=
module function resonance_info_is_on_shell (res_info, p, on_shell_limit) &
result (flag)
class(resonance_info_t), intent(in) :: res_info
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: on_shell_limit
logical :: flag
real(default) :: dist
call res_info%evaluate_distance (p, dist)
flag = dist < on_shell_limit
end function resonance_info_is_on_shell
@ %def resonance_info_is_on_shell
@
<<Resonances: resonance history: TBP>>=
procedure :: is_on_shell => resonance_history_is_on_shell
<<Resonances: sub interfaces>>=
module function resonance_history_is_on_shell &
(res_hist, p, on_shell_limit) result (flag)
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: on_shell_limit
logical :: flag
end function resonance_history_is_on_shell
<<Resonances: procedures>>=
module function resonance_history_is_on_shell &
(res_hist, p, on_shell_limit) result (flag)
class(resonance_history_t), intent(in) :: res_hist
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: on_shell_limit
logical :: flag
integer :: i
flag = .true.
do i = 1, res_hist%n_resonances
flag = flag .and. res_hist%resonances(i)%is_on_shell (p, on_shell_limit)
end do
end function resonance_history_is_on_shell
@ %def resonance_history_is_on_shell
@
\subsection{OMega restriction strings}
One application of the resonance module is creating restriction
strings that can be fed into process definitions with the OMega
generator. Since OMega counts the incoming particles first, we have
to supply [[n_in]] as an offset.
<<Resonances: resonance info: TBP>>=
procedure :: as_omega_string => resonance_info_as_omega_string
<<Resonances: resonance history: TBP>>=
procedure :: as_omega_string => resonance_history_as_omega_string
<<Resonances: sub interfaces>>=
module function resonance_info_as_omega_string &
(res_info, n_in) result (string)
class(resonance_info_t), intent(in) :: res_info
integer, intent(in) :: n_in
type(string_t) :: string
end function resonance_info_as_omega_string
module function resonance_history_as_omega_string &
(res_hist, n_in) result (string)
class(resonance_history_t), intent(in) :: res_hist
integer, intent(in) :: n_in
type(string_t) :: string
end function resonance_history_as_omega_string
<<Resonances: procedures>>=
module function resonance_info_as_omega_string &
(res_info, n_in) result (string)
class(resonance_info_t), intent(in) :: res_info
integer, intent(in) :: n_in
type(string_t) :: string
integer :: i
string = ""
if (allocated (res_info%contributors%c)) then
do i = 1, size (res_info%contributors%c)
if (i > 1) string = string // "+"
string = string // str (res_info%contributors%c(i) + n_in)
end do
string = string // "~" // res_info%flavor%get_name ()
end if
end function resonance_info_as_omega_string
module function resonance_history_as_omega_string &
(res_hist, n_in) result (string)
class(resonance_history_t), intent(in) :: res_hist
integer, intent(in) :: n_in
type(string_t) :: string
integer :: i
string = ""
do i = 1, res_hist%n_resonances
if (i > 1) string = string // " && "
string = string // res_hist%resonances(i)%as_omega_string (n_in)
end do
end function resonance_history_as_omega_string
@ %def resonance_info_as_omega_string
@ %def resonance_history_as_omega_string
@
\subsection{Resonance history as tree}
If we want to organize the resonances and their decay products, it can be
useful to have them explicitly as a tree structure. We implement this in the
traditional event-record form with the resonances sorted by decreasing number
of contributors, and their decay products added as an extra array.
<<Resonances: public>>=
public :: resonance_tree_t
<<Resonances: types>>=
type :: resonance_branch_t
integer :: i = 0
type(flavor_t) :: flv
integer, dimension(:), allocatable :: r_child
integer, dimension(:), allocatable :: o_child
end type resonance_branch_t
type :: resonance_tree_t
private
integer :: n = 0
type(resonance_branch_t), dimension(:), allocatable :: branch
contains
<<Resonances: resonance tree: TBP>>
end type resonance_tree_t
@ %def resonance_branch_t resonance_tree_t
@
<<Resonances: resonance tree: TBP>>=
procedure :: write => resonance_tree_write
<<Resonances: sub interfaces>>=
module subroutine resonance_tree_write (tree, unit, indent)
class(resonance_tree_t), intent(in) :: tree
integer, intent(in), optional :: unit, indent
end subroutine resonance_tree_write
<<Resonances: procedures>>=
module subroutine resonance_tree_write (tree, unit, indent)
class(resonance_tree_t), intent(in) :: tree
integer, intent(in), optional :: unit, indent
integer :: u, b, c
u = given_output_unit (unit)
call write_indent (u, indent)
write (u, "(A)", advance="no") "Resonance tree:"
if (tree%n > 0) then
write (u, *)
do b = 1, tree%n
call write_indent (u, indent)
write (u, "(2x,'r',I0,':',1x)", advance="no") b
associate (branch => tree%branch(b))
call branch%flv%write (u)
write (u, "(1x,'=>')", advance="no")
if (allocated (branch%r_child)) then
do c = 1, size (branch%r_child)
write (u, "(1x,'r',I0)", advance="no") branch%r_child(c)
end do
end if
if (allocated (branch%o_child)) then
do c = 1, size (branch%o_child)
write (u, "(1x,I0)", advance="no") branch%o_child(c)
end do
end if
write (u, *)
end associate
end do
else
write (u, "(1x,A)") "[empty]"
end if
end subroutine resonance_tree_write
@ %def resonance_tree_write
@ Contents.
<<Resonances: resonance tree: TBP>>=
procedure :: get_n_resonances => resonance_tree_get_n_resonances
procedure :: get_flv => resonance_tree_get_flv
<<Resonances: sub interfaces>>=
module function resonance_tree_get_n_resonances (tree) result (n)
class(resonance_tree_t), intent(in) :: tree
integer :: n
end function resonance_tree_get_n_resonances
module function resonance_tree_get_flv (tree, i) result (flv)
class(resonance_tree_t), intent(in) :: tree
integer, intent(in) :: i
type(flavor_t) :: flv
end function resonance_tree_get_flv
<<Resonances: procedures>>=
module function resonance_tree_get_n_resonances (tree) result (n)
class(resonance_tree_t), intent(in) :: tree
integer :: n
n = tree%n
end function resonance_tree_get_n_resonances
module function resonance_tree_get_flv (tree, i) result (flv)
class(resonance_tree_t), intent(in) :: tree
integer, intent(in) :: i
type(flavor_t) :: flv
flv = tree%branch(i)%flv
end function resonance_tree_get_flv
@ %def resonance_tree_get_n_resonances
@ %def resonance_tree_get_flv
@ Return the shifted indices of the resonance children for branch [[i]]. For
a child which is itself a resonance, add [[offset_r]] to the index value. For
the others, add [[offset_o]]. Combine both in a single array.
<<Resonances: resonance tree: TBP>>=
procedure :: get_children => resonance_tree_get_children
<<Resonances: sub interfaces>>=
module function resonance_tree_get_children (tree, i, offset_r, offset_o) &
result (child)
class(resonance_tree_t), intent(in) :: tree
integer, intent(in) :: i, offset_r, offset_o
integer, dimension(:), allocatable :: child
end function resonance_tree_get_children
<<Resonances: procedures>>=
module function resonance_tree_get_children (tree, i, offset_r, offset_o) &
result (child)
class(resonance_tree_t), intent(in) :: tree
integer, intent(in) :: i, offset_r, offset_o
integer, dimension(:), allocatable :: child
integer :: nr, no
associate (branch => tree%branch(i))
nr = size (branch%r_child)
no = size (branch%o_child)
allocate (child (nr + no))
child(1:nr) = branch%r_child + offset_r
child(nr+1:nr+no) = branch%o_child + offset_o
end associate
end function resonance_tree_get_children
@ %def resonance_tree_get_children
@ Transform a resonance history into a resonance tree.
Algorithm:
\begin{enumerate}
\item
Determine a mapping of the resonance array, such that in the new array the
resonances are ordered by decreasing number of contributors.
\item
Copy the flavor entries to the mapped array.
\item
Scan all resonances and, for each one, find a resonance that is its parent.
Since the resonances are ordered, later matches overwrite earlier ones. The
last match is the correct one. Then scan again and, for each resonance,
collect the resonances that have it as parent. This is the set of child
resonances.
\item
Analogously, scan all outgoing particles that appear in any of the
contributors list. Determine their immediate parent as above, and set the
child outgoing parents for the resonances, as above.
\end{enumerate}
<<Resonances: resonance history: TBP>>=
procedure :: to_tree => resonance_history_to_tree
<<Resonances: sub interfaces>>=
module subroutine resonance_history_to_tree (res_hist, tree)
class(resonance_history_t), intent(in) :: res_hist
type(resonance_tree_t), intent(out) :: tree
end subroutine resonance_history_to_tree
<<Resonances: procedures>>=
module subroutine resonance_history_to_tree (res_hist, tree)
class(resonance_history_t), intent(in) :: res_hist
type(resonance_tree_t), intent(out) :: tree
integer :: nr
integer, dimension(:), allocatable :: r_branch, r_source
nr = res_hist%n_resonances
tree%n = nr
allocate (tree%branch (tree%n), r_branch (tree%n), r_source (tree%n))
if (tree%n > 0) then
call find_branch_ordering ()
call set_flavors ()
call set_child_resonances ()
call set_child_outgoing ()
end if
contains
subroutine find_branch_ordering ()
integer, dimension(:), allocatable :: nc_array
integer :: r, ir, nc
allocate (nc_array (tree%n))
nc_array(:) = res_hist%resonances%get_n_contributors ()
ir = 0
do nc = maxval (nc_array), minval (nc_array), -1
do r = 1, nr
if (nc_array(r) == nc) then
ir = ir + 1
r_branch(r) = ir
r_source(ir) = r
end if
end do
end do
end subroutine find_branch_ordering
subroutine set_flavors ()
integer :: r
do r = 1, nr
tree%branch(r_branch(r))%flv = res_hist%resonances(r)%flavor
end do
end subroutine set_flavors
subroutine set_child_resonances ()
integer, dimension(:), allocatable :: r_child, r_parent
integer :: r, ir, pr
allocate (r_parent (nr), source = 0)
SCAN_RES: do r = 1, nr
associate (this_res => res_hist%resonances(r))
SCAN_PARENT: do ir = 1, nr
pr = r_source(ir)
if (pr == r) cycle SCAN_PARENT
if (all (res_hist%resonances(pr)%contains &
(this_res%contributors%c))) then
r_parent (r) = pr
end if
end do SCAN_PARENT
end associate
end do SCAN_RES
allocate (r_child (nr), source = [(r, r = 1, nr)])
do r = 1, nr
ir = r_branch(r)
tree%branch(ir)%r_child = r_branch (pack (r_child, r_parent == r))
end do
end subroutine set_child_resonances
subroutine set_child_outgoing ()
integer, dimension(:), allocatable :: o_child, o_parent
integer :: o_max, r, o, ir
o_max = 0
do r = 1, nr
associate (this_res => res_hist%resonances(r))
o_max = max (o_max, maxval (this_res%contributors%c))
end associate
end do
allocate (o_parent (o_max), source=0)
SCAN_OUT: do o = 1, o_max
SCAN_PARENT: do ir = 1, nr
r = r_source(ir)
associate (this_res => res_hist%resonances(r))
if (this_res%contains (o)) o_parent(o) = r
end associate
end do SCAN_PARENT
end do SCAN_OUT
allocate (o_child (o_max), source = [(o, o = 1, o_max)])
do r = 1, nr
ir = r_branch(r)
tree%branch(ir)%o_child = pack (o_child, o_parent == r)
end do
end subroutine set_child_outgoing
end subroutine resonance_history_to_tree
@ %def resonance_history_to_tree
@
\subsection{Resonance history set}
This is an array of resonance histories. The elements are supposed to
be unique. That is, entering a new element is successful only if the
element does not already exist.
The current implementation uses a straightforward linear search for
comparison. If this should become an issue, we may change the
implementation to a hash table. To keep this freedom, the set should
be an opaque object. In fact, we expect to use it as a transient data
structure. Once the set is complete, we transform it into a
contiguous array.
<<Resonances: public>>=
public :: resonance_history_set_t
<<Resonances: types>>=
type :: index_array_t
integer, dimension(:), allocatable :: i
end type index_array_t
type :: resonance_history_set_t
private
logical :: complete = .false.
integer :: n_filter = 0
type(resonance_history_t), dimension(:), allocatable :: history
type(index_array_t), dimension(:), allocatable :: contains_this
type(resonance_tree_t), dimension(:), allocatable :: tree
integer :: last = 0
contains
<<Resonances: resonance history set: TBP>>
end type resonance_history_set_t
@ %def resonance_history_set_t
@ Display.
The tree-format version of the histories is displayed only upon request.
<<Resonances: resonance history set: TBP>>=
procedure :: write => resonance_history_set_write
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_write &
(res_set, unit, indent, show_trees)
class(resonance_history_set_t), intent(in) :: res_set
integer, intent(in), optional :: unit
integer, intent(in), optional :: indent
logical, intent(in), optional :: show_trees
end subroutine resonance_history_set_write
<<Resonances: procedures>>=
module subroutine resonance_history_set_write &
(res_set, unit, indent, show_trees)
class(resonance_history_set_t), intent(in) :: res_set
integer, intent(in), optional :: unit
integer, intent(in), optional :: indent
logical, intent(in), optional :: show_trees
logical :: s_trees
integer :: u, i, j, ind
u = given_output_unit (unit)
s_trees = .false.; if (present (show_trees)) s_trees = show_trees
ind = 0; if (present (indent)) ind = indent
call write_indent (u, indent)
write (u, "(A)", advance="no") "Resonance history set:"
if (res_set%complete) then
write (u, *)
else
write (u, "(1x,A)") "[incomplete]"
end if
do i = 1, res_set%last
write (u, "(1x,I0,1x)", advance="no") i
call res_set%history(i)%write (u, verbose=.false., indent=indent)
if (allocated (res_set%contains_this)) then
call write_indent (u, indent)
write (u, "(3x,A)", advance="no") "contained in ("
do j = 1, size (res_set%contains_this(i)%i)
if (j>1) write (u, "(',')", advance="no")
write (u, "(I0)", advance="no") res_set%contains_this(i)%i(j)
end do
write (u, "(A)") ")"
end if
if (s_trees .and. allocated (res_set%tree)) then
call res_set%tree(i)%write (u, ind + 1)
end if
end do
end subroutine resonance_history_set_write
@ %def resonance_history_set_write
@ Initialization. The default initial size is 16 elements, to be doubled in
size repeatedly as needed.
<<Resonances: parameters>>=
integer, parameter :: resonance_history_set_initial_size = 16
@ %def resonance_history_set_initial_size = 16
<<Resonances: resonance history set: TBP>>=
procedure :: init => resonance_history_set_init
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_init &
(res_set, n_filter, initial_size)
class(resonance_history_set_t), intent(out) :: res_set
integer, intent(in), optional :: n_filter
integer, intent(in), optional :: initial_size
end subroutine resonance_history_set_init
<<Resonances: procedures>>=
module subroutine resonance_history_set_init &
(res_set, n_filter, initial_size)
class(resonance_history_set_t), intent(out) :: res_set
integer, intent(in), optional :: n_filter
integer, intent(in), optional :: initial_size
if (present (n_filter)) res_set%n_filter = n_filter
if (present (initial_size)) then
allocate (res_set%history (initial_size))
else
allocate (res_set%history (resonance_history_set_initial_size))
end if
end subroutine resonance_history_set_init
@ %def resonance_history_set_init
@ Enter an entry: append to the array if it does not yet exist, expand
as needed. If a [[n_filter]] value has been provided, enter the
resonance only if it fulfils the requirement.
An empty resonance history is entered only if the [[trivial]] flag is set.
<<Resonances: resonance history set: TBP>>=
procedure :: enter => resonance_history_set_enter
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_enter &
(res_set, res_history, trivial)
class(resonance_history_set_t), intent(inout) :: res_set
type(resonance_history_t), intent(in) :: res_history
logical, intent(in), optional :: trivial
end subroutine resonance_history_set_enter
<<Resonances: procedures>>=
module subroutine resonance_history_set_enter &
(res_set, res_history, trivial)
class(resonance_history_set_t), intent(inout) :: res_set
type(resonance_history_t), intent(in) :: res_history
logical, intent(in), optional :: trivial
integer :: i, new
if (res_history%n_resonances == 0) then
if (present (trivial)) then
if (.not. trivial) return
else
return
end if
end if
if (res_set%n_filter > 0) then
if (.not. res_history%only_has_n_contributors (res_set%n_filter)) return
end if
do i = 1, res_set%last
if (res_set%history(i) == res_history) return
end do
new = res_set%last + 1
if (new > size (res_set%history)) call res_set%expand ()
res_set%history(new) = res_history
res_set%last = new
end subroutine resonance_history_set_enter
@ %def resonance_history_set_enter
@ Freeze the resonance history set: determine the array that determines
in which other resonance histories a particular history is contained.
This can only be done once, and once this is done, no further histories can be
entered.
<<Resonances: resonance history set: TBP>>=
procedure :: freeze => resonance_history_set_freeze
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_freeze (res_set)
class(resonance_history_set_t), intent(inout) :: res_set
end subroutine resonance_history_set_freeze
<<Resonances: procedures>>=
module subroutine resonance_history_set_freeze (res_set)
class(resonance_history_set_t), intent(inout) :: res_set
integer :: i, n, c
logical, dimension(:), allocatable :: contains_this
integer, dimension(:), allocatable :: index_array
n = res_set%last
allocate (contains_this (n))
allocate (index_array (n), source = [(i, i=1, n)])
allocate (res_set%contains_this (n))
do i = 1, n
contains_this = resonance_history_contains &
(res_set%history(1:n), res_set%history(i))
c = count (contains_this)
allocate (res_set%contains_this(i)%i (c))
res_set%contains_this(i)%i = pack (index_array, contains_this)
end do
allocate (res_set%tree (n))
do i = 1, n
call res_set%history(i)%to_tree (res_set%tree(i))
end do
res_set%complete = .true.
end subroutine resonance_history_set_freeze
@ %def resonance_history_set_freeze
@ Determine the histories (in form of their indices in the array) that can be
considered on-shell, given a set of momenta and a maximum distance. The
distance from the resonance is measured in multiples of the resonance width.
Note that the momentum array must only contain the outgoing particles.
If a particular history is on-shell, but there is another history which
contains this and also is on-shell, only the latter is retained.
<<Resonances: resonance history set: TBP>>=
procedure :: determine_on_shell_histories &
=> resonance_history_set_determine_on_shell_histories
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_determine_on_shell_histories &
(res_set, p, on_shell_limit, index_array)
class(resonance_history_set_t), intent(in) :: res_set
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: on_shell_limit
integer, dimension(:), allocatable, intent(out) :: index_array
end subroutine resonance_history_set_determine_on_shell_histories
<<Resonances: procedures>>=
module subroutine resonance_history_set_determine_on_shell_histories &
(res_set, p, on_shell_limit, index_array)
class(resonance_history_set_t), intent(in) :: res_set
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: on_shell_limit
integer, dimension(:), allocatable, intent(out) :: index_array
integer :: n, i
integer, dimension(:), allocatable :: i_array
if (res_set%complete) then
n = res_set%last
allocate (i_array (n), source=0)
do i = 1, n
if (res_set%history(i)%is_on_shell (p, on_shell_limit)) i_array(i) = i
end do
do i = 1, n
if (any (i_array(res_set%contains_this(i)%i) /= 0)) then
i_array(i) = 0
end if
end do
allocate (index_array (count (i_array /= 0)))
index_array(:) = pack (i_array, i_array /= 0)
end if
end subroutine resonance_history_set_determine_on_shell_histories
@ %def resonance_history_set_determine_on_shell_histories
@ For the selected history, compute the Gaussian turnoff factor.
The turnoff parameter is [[gw]].
<<Resonances: resonance history set: TBP>>=
procedure :: evaluate_gaussian => resonance_history_set_evaluate_gaussian
<<Resonances: sub interfaces>>=
module function resonance_history_set_evaluate_gaussian &
(res_set, p, gw, i) result (factor)
class(resonance_history_set_t), intent(in) :: res_set
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: gw
integer, intent(in) :: i
real(default) :: factor
end function resonance_history_set_evaluate_gaussian
<<Resonances: procedures>>=
module function resonance_history_set_evaluate_gaussian &
(res_set, p, gw, i) result (factor)
class(resonance_history_set_t), intent(in) :: res_set
type(vector4_t), dimension(:), intent(in) :: p
real(default), intent(in) :: gw
integer, intent(in) :: i
real(default) :: factor
factor = res_set%history(i)%evaluate_gaussian (p, gw)
end function resonance_history_set_evaluate_gaussian
@ %def resonance_history_set_evaluate_gaussian
@ Return the number of histories. This is zero if there are none, or
if [[freeze]] has not been called yet.
<<Resonances: resonance history set: TBP>>=
procedure :: get_n_history => resonance_history_set_get_n_history
<<Resonances: sub interfaces>>=
module function resonance_history_set_get_n_history (res_set) result (n)
class(resonance_history_set_t), intent(in) :: res_set
integer :: n
end function resonance_history_set_get_n_history
<<Resonances: procedures>>=
module function resonance_history_set_get_n_history (res_set) result (n)
class(resonance_history_set_t), intent(in) :: res_set
integer :: n
if (res_set%complete) then
n = res_set%last
else
n = 0
end if
end function resonance_history_set_get_n_history
@ %def resonance_history_set_get_n_history
@ Return a single history.
<<Resonances: resonance history set: TBP>>=
procedure :: get_history => resonance_history_set_get_history
<<Resonances: sub interfaces>>=
module function resonance_history_set_get_history &
(res_set, i) result (res_history)
class(resonance_history_set_t), intent(in) :: res_set
integer, intent(in) :: i
type(resonance_history_t) :: res_history
end function resonance_history_set_get_history
<<Resonances: procedures>>=
module function resonance_history_set_get_history &
(res_set, i) result (res_history)
class(resonance_history_set_t), intent(in) :: res_set
integer, intent(in) :: i
type(resonance_history_t) :: res_history
if (res_set%complete .and. i <= res_set%last) then
res_history = res_set%history(i)
end if
end function resonance_history_set_get_history
@ %def resonance_history_set_get_history
@ Conversion to a plain array, sized correctly.
<<Resonances: resonance history set: TBP>>=
procedure :: to_array => resonance_history_set_to_array
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_to_array (res_set, res_history)
class(resonance_history_set_t), intent(in) :: res_set
type(resonance_history_t), dimension(:), allocatable, intent(out) :: &
res_history
end subroutine resonance_history_set_to_array
<<Resonances: procedures>>=
module subroutine resonance_history_set_to_array (res_set, res_history)
class(resonance_history_set_t), intent(in) :: res_set
type(resonance_history_t), dimension(:), allocatable, intent(out) :: &
res_history
if (res_set%complete) then
allocate (res_history (res_set%last))
res_history(:) = res_set%history(1:res_set%last)
end if
end subroutine resonance_history_set_to_array
@ %def resonance_history_set_to_array
@ Return a selected history in tree form.
<<Resonances: resonance history set: TBP>>=
procedure :: get_tree => resonance_history_set_get_tree
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_get_tree (res_set, i, res_tree)
class(resonance_history_set_t), intent(in) :: res_set
integer, intent(in) :: i
type(resonance_tree_t), intent(out) :: res_tree
end subroutine resonance_history_set_get_tree
<<Resonances: procedures>>=
module subroutine resonance_history_set_get_tree (res_set, i, res_tree)
class(resonance_history_set_t), intent(in) :: res_set
integer, intent(in) :: i
type(resonance_tree_t), intent(out) :: res_tree
if (res_set%complete) then
res_tree = res_set%tree(i)
end if
end subroutine resonance_history_set_get_tree
@ %def resonance_history_set_to_array
@ Expand: double the size of the array. We do not need this in the API.
<<Resonances: resonance history set: TBP>>=
procedure, private :: expand => resonance_history_set_expand
<<Resonances: sub interfaces>>=
module subroutine resonance_history_set_expand (res_set)
class(resonance_history_set_t), intent(inout) :: res_set
end subroutine resonance_history_set_expand
<<Resonances: procedures>>=
module subroutine resonance_history_set_expand (res_set)
class(resonance_history_set_t), intent(inout) :: res_set
type(resonance_history_t), dimension(:), allocatable :: history_new
integer :: s
s = size (res_set%history)
allocate (history_new (2 * s))
history_new(1:s) = res_set%history(1:s)
call move_alloc (history_new, res_set%history)
end subroutine resonance_history_set_expand
@ %def resonance_history_set_expand
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[resonances_ut.f90]]>>=
<<File header>>
module resonances_ut
use unit_tests
use resonances_uti
<<Standard module head>>
<<Resonances: public test>>
contains
<<Resonances: test driver>>
end module resonances_ut
@ %def resonances_ut
@
<<[[resonances_uti.f90]]>>=
<<File header>>
module resonances_uti
<<Use kinds>>
<<Use strings>>
use format_defs, only: FMF_12
use lorentz, only: vector4_t, vector4_at_rest
use model_data, only: model_data_t
use flavors, only: flavor_t
use resonances, only: resonance_history_t
use resonances
<<Standard module head>>
<<Resonances: test declarations>>
contains
<<Resonances: tests>>
end module resonances_uti
@ %def resonances_ut
@ API: driver for the unit tests below.
<<Resonances: public test>>=
public :: resonances_test
<<Resonances: test driver>>=
subroutine resonances_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<Resonances: execute tests>>
end subroutine resonances_test
@ %def resonances_test
@ Basic operations on a resonance history object.
<<Resonances: execute tests>>=
call test (resonances_1, "resonances_1", &
"check resonance history setup", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_1
<<Resonances: tests>>=
subroutine resonances_1 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(model_data_t), target :: model
write (u, "(A)") "* Test output: resonances_1"
write (u, "(A)") "* Purpose: test resonance history setup"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Empty resonance history"
write (u, "(A)")
call res_history%write (u)
write (u, "(A)")
write (u, "(A)") "* Add resonance"
write (u, "(A)")
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
write (u, "(A)")
write (u, "(A)") "* Add another resonance"
write (u, "(A)")
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
write (u, "(A)")
write (u, "(A)") "* Remove resonance"
write (u, "(A)")
call res_history%remove_resonance (1)
call res_history%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_1"
end subroutine resonances_1
@ %def resonances_1
@ Basic operations on a resonance history object.
<<Resonances: execute tests>>=
call test (resonances_2, "resonances_2", &
"check O'Mega restriction strings", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_2
<<Resonances: tests>>=
subroutine resonances_2 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(model_data_t), target :: model
type(string_t) :: restrictions
write (u, "(A)") "* Test output: resonances_2"
write (u, "(A)") "* Purpose: test OMega restrictions strings &
&for resonance history"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Empty resonance history"
write (u, "(A)")
restrictions = res_history%as_omega_string (2)
write (u, "(A,A,A)") "restrictions = '", char (restrictions), "'"
write (u, "(A)")
write (u, "(A)") "* Add resonance"
write (u, "(A)")
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
restrictions = res_history%as_omega_string (2)
write (u, "(A,A,A)") "restrictions = '", char (restrictions), "'"
write (u, "(A)")
write (u, "(A)") "* Add another resonance"
write (u, "(A)")
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
restrictions = res_history%as_omega_string (2)
write (u, "(A,A,A)") "restrictions = '", char (restrictions), "'"
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_2"
end subroutine resonances_2
@ %def resonances_2
@ Basic operations on a resonance history set.
<<Resonances: execute tests>>=
call test (resonances_3, "resonances_3", &
"check resonance history set", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_3
<<Resonances: tests>>=
subroutine resonances_3 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(resonance_history_t), dimension(:), allocatable :: res_histories
type(resonance_history_set_t) :: res_set
type(model_data_t), target :: model
integer :: i
write (u, "(A)") "* Test output: resonances_3"
write (u, "(A)") "* Purpose: test resonance history set"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Initialize resonance history set"
write (u, "(A)")
call res_set%init (initial_size = 2)
write (u, "(A)") "* Add resonance histories, one at a time"
write (u, "(A)")
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_info%init (7, 25, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
call res_set%freeze ()
write (u, "(A)")
write (u, "(A)") "* Result"
write (u, "(A)")
call res_set%write (u)
write (u, "(A)")
write (u, "(A)") "* Queries"
write (u, "(A)")
write (u, "(A,1x,I0)") "n_history =", res_set%get_n_history ()
write (u, "(A)")
write (u, "(A)") "History #2:"
res_history = res_set%get_history (2)
call res_history%write (u, indent=1)
call res_history%clear ()
write (u, "(A)")
write (u, "(A)") "* Result in array form"
call res_set%to_array (res_histories)
do i = 1, size (res_histories)
write (u, *)
call res_histories(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Re-initialize resonance history set with filter n=2"
write (u, "(A)")
call res_set%init (n_filter = 2)
write (u, "(A)") "* Add resonance histories, one at a time"
write (u, "(A)")
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
write (u, *)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
call res_set%enter (res_history)
call res_history%clear ()
call res_set%freeze ()
write (u, "(A)")
write (u, "(A)") "* Result"
write (u, "(A)")
call res_set%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_3"
end subroutine resonances_3
@ %def resonances_3
@ Probe momenta for resonance histories
<<Resonances: execute tests>>=
call test (resonances_4, "resonances_4", &
"resonance history: distance evaluation", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_4
<<Resonances: tests>>=
subroutine resonances_4 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(model_data_t), target :: model
type(flavor_t) :: fw, fz
real(default) :: mw, mz, ww, wz
type(vector4_t), dimension(3) :: p
real(default), dimension(2) :: dist
real(default) :: gw, factor
integer :: i
write (u, "(A)") "* Test output: resonances_4"
write (u, "(A)") "* Purpose: test resonance history evaluation"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* W and Z parameters"
write (u, "(A)")
call fw%init (24, model)
call fz%init (23, model)
mw = fw%get_mass ()
ww = fw%get_width ()
mz = fz%get_mass ()
wz = fz%get_width ()
write (u, "(A,1x," // FMF_12 // ")") "mW =", mw
write (u, "(A,1x," // FMF_12 // ")") "wW =", ww
write (u, "(A,1x," // FMF_12 // ")") "mZ =", mz
write (u, "(A,1x," // FMF_12 // ")") "wZ =", wz
write (u, "(A)")
write (u, "(A)") "* Gaussian width parameter"
write (u, "(A)")
gw = 2
write (u, "(A,1x," // FMF_12 // ")") "gw =", gw
write (u, "(A)")
write (u, "(A)") "* Setup resonance histories"
write (u, "(A)")
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
write (u, "(A)")
write (u, "(A)") "* Setup zero momenta"
write (u, "(A)")
do i = 1, 3
call p(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Evaluate distances from resonances"
write (u, "(A)")
call res_history%evaluate_distances (p, dist)
write (u, "(A,1x," // FMF_12 // ")") "distance (W) =", dist(1)
write (u, "(A,1x," // FMF_12 // ")") "m/w (W) =", mw / ww
write (u, "(A,1x," // FMF_12 // ")") "distance (Z) =", dist(2)
write (u, "(A,1x," // FMF_12 // ")") "m/w (Z) =", mz / wz
write (u, "(A)")
write (u, "(A)") "* Evaluate Gaussian turnoff factor"
write (u, "(A)")
factor = res_history%evaluate_gaussian (p, gw)
write (u, "(A,1x," // FMF_12 // ")") "gaussian fac =", factor
write (u, "(A)")
write (u, "(A)") "* Set momenta on W peak"
write (u, "(A)")
p(1) = vector4_at_rest (mw/2)
p(2) = vector4_at_rest (mw/2)
do i = 1, 3
call p(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Evaluate distances from resonances"
write (u, "(A)")
call res_history%evaluate_distances (p, dist)
write (u, "(A,1x," // FMF_12 // ")") "distance (W) =", dist(1)
write (u, "(A,1x," // FMF_12 // ")") "distance (Z) =", dist(2)
write (u, "(A,1x," // FMF_12 // ")") "expected =", &
abs (mz**2 - mw**2) / (mz*wz)
write (u, "(A)")
write (u, "(A)") "* Evaluate Gaussian turnoff factor"
write (u, "(A)")
factor = res_history%evaluate_gaussian (p, gw)
write (u, "(A,1x," // FMF_12 // ")") "gaussian fac =", factor
write (u, "(A,1x," // FMF_12 // ")") "expected =", &
exp (- (abs (mz**2 - mw**2) / (mz*wz))**2 / (gw * wz)**2)
write (u, "(A)")
write (u, "(A)") "* Set momenta on both peaks"
write (u, "(A)")
p(3) = vector4_at_rest (mz - mw)
do i = 1, 3
call p(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Evaluate distances from resonances"
write (u, "(A)")
call res_history%evaluate_distances (p, dist)
write (u, "(A,1x," // FMF_12 // ")") "distance (W) =", dist(1)
write (u, "(A,1x," // FMF_12 // ")") "distance (Z) =", dist(2)
write (u, "(A)")
write (u, "(A)") "* Evaluate Gaussian turnoff factor"
write (u, "(A)")
factor = res_history%evaluate_gaussian (p, gw)
write (u, "(A,1x," // FMF_12 // ")") "gaussian fac =", factor
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_4"
end subroutine resonances_4
@ %def resonances_4
@ Probe on-shell test for resonance histories
<<Resonances: execute tests>>=
call test (resonances_5, "resonances_5", &
"resonance history: on-shell test", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_5
<<Resonances: tests>>=
subroutine resonances_5 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(resonance_history_set_t) :: res_set
type(model_data_t), target :: model
type(flavor_t) :: fw, fz
real(default) :: mw, mz, ww, wz
real(default) :: on_shell_limit
integer, dimension(:), allocatable :: on_shell
type(vector4_t), dimension(4) :: p
write (u, "(A)") "* Test output: resonances_5"
write (u, "(A)") "* Purpose: resonance history on-shell test"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* W and Z parameters"
write (u, "(A)")
call fw%init (24, model)
call fz%init (23, model)
mw = fw%get_mass ()
ww = fw%get_width ()
mz = fz%get_mass ()
wz = fz%get_width ()
write (u, "(A,1x," // FMF_12 // ")") "mW =", mw
write (u, "(A,1x," // FMF_12 // ")") "wW =", ww
write (u, "(A,1x," // FMF_12 // ")") "mZ =", mz
write (u, "(A,1x," // FMF_12 // ")") "wZ =", wz
write (u, "(A)")
write (u, "(A)") "* On-shell parameter: distance as multiple of width"
write (u, "(A)")
on_shell_limit = 3
write (u, "(A,1x," // FMF_12 // ")") "on-shell limit =", on_shell_limit
write (u, "(A)")
write (u, "(A)") "* Setup resonance history set"
write (u, "(A)")
call res_set%init ()
call res_info%init (3, -24, model, 6)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (12, 24, model, 6)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (15, 23, model, 6)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (3, -24, model, 6)
call res_history%add_resonance (res_info)
call res_info%init (15, 23, model, 6)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (12, 24, model, 6)
call res_history%add_resonance (res_info)
call res_info%init (15, 23, model, 6)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_set%freeze ()
call res_set%write (u)
write (u, "(A)")
write (u, "(A)") "* Setup zero momenta"
write (u, "(A)")
call write_momenta (p)
call res_set%determine_on_shell_histories (p, on_shell_limit, on_shell)
call write_on_shell_histories (on_shell)
write (u, "(A)")
write (u, "(A)") "* Setup momenta near W- resonance (2 widths off)"
write (u, "(A)")
p(1) = vector4_at_rest (82.5_default)
call write_momenta (p)
call res_set%determine_on_shell_histories (p, on_shell_limit, on_shell)
call write_on_shell_histories (on_shell)
write (u, "(A)")
write (u, "(A)") "* Setup momenta near W- resonance (4 widths off)"
write (u, "(A)")
p(1) = vector4_at_rest (84.5_default)
call write_momenta (p)
call res_set%determine_on_shell_histories (p, on_shell_limit, on_shell)
call write_on_shell_histories (on_shell)
write (u, "(A)")
write (u, "(A)") "* Setup momenta near Z resonance"
write (u, "(A)")
p(1) = vector4_at_rest (45._default)
p(3) = vector4_at_rest (45._default)
call write_momenta (p)
call res_set%determine_on_shell_histories (p, on_shell_limit, on_shell)
call write_on_shell_histories (on_shell)
write (u, "(A)")
write (u, "(A)") "* Setup momenta near W- and W+ resonances"
write (u, "(A)")
p(1) = vector4_at_rest (40._default)
p(2) = vector4_at_rest (40._default)
p(3) = vector4_at_rest (40._default)
p(4) = vector4_at_rest (40._default)
call write_momenta (p)
call res_set%determine_on_shell_histories (p, on_shell_limit, on_shell)
call write_on_shell_histories (on_shell)
write (u, "(A)")
write (u, "(A)") "* Setup momenta near W- and Z resonances, &
&shadowing single resonances"
write (u, "(A)")
p(1) = vector4_at_rest (40._default)
p(2) = vector4_at_rest (40._default)
p(3) = vector4_at_rest (10._default)
p(4) = vector4_at_rest ( 0._default)
call write_momenta (p)
call res_set%determine_on_shell_histories (p, on_shell_limit, on_shell)
call write_on_shell_histories (on_shell)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_5"
contains
subroutine write_momenta (p)
type(vector4_t), dimension(:), intent(in) :: p
integer :: i
do i = 1, size (p)
call p(i)%write (u)
end do
end subroutine write_momenta
subroutine write_on_shell_histories (on_shell)
integer, dimension(:), intent(in) :: on_shell
integer :: i
write (u, *)
write (u, "(A)", advance="no") "on-shell = ("
do i = 1, size (on_shell)
if (i > 1) write (u, "(',')", advance="no")
write (u, "(I0)", advance="no") on_shell(i)
end do
write (u, "(')')")
end subroutine write_on_shell_histories
end subroutine resonances_5
@ %def resonances_5
@ Organize the resonance history as a tree structure.
<<Resonances: execute tests>>=
call test (resonances_6, "resonances_6", &
"check resonance history setup", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_6
<<Resonances: tests>>=
subroutine resonances_6 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(resonance_tree_t) :: res_tree
type(model_data_t), target :: model
write (u, "(A)") "* Test output: resonances_6"
write (u, "(A)") "* Purpose: retrieve resonance histories as trees"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Empty resonance history"
write (u, "(A)")
call res_history%write (u)
write (u, "(A)")
call res_history%to_tree (res_tree)
call res_tree%write (u)
write (u, "(A)")
write (u, "(A)") "* Single resonance"
write (u, "(A)")
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
write (u, "(A)")
call res_history%to_tree (res_tree)
call res_tree%write (u)
write (u, "(A)")
write (u, "(A)") "* Nested resonances"
write (u, "(A)")
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_history%write (u)
write (u, "(A)")
call res_history%to_tree (res_tree)
call res_tree%write (u)
write (u, "(A)")
write (u, "(A)") "* Disjunct resonances"
write (u, "(A)")
call res_history%clear ()
call res_info%init (5, 24, model, 7)
call res_history%add_resonance (res_info)
call res_info%init (7, 6, model, 7)
call res_history%add_resonance (res_info)
call res_info%init (80, -24, model, 7)
call res_history%add_resonance (res_info)
call res_info%init (112, -6, model, 7)
call res_history%add_resonance (res_info)
call res_history%write (u)
write (u, "(A)")
call res_history%to_tree (res_tree)
call res_tree%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_6"
end subroutine resonances_6
@ %def resonances_6
@ Basic operations on a resonance history set.
<<Resonances: execute tests>>=
call test (resonances_7, "resonances_7", &
"display tree format of history set elements", &
u, results)
<<Resonances: test declarations>>=
public :: resonances_7
<<Resonances: tests>>=
subroutine resonances_7 (u)
integer, intent(in) :: u
type(resonance_info_t) :: res_info
type(resonance_history_t) :: res_history
type(resonance_tree_t) :: res_tree
type(resonance_history_set_t) :: res_set
type(model_data_t), target :: model
type(flavor_t) :: flv
write (u, "(A)") "* Test output: resonances_7"
write (u, "(A)") "* Purpose: test tree format"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Initialize, fill and freeze resonance history set"
write (u, "(A)")
call res_set%init (initial_size = 2)
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_history%clear ()
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (7, 23, model, 5)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_info%init (3, -24, model, 5)
call res_history%add_resonance (res_info)
call res_info%init (7, 25, model, 5)
call res_history%add_resonance (res_info)
call res_set%enter (res_history)
call res_history%clear ()
call res_set%freeze ()
call res_set%write (u, show_trees = .true.)
write (u, "(A)")
write (u, "(A)") "* Extract tree #1"
write (u, "(A)")
call res_set%get_tree (1, res_tree)
call res_tree%write (u)
write (u, *)
write (u, "(1x,A,1x,I0)") "n_resonances =", res_tree%get_n_resonances ()
write (u, *)
write (u, "(1x,A,1x)", advance="no") "flv(r1) ="
flv = res_tree%get_flv (1)
call flv%write (u)
write (u, *)
write (u, "(1x,A,1x)", advance="no") "flv(r2) ="
flv = res_tree%get_flv (2)
call flv%write (u)
write (u, *)
write (u, *)
write (u, "(1x,A)") "[offset = 2, 4]"
write (u, "(1x,A,9(1x,I0))") "children(r1) =", &
res_tree%get_children(1, 2, 4)
write (u, "(1x,A,9(1x,I0))") "children(r2) =", &
res_tree%get_children(2, 2, 4)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: resonances_7"
end subroutine resonances_7
@ %def resonances_7
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\clearpage
\section{Mappings}
Mappings are objects that encode the transformation of the interval
$(0,1)$ to a physical variable $m^2$ or $\cos\theta$ (and back), as it
is used in the phase space parameterization. The mapping objects
contain fixed parameters, the associated methods implement the mapping
and inverse mapping operations, including the computation of the
Jacobian (phase space factor).
<<[[mappings.f90]]>>=
<<File header>>
module mappings
<<Use kinds>>
use kinds, only: TC
<<Use strings>>
use model_data
use flavors
<<Standard module head>>
<<Mappings: public>>
<<Mappings: parameters>>
<<Mappings: types>>
<<Mappings: interfaces>>
interface
<<Mappings: sub interfaces>>
end interface
end module mappings
@ %def mappings
@
<<[[mappings_sub.f90]]>>=
<<File header>>
submodule (mappings) mappings_s
use io_units
use constants, only: pi
use format_defs, only: FMT_19
use diagnostics
use md5
implicit none
contains
<<Mappings: procedures>>
end submodule mappings_s
@ %def mappings_s
@
\subsection{Default parameters}
This type holds the default parameters, needed for setting the scale
in cases where no mass parameter is available. The contents are public.
<<Mappings: public>>=
public :: mapping_defaults_t
<<Mappings: types>>=
type :: mapping_defaults_t
real(default) :: energy_scale = 10
real(default) :: invariant_mass_scale = 10
real(default) :: momentum_transfer_scale = 10
logical :: step_mapping = .true.
logical :: step_mapping_exp = .true.
logical :: enable_s_mapping = .false.
contains
<<Mappings: mapping defaults: TBP>>
end type mapping_defaults_t
@ %def mapping_defaults_t
@ Output.
<<Mappings: mapping defaults: TBP>>=
procedure :: write => mapping_defaults_write
<<Mappings: sub interfaces>>=
module subroutine mapping_defaults_write (object, unit)
class(mapping_defaults_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine mapping_defaults_write
<<Mappings: procedures>>=
module subroutine mapping_defaults_write (object, unit)
class(mapping_defaults_t), intent(in) :: object
integer, intent(in), optional :: unit
integer :: u
u = given_output_unit (unit)
write (u, "(3x,A," // FMT_19 // ")") "energy scale = ", &
object%energy_scale
write (u, "(3x,A," // FMT_19 // ")") "mass scale = ", &
object%invariant_mass_scale
write (u, "(3x,A," // FMT_19 // ")") "q scale = ", &
object%momentum_transfer_scale
write (u, "(3x,A,L1)") "step mapping = ", &
object%step_mapping
write (u, "(3x,A,L1)") "step exp. mode = ", &
object%step_mapping_exp
write (u, "(3x,A,L1)") "allow s mapping = ", &
object%enable_s_mapping
end subroutine mapping_defaults_write
@ %def mapping_defaults_write
@
<<Mappings: public>>=
public :: mapping_defaults_md5sum
<<Mappings: sub interfaces>>=
module function mapping_defaults_md5sum &
(mapping_defaults) result (md5sum_map)
character(32) :: md5sum_map
type(mapping_defaults_t), intent(in) :: mapping_defaults
end function mapping_defaults_md5sum
<<Mappings: procedures>>=
module function mapping_defaults_md5sum &
(mapping_defaults) result (md5sum_map)
character(32) :: md5sum_map
type(mapping_defaults_t), intent(in) :: mapping_defaults
integer :: u
u = free_unit ()
open (u, status = "scratch")
write (u, *) mapping_defaults%energy_scale
write (u, *) mapping_defaults%invariant_mass_scale
write (u, *) mapping_defaults%momentum_transfer_scale
write (u, *) mapping_defaults%step_mapping
write (u, *) mapping_defaults%step_mapping_exp
write (u, *) mapping_defaults%enable_s_mapping
rewind (u)
md5sum_map = md5sum (u)
close (u)
end function mapping_defaults_md5sum
@ %def mapping_defaults_md5sum
@
\subsection{The Mapping type}
Each mapping has a type (e.g., s-channel, infrared), a binary code
(redundant, but useful for debugging), and a reference particle. The
flavor code of this particle is stored for bookkeeping reasons, what
matters are the mass and width of this particle. Furthermore,
depending on the type, various mapping parameters can be set and used.
The parameters [[a1]] to [[a3]] (for $m^2$ mappings) and [[b1]] to
[[b3]] (for $\cos\theta$ mappings) are values that are stored once to
speed up the calculation, if [[variable_limits]] is false. The exact
meaning of these parameters depends on the mapping type. The limits
are fixed if there is a fixed c.m. energy.
<<Mappings: public>>=
public :: mapping_t
<<Mappings: types>>=
type :: mapping_t
private
integer :: type = NO_MAPPING
integer(TC) :: bincode
type(flavor_t) :: flv
real(default) :: mass = 0
real(default) :: width = 0
logical :: a_unknown = .true.
real(default) :: a1 = 0
real(default) :: a2 = 0
real(default) :: a3 = 0
logical :: b_unknown = .true.
real(default) :: b1 = 0
real(default) :: b2 = 0
real(default) :: b3 = 0
logical :: variable_limits = .true.
contains
<<Mappings: mapping: TBP>>
end type mapping_t
@ %def mapping_t
@ The valid mapping types. The extra type [[STEP_MAPPING]] is used
only internally.
<<Mappings: parameters>>=
<<Mapping modes>>
@
\subsection{Screen output}
Do not write empty mappings.
<<Mappings: mapping: TBP>>=
procedure :: write => mapping_write
<<Mappings: sub interfaces>>=
module subroutine mapping_write (map, unit, verbose)
class(mapping_t), intent(in) :: map
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine mapping_write
<<Mappings: procedures>>=
module subroutine mapping_write (map, unit, verbose)
class(mapping_t), intent(in) :: map
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u
character(len=9) :: str
u = given_output_unit (unit); if (u < 0) return
select case(map%type)
case(S_CHANNEL); str = "s_channel"
case(COLLINEAR); str = "collinear"
case(INFRARED); str = "infrared "
case(RADIATION); str = "radiation"
case(T_CHANNEL); str = "t_channel"
case(U_CHANNEL); str = "u_channel"
case(STEP_MAPPING_E); str = "step_exp"
case(STEP_MAPPING_H); str = "step_hyp"
case(ON_SHELL); str = "on_shell"
case default; str = "????????"
end select
if (map%type /= NO_MAPPING) then
write (u, '(1x,A,I4,A)') &
"Branch #", map%bincode, ": " // &
"Mapping (" // str // ") for particle " // &
'"' // char (map%flv%get_name ()) // '"'
if (present (verbose)) then
if (verbose) then
select case (map%type)
case (S_CHANNEL, RADIATION, STEP_MAPPING_E, STEP_MAPPING_H)
write (u, "(1x,A,3(" // FMT_19 // "))") &
" m/w = ", map%mass, map%width
case default
write (u, "(1x,A,3(" // FMT_19 // "))") &
" m = ", map%mass
end select
select case (map%type)
case (S_CHANNEL, T_CHANNEL, U_CHANNEL, &
STEP_MAPPING_E, STEP_MAPPING_H, &
COLLINEAR, INFRARED, RADIATION)
write (u, "(1x,A,3(" // FMT_19 // "))") &
" a1/2/3 = ", map%a1, map%a2, map%a3
end select
select case (map%type)
case (T_CHANNEL, U_CHANNEL, COLLINEAR)
write (u, "(1x,A,3(" // FMT_19 // "))") &
" b1/2/3 = ", map%b1, map%b2, map%b3
end select
end if
end if
end if
end subroutine mapping_write
@ %def mapping_write
@
\subsection{Define a mapping}
The initialization routine sets the mapping type and the particle
(binary code and flavor code) for which the mapping applies (e.g., a
$Z$ resonance in branch \#3).
<<Mappings: mapping: TBP>>=
procedure :: init => mapping_init
<<Mappings: sub interfaces>>=
module subroutine mapping_init (mapping, bincode, type, f, model)
class(mapping_t), intent(inout) :: mapping
integer(TC), intent(in) :: bincode
type(string_t), intent(in) :: type
integer, intent(in), optional :: f
class(model_data_t), intent(in), optional, target :: model
end subroutine mapping_init
<<Mappings: procedures>>=
module subroutine mapping_init (mapping, bincode, type, f, model)
class(mapping_t), intent(inout) :: mapping
integer(TC), intent(in) :: bincode
type(string_t), intent(in) :: type
integer, intent(in), optional :: f
class(model_data_t), intent(in), optional, target :: model
mapping%bincode = bincode
select case (char (type))
case ("s_channel"); mapping%type = S_CHANNEL
case ("collinear"); mapping%type = COLLINEAR
case ("infrared"); mapping%type = INFRARED
case ("radiation"); mapping%type = RADIATION
case ("t_channel"); mapping%type = T_CHANNEL
case ("u_channel"); mapping%type = U_CHANNEL
case ("step_exp"); mapping%type = STEP_MAPPING_E
case ("step_hyp"); mapping%type = STEP_MAPPING_H
case ("on_shell"); mapping%type = ON_SHELL
case default
call msg_bug ("Mappings: encountered undefined mapping key '" &
// char (type) // "'")
end select
if (present (f) .and. present (model)) call mapping%flv%init (f, model)
end subroutine mapping_init
@ %def mapping_init
@ This sets the actual mass and width, using a parameter set. Since
the auxiliary parameters will only be determined when the mapping is
first called, they are marked as unknown.
<<Mappings: mapping: TBP>>=
procedure :: set_parameters => mapping_set_parameters
<<Mappings: sub interfaces>>=
module subroutine mapping_set_parameters &
(map, mapping_defaults, variable_limits)
class(mapping_t), intent(inout) :: map
type(mapping_defaults_t), intent(in) :: mapping_defaults
logical, intent(in) :: variable_limits
end subroutine mapping_set_parameters
<<Mappings: procedures>>=
module subroutine mapping_set_parameters &
(map, mapping_defaults, variable_limits)
class(mapping_t), intent(inout) :: map
type(mapping_defaults_t), intent(in) :: mapping_defaults
logical, intent(in) :: variable_limits
if (map%type /= NO_MAPPING) then
map%mass = map%flv%get_mass ()
map%width = map%flv%get_width ()
map%variable_limits = variable_limits
map%a_unknown = .true.
map%b_unknown = .true.
select case (map%type)
case (S_CHANNEL)
if (map%mass <= 0) then
call map%write ()
call msg_fatal &
& (" S-channel resonance must have positive mass")
else if (map%width <= 0) then
call map%write ()
call msg_fatal &
& (" S-channel resonance must have positive width")
end if
case (RADIATION)
map%width = max (map%width, mapping_defaults%energy_scale)
case (INFRARED, COLLINEAR)
map%mass = max (map%mass, mapping_defaults%invariant_mass_scale)
case (T_CHANNEL, U_CHANNEL)
map%mass = max (map%mass, mapping_defaults%momentum_transfer_scale)
end select
end if
end subroutine mapping_set_parameters
@ %def mapping_set_code mapping_set_parameters
@ For a step mapping the mass and width are set directly, instead of
being determined from the flavor parameter (which is meaningless
here). They correspond to the effective upper bound of phase space
due to a resonance, as opposed to the absolute upper bound.
<<Mappings: mapping: TBP>>=
procedure :: set_step_mapping_parameters => &
mapping_set_step_mapping_parameters
<<Mappings: sub interfaces>>=
module subroutine mapping_set_step_mapping_parameters (map, &
mass, width, variable_limits)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: mass, width
logical, intent(in) :: variable_limits
end subroutine mapping_set_step_mapping_parameters
<<Mappings: procedures>>=
module subroutine mapping_set_step_mapping_parameters (map, &
mass, width, variable_limits)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: mass, width
logical, intent(in) :: variable_limits
select case (map%type)
case (STEP_MAPPING_E, STEP_MAPPING_H)
map%variable_limits = variable_limits
map%a_unknown = .true.
map%b_unknown = .true.
map%mass = mass
map%width = width
end select
end subroutine mapping_set_step_mapping_parameters
@ %def mapping_set_step_mapping_parameters
@
\subsection{Retrieve contents}
Return true if there is any / an s-channel mapping.
<<Mappings: mapping: TBP>>=
procedure :: is_set => mapping_is_set
procedure :: is_s_channel => mapping_is_s_channel
procedure :: is_on_shell => mapping_is_on_shell
<<Mappings: sub interfaces>>=
module function mapping_is_set (mapping) result (flag)
class(mapping_t), intent(in) :: mapping
logical :: flag
end function mapping_is_set
module function mapping_is_s_channel (mapping) result (flag)
class(mapping_t), intent(in) :: mapping
logical :: flag
end function mapping_is_s_channel
module function mapping_is_on_shell (mapping) result (flag)
class(mapping_t), intent(in) :: mapping
logical :: flag
end function mapping_is_on_shell
<<Mappings: procedures>>=
module function mapping_is_set (mapping) result (flag)
class(mapping_t), intent(in) :: mapping
logical :: flag
flag = mapping%type /= NO_MAPPING
end function mapping_is_set
module function mapping_is_s_channel (mapping) result (flag)
class(mapping_t), intent(in) :: mapping
logical :: flag
flag = mapping%type == S_CHANNEL
end function mapping_is_s_channel
module function mapping_is_on_shell (mapping) result (flag)
class(mapping_t), intent(in) :: mapping
logical :: flag
flag = mapping%type == ON_SHELL
end function mapping_is_on_shell
@ %def mapping_is_set
@ %def mapping_is_s_channel
@ %def mapping_is_on_shell
@ Return the binary code for the mapped particle.
<<Mappings: mapping: TBP>>=
procedure :: get_bincode => mapping_get_bincode
<<Mappings: sub interfaces>>=
module function mapping_get_bincode (mapping) result (bincode)
class(mapping_t), intent(in) :: mapping
integer(TC) :: bincode
end function mapping_get_bincode
<<Mappings: procedures>>=
module function mapping_get_bincode (mapping) result (bincode)
class(mapping_t), intent(in) :: mapping
integer(TC) :: bincode
bincode = mapping%bincode
end function mapping_get_bincode
@ %def mapping_get_bincode
@ Return the flavor object for the mapped particle.
<<Mappings: mapping: TBP>>=
procedure :: get_flv => mapping_get_flv
<<Mappings: sub interfaces>>=
module function mapping_get_flv (mapping) result (flv)
class(mapping_t), intent(in) :: mapping
type(flavor_t) :: flv
end function mapping_get_flv
<<Mappings: procedures>>=
module function mapping_get_flv (mapping) result (flv)
class(mapping_t), intent(in) :: mapping
type(flavor_t) :: flv
flv = mapping%flv
end function mapping_get_flv
@ %def mapping_get_flv
@ Return stored mass and width, respectively.
<<Mappings: mapping: TBP>>=
procedure :: get_mass => mapping_get_mass
procedure :: get_width => mapping_get_width
<<Mappings: sub interfaces>>=
module function mapping_get_mass (mapping) result (mass)
class(mapping_t), intent(in) :: mapping
real(default) :: mass
end function mapping_get_mass
module function mapping_get_width (mapping) result (width)
class(mapping_t), intent(in) :: mapping
real(default) :: width
end function mapping_get_width
<<Mappings: procedures>>=
module function mapping_get_mass (mapping) result (mass)
class(mapping_t), intent(in) :: mapping
real(default) :: mass
mass = mapping%mass
end function mapping_get_mass
module function mapping_get_width (mapping) result (width)
class(mapping_t), intent(in) :: mapping
real(default) :: width
width = mapping%width
end function mapping_get_width
@ %def mapping_get_mass
@ %def mapping_get_width
@
\subsection{Compare mappings}
Equality for single mappings and arrays
<<Mappings: public>>=
public :: operator(==)
<<Mappings: interfaces>>=
interface operator(==)
module procedure mapping_equal
end interface
<<Mappings: sub interfaces>>=
module function mapping_equal (m1, m2) result (equal)
type(mapping_t), intent(in) :: m1, m2
logical :: equal
end function mapping_equal
<<Mappings: procedures>>=
module function mapping_equal (m1, m2) result (equal)
type(mapping_t), intent(in) :: m1, m2
logical :: equal
if (m1%type == m2%type) then
select case (m1%type)
case (NO_MAPPING)
equal = .true.
case (S_CHANNEL, RADIATION, STEP_MAPPING_E, STEP_MAPPING_H)
equal = (m1%mass == m2%mass) .and. (m1%width == m2%width)
case default
equal = (m1%mass == m2%mass)
end select
else
equal = .false.
end if
end function mapping_equal
@ %def mapping_equal
@
\subsection{Mappings of the invariant mass}
Inserting an $x$ value between 0 and 1, we want to compute the
corresponding invariant mass $m^2(x)$ and the jacobian, aka phase
space factor $f(x)$. We also need the reverse operation.
In general, the phase space factor $f$ is defined by
\begin{equation}
\frac{1}{s}\int_{m^2_{\textrm{min}}}^{m^2_{\textrm{max}}} dm^2\,g(m^2)
= \int_0^1 dx\,\frac{1}{s}\,\frac{dm^2}{dx}\,g(m^2(x))
= \int_0^1 dx\,f(x)\,g(x),
\end{equation}
where thus
\begin{equation}
f(x) = \frac{1}{s}\,\frac{dm^2}{dx}.
\end{equation}
With this mapping, a function of the form
\begin{equation}
g(m^2) = c\frac{dx(m^2)}{dm^2}
\end{equation}
is mapped to a constant:
\begin{equation}
\frac{1}{s}\int_{m^2_{\textrm{min}}}^{m^2_{\textrm{max}}} dm^2\,g(m^2)
= \int_0^1 dx\,f(x)\,g(m^2(x)) = \int_0^1 dx\,\frac{c}{s}.
\end{equation}
Here is the mapping routine. Input are the available energy
squared [[s]], the limits for $m^2$, and the $x$ value. Output are
the $m^2$ value and the phase space factor $f$.
<<Mappings: mapping: TBP>>=
procedure :: compute_msq_from_x => mapping_compute_msq_from_x
<<Mappings: sub interfaces>>=
module subroutine mapping_compute_msq_from_x &
(map, s, msq_min, msq_max, msq, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s, msq_min, msq_max
real(default), intent(out) :: msq, f
real(default), intent(in) :: x
end subroutine mapping_compute_msq_from_x
<<Mappings: procedures>>=
module subroutine mapping_compute_msq_from_x &
(map, s, msq_min, msq_max, msq, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s, msq_min, msq_max
real(default), intent(out) :: msq, f
real(default), intent(in) :: x
real(default) :: z, msq0, msq1, tmp
integer :: type
type = map%type
if (s == 0) &
call msg_fatal (" Applying msq mapping for zero energy")
<<Modify mapping type if necessary>>
select case(type)
case (NO_MAPPING)
<<Constants for trivial msq mapping>>
<<Apply trivial msq mapping>>
case (S_CHANNEL)
<<Constants for s-channel resonance mapping>>
<<Apply s-channel resonance mapping>>
case (COLLINEAR, INFRARED, RADIATION)
<<Constants for s-channel pole mapping>>
<<Apply s-channel pole mapping>>
case (T_CHANNEL, U_CHANNEL)
<<Constants for t-channel pole mapping>>
<<Apply t-channel pole mapping>>
case (STEP_MAPPING_E)
<<Constants for exponential step mapping>>
<<Apply exponential step mapping>>
case (STEP_MAPPING_H)
<<Constants for hyperbolic step mapping>>
<<Apply hyperbolic step mapping>>
case default
call msg_fatal ( " Attempt to apply undefined msq mapping")
end select
end subroutine mapping_compute_msq_from_x
@ %def mapping_compute_msq_from_x
@ The inverse mapping
<<Mappings: mapping: TBP>>=
procedure :: compute_x_from_msq => mapping_compute_x_from_msq
<<Mappings: sub interfaces>>=
module subroutine mapping_compute_x_from_msq &
(map, s, msq_min, msq_max, msq, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s, msq_min, msq_max
real(default), intent(in) :: msq
real(default), intent(out) :: f, x
end subroutine mapping_compute_x_from_msq
<<Mappings: procedures>>=
module subroutine mapping_compute_x_from_msq &
(map, s, msq_min, msq_max, msq, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s, msq_min, msq_max
real(default), intent(in) :: msq
real(default), intent(out) :: f, x
real(default) :: msq0, msq1, tmp, z
integer :: type
type = map%type
if (s == 0) &
call msg_fatal (" Applying inverse msq mapping for zero energy")
<<Modify mapping type if necessary>>
select case (type)
case (NO_MAPPING)
<<Constants for trivial msq mapping>>
<<Apply inverse trivial msq mapping>>
case (S_CHANNEL)
<<Constants for s-channel resonance mapping>>
<<Apply inverse s-channel resonance mapping>>
case (COLLINEAR, INFRARED, RADIATION)
<<Constants for s-channel pole mapping>>
<<Apply inverse s-channel pole mapping>>
case (T_CHANNEL, U_CHANNEL)
<<Constants for t-channel pole mapping>>
<<Apply inverse t-channel pole mapping>>
case (STEP_MAPPING_E)
<<Constants for exponential step mapping>>
<<Apply inverse exponential step mapping>>
case (STEP_MAPPING_H)
<<Constants for hyperbolic step mapping>>
<<Apply inverse hyperbolic step mapping>>
case default
call msg_fatal ( " Attempt to apply undefined msq mapping")
end select
end subroutine mapping_compute_x_from_msq
@ %def mapping_compute_x_from_msq
@
\subsubsection{Trivial mapping}
We simply map the boundaries of the interval $(m_{\textrm{min}},
m_{\textrm{max}})$ to $(0,1)$:
\begin{equation}
m^2 = (1-x) m_{\textrm{min}}^2 + x m_{\textrm{max}}^2;
\end{equation}
the inverse is
\begin{equation}
x = \frac{m^2 - m_{\textrm{min}}^2}{m_{\textrm{max}}^2- m_{\textrm{min}}^2}.
\end{equation}
Hence
\begin{equation}
f(x) = \frac{m_{\textrm{max}}^2 - m_{\textrm{min}}^2}{s},
\end{equation}
and we have, as required,
\begin{equation}
f(x)\,\frac{dx}{dm^2} = \frac{1}{s}.
\end{equation}
We store the constant parameters the first time the mapping is called
-- or, if limits vary, recompute them each time.
<<Constants for trivial msq mapping>>=
if (map%variable_limits .or. map%a_unknown) then
map%a1 = 0
map%a2 = msq_max - msq_min
map%a3 = map%a2 / s
map%a_unknown = .false.
end if
<<Apply trivial msq mapping>>=
msq = (1-x) * msq_min + x * msq_max
f = map%a3
<<Apply inverse trivial msq mapping>>=
if (map%a2 /= 0) then
x = (msq - msq_min) / map%a2
else
x = 0
end if
f = map%a3
@ Resonance or step mapping does not make much sense if the resonance mass is
outside the kinematical bounds. If this is the case, revert to
[[NO_MAPPING]]. This is possible even if the kinematical bounds vary
from event to event.
<<Modify mapping type if necessary>>=
select case (type)
case (S_CHANNEL, STEP_MAPPING_E, STEP_MAPPING_H)
msq0 = map%mass**2
if (msq0 < msq_min .or. msq0 > msq_max) type = NO_MAPPING
end select
@
\subsubsection{Breit-Wigner mapping}
A Breit-Wigner resonance with mass $M$ and width $\Gamma$ is flattened
by the following mapping:
This mapping does not make much sense if the resonance mass is too low.
If this is the case, revert to [[NO_MAPPING]]. There is a tricky
point with this if the mass is too high: [[msq_max]] is not a
constant if structure functions are around. However, switching the
type depending on the overall energy does not change the integral, it
is just another branching point.
\begin{equation}
m^2 = M(M+t\Gamma),
\end{equation}
where
\begin{equation}
t = \tan\left[(1-x)\arctan\frac{m^2_{\textrm{min}} - M^2}{M\Gamma}
+ x \arctan\frac{m^2_{\textrm{max}} - M^2}{M\Gamma}\right].
\end{equation}
The inverse:
\begin{equation}
x = \frac{ \arctan\frac{m^2 - M^2}{M\Gamma}
- \arctan\frac{m^2_{\textrm{min}} - M^2}{M\Gamma}}
{ \arctan\frac{m^2_{\textrm{max}} - M^2}{M\Gamma}
- \arctan\frac{m^2_{\textrm{min}} - M^2}{M\Gamma}}
\end{equation}
The phase-space factor of this transformation is
\begin{equation}
f(x) = \frac{M\Gamma}{s}\left(
\arctan\frac{m^2_{\textrm{max}} - M^2}{M\Gamma}
- \arctan\frac{m^2_{\textrm{min}} - M^2}{M\Gamma}\right)
(1 + t^2).
\end{equation}
This maps any function proportional to
\begin{equation}
g(m^2) = \frac{M\Gamma}{(m^2-M^2)^2 + M^2\Gamma^2}
\end{equation}
to a constant times $1/s$.
<<Constants for s-channel resonance mapping>>=
if (map%variable_limits .or. map%a_unknown) then
msq0 = map%mass ** 2
map%a1 = atan ((msq_min - msq0) / (map%mass * map%width))
map%a2 = atan ((msq_max - msq0) / (map%mass * map%width))
map%a3 = (map%a2 - map%a1) * (map%mass * map%width) / s
map%a_unknown = .false.
end if
<<Apply s-channel resonance mapping>>=
z = (1-x) * map%a1 + x * map%a2
if (-pi/2 < z .and. z < pi/2) then
tmp = tan (z)
msq = map%mass * (map%mass + map%width * tmp)
f = map%a3 * (1 + tmp**2)
else
msq = 0
f = 0
end if
<<Apply inverse s-channel resonance mapping>>=
tmp = (msq - msq0) / (map%mass * map%width)
x = (atan (tmp) - map%a1) / (map%a2 - map%a1)
f = map%a3 * (1 + tmp**2)
@
\subsubsection{Mapping for massless splittings}
This mapping accounts for approximately scale-invariant behavior where
$\ln M^2$ is evenly distributed.
\begin{equation}
m^2 = m_{\textrm{min}}^2 + M^2\left(\exp(xL)-1\right)
\end{equation}
where
\begin{equation}
L = \ln\left(\frac{m_{\textrm{max}}^2 - m_{\textrm{min}}^2}{M^2} + 1\right).
\end{equation}
The inverse:
\begin{equation}
x = \frac1L\ln\left(\frac{m^2-m_{\textrm{min}}^2}{M^2} + 1\right)
\end{equation}
The constant $M$ is a characteristic scale. Above this scale
($m^2-m_{\textrm{min}}^2 \gg M^2$), this mapping behaves like
$x\propto\ln m^2$, while below the scale it reverts to a linear
mapping.
The phase-space factor is
\begin{equation}
f(x) = \frac{M^2}{s}\,\exp(xL)\,L.
\end{equation}
A function proportional to
\begin{equation}
g(m^2) = \frac{1}{(m^2-m_{\textrm{min}}^2) + M^2}
\end{equation}
is mapped to a constant, i.e., a simple pole near $m_{\textrm{min}}$
with a regulator mass $M$.
This type of mapping is useful for massless collinear and infrared
singularities, where the scale is stored as the mass parameter. In
the radiation case (IR radiation off massive particle), the heavy
particle width is the characteristic scale.
<<Constants for s-channel pole mapping>>=
if (map%variable_limits .or. map%a_unknown) then
if (type == RADIATION) then
msq0 = map%width**2
else
msq0 = map%mass**2
end if
map%a1 = msq0
map%a2 = log ((msq_max - msq_min) / msq0 + 1)
map%a3 = map%a2 / s
map%a_unknown = .false.
end if
<<Apply s-channel pole mapping>>=
msq1 = map%a1 * exp (x * map%a2)
msq = msq1 - map%a1 + msq_min
f = map%a3 * msq1
<<Apply inverse s-channel pole mapping>>=
msq1 = msq - msq_min + map%a1
x = log (msq1 / map%a1) / map%a2
f = map%a3 * msq1
@
\subsubsection{Mapping for t-channel poles}
This is also approximately scale-invariant, and we use the same type
of mapping as before. However, we map $1/x$ singularities at both
ends of the interval; again, the mapping becomes linear when the
distance is less than $M^2$:
\begin{equation}
m^2 =
\begin{cases}
m_{\textrm{min}}^2 + M^2\left(\exp(xL)-1\right)
&
\text{for $0 < x < \frac12$}
\\
m_{\textrm{max}}^2 - M^2\left(\exp((1-x)L)-1\right)
&
\text{for $\frac12 \leq x < 1$}
\end{cases}
\end{equation}
where
\begin{equation}
L = 2\ln\left(\frac{m_{\textrm{max}}^2 - m_{\textrm{min}}^2}{2M^2}
+ 1\right).
\end{equation}
The inverse:
\begin{equation}
x =
\begin{cases}
\frac1L\ln\left(\frac{m^2-m_{\textrm{min}}^2}{M^2} + 1\right)
&
\text{for $m^2 < (m_{\textrm{max}}^2 - m_{\textrm{min}}^2)/2$}
\\
1 - \frac1L\ln\left(\frac{m_{\textrm{max}}-m^2}{M^2} + 1\right)
&
\text{for $m^2 \geq (m_{\textrm{max}}^2 - m_{\textrm{min}}^2)/2$}
\end{cases}
\end{equation}
The phase-space factor is
\begin{equation}
f(x) =
\begin{cases}
\frac{M^2}{s}\,\exp(xL)\,L.
&
\text{for $0 < x < \frac12$}
\\
\frac{M^2}{s}\,\exp((1-x)L)\,L.
&
\text{for $\frac12 \leq x < 1$}
\end{cases}
\end{equation}
A (continuous) function proportional to
\begin{equation}
g(m^2) =
\begin{cases}
1/(m^2-m_{\textrm{min}}^2) + M^2)
&
\text{for $m^2 < (m_{\textrm{max}}^2 - m_{\textrm{min}}^2)/2$}
\\
1/((m_{\textrm{max}}^2 - m^2) + M^2)
&
\text{for $m^2 \leq (m_{\textrm{max}}^2 - m_{\textrm{min}}^2)/2$}
\end{cases}
\end{equation}
is mapped to a constant by this mapping, i.e., poles near both ends of
the interval.
<<Constants for t-channel pole mapping>>=
if (map%variable_limits .or. map%a_unknown) then
msq0 = map%mass**2
map%a1 = msq0
map%a2 = 2 * log ((msq_max - msq_min)/(2*msq0) + 1)
map%a3 = map%a2 / s
map%a_unknown = .false.
end if
<<Apply t-channel pole mapping>>=
if (x < .5_default) then
msq1 = map%a1 * exp (x * map%a2)
msq = msq1 - map%a1 + msq_min
else
msq1 = map%a1 * exp ((1-x) * map%a2)
msq = -(msq1 - map%a1) + msq_max
end if
f = map%a3 * msq1
<<Apply inverse t-channel pole mapping>>=
if (msq < (msq_max + msq_min)/2) then
msq1 = msq - msq_min + map%a1
x = log (msq1/map%a1) / map%a2
else
msq1 = msq_max - msq + map%a1
x = 1 - log (msq1/map%a1) / map%a2
end if
f = map%a3 * msq1
@
\subsection{Step mapping}
Step mapping is useful when the allowed range for a squared-mass
variable is large, but only a fraction at the lower end is populated
because the particle in question is an (off-shell) decay product of a
narrow resonance. I.e., if the resonance was forced to be on-shell,
the upper end of the range would be the resonance mass, minus the
effective (real or resonance) mass of the particle(s) in the sibling
branch of the decay.
The edge of this phase space section has a width which is determined
by the width of the parent, plus the width of the sibling branch. (The
widths might be added in quadrature, but this precision is probably
not important.)
\subsubsection{Fermi function}
A possible mapping is derived from the Fermi function which has
precisely this behavior. The Fermi function is given by
\begin{equation}
f(x) = \frac{1}{1 + \exp\frac{x-\mu}{\gamma}}
\end{equation}
where $x$ is taken as the invariant mass squared, $\mu$ is the
invariant mass squared of the edge, and $\gamma$ is the effective
width which is given by the widths of the parent and the sibling
branch. (Widths might be added in quadrature, but we do not require
this level of precision.)
\begin{align}
x &= \frac{m^2 - m_{\text{min}}^2}{\Delta m^2}
\\
\mu &=
\frac{m_{\text{max,eff}}^2 - m_{\text{min}}^2}
{\Delta m^2}
\\
\gamma &= \frac{2m_{\text{max,eff}}\Gamma}{\Delta m^2}
\end{align}
with
\begin{equation}
\Delta m^2 = m_{\text{max}}^2 - m_{\text{min}}^2
\end{equation}
$m^2$ is thus given by
\begin{equation}
m^2(x) = xm_{\text{max}}^2 + (1-x)m_{\text{min}}^2
\end{equation}
For the mapping, we compute the integral $g(x)$ of the Fermi function,
normalized such that $g(0)=0$ and $g(1)=1$. We introduce the abbreviations
\begin{align}
\alpha &= 1 - \gamma\ln\frac{1 + \beta e^{1/\gamma}}{1 + \beta}
\\
\beta &= e^{- \mu/\gamma}
\end{align}
and obtain
\begin{equation}
g(x) = \frac{1}{\alpha}
\left(x - \gamma\ln\frac{1 + \beta e^{x/\gamma}}
{1 + \beta}\right)
\end{equation}
The actual mapping is the inverse function $h(y) = g^{-1}(y)$,
\begin{equation}
h(y) = -\gamma\ln\left(e^{-\alpha y/\gamma}(1 + \beta) - \beta\right)
\end{equation}
The Jacobian is
\begin{equation}
\frac{dh}{dy} = \alpha\left(1 - e^{\alpha y/\gamma}
\frac{\beta}{1 + \beta}\right)^{-1}
\end{equation}
which is equal to $1/(dg/dx)$, namely
\begin{equation}
\frac{dg}{dx} = \frac{1}{\alpha}\,\frac{1}{1 + \beta e^{x/\gamma}}
\end{equation}
The final result is
\begin{align}
\int_{m_{\text{min}}^2}^{m_{\text{max}}^2} dm^2\,F(m^2)
&= \Delta m^2\int_0^1\,dx\,F(m^2(x))
\\
&= \Delta m^2\int_0^1\,dy\,F(m^2(h(y)))\,\frac{dh}{dy}
\end{align}
Here is the implementation. We fill [[a1]], [[a2]], [[a3]] with
$\alpha,\beta,\gamma$, respectively.
<<Constants for exponential step mapping>>=
if (map%variable_limits .or. map%a_unknown) then
map%a3 = max (2 * map%mass * map%width / (msq_max - msq_min), 0.01_default)
map%a2 = exp (- (map%mass**2 - msq_min) / (msq_max - msq_min) &
/ map%a3)
map%a1 = 1 - map%a3 * log ((1 + map%a2 * exp (1 / map%a3)) / (1 + map%a2))
end if
<<Apply exponential step mapping>>=
tmp = exp (- x * map%a1 / map%a3) * (1 + map%a2)
z = - map%a3 * log (tmp - map%a2)
msq = z * msq_max + (1 - z) * msq_min
f = map%a1 / (1 - map%a2 / tmp) * (msq_max - msq_min) / s
<<Apply inverse exponential step mapping>>=
z = (msq - msq_min) / (msq_max - msq_min)
tmp = 1 + map%a2 * exp (z / map%a3)
x = (z - map%a3 * log (tmp / (1 + map%a2))) &
/ map%a1
f = map%a1 * tmp * (msq_max - msq_min) / s
@
\subsubsection{Hyperbolic mapping}
The Fermi function has the drawback that it decreases exponentially.
It might be preferable to take a function with a power-law decrease,
such that the high-mass region is not completely depopulated.
Here, we start with the actual mapping which we take as
\begin{equation}
h(y) = \frac{b}{a-y} - \frac{b}{a} + \mu y
\end{equation}
with the abbreviation
\begin{equation}
a = \frac12\left(1 + \sqrt{1 + \frac{4b}{1-\mu}}\right)
\end{equation}
This is a hyperbola in the $xy$ plane. The derivative is
\begin{equation}
\frac{dh}{dy} = \frac{b}{(a-y)^2} + \mu
\end{equation}
The constants correspond to
\begin{align}
\mu &=
\frac{m_{\text{max,eff}}^2 - m_{\text{min}}^2}
{\Delta m^2}
\\
b &= \frac{1}{\mu}\left(\frac{2m_{\text{max,eff}}\Gamma}{\Delta m^2}\right)^2
\end{align}
The inverse function is the solution of a quadratic equation,
\begin{equation}
g(x) = \frac{1}{2}
\left[\left(a + \frac{x}{\mu} + \frac{b}{a\mu}\right)
- \sqrt{\left(a-\frac{x}{\mu}\right)^2
+ 2\frac{b}{a\mu}\left(a + \frac{x}{\mu}\right)
+ \left(\frac{b}{a\mu}\right)^2}\right]
\end{equation}
The constants $a_{1,2,3}$ are identified with $a,b,\mu$.
<<Constants for hyperbolic step mapping>>=
if (map%variable_limits .or. map%a_unknown) then
map%a3 = (map%mass**2 - msq_min) / (msq_max - msq_min)
map%a2 = max ((2 * map%mass * map%width / (msq_max - msq_min))**2 &
/ map%a3, 1e-6_default)
map%a1 = (1 + sqrt (1 + 4 * map%a2 / (1 - map%a3))) / 2
end if
<<Apply hyperbolic step mapping>>=
z = map%a2 / (map%a1 - x) - map%a2 / map%a1 + map%a3 * x
msq = z * msq_max + (1 - z) * msq_min
f = (map%a2 / (map%a1 - x)**2 + map%a3) * (msq_max - msq_min) / s
<<Apply inverse hyperbolic step mapping>>=
z = (msq - msq_min) / (msq_max - msq_min)
tmp = map%a2 / (map%a1 * map%a3)
x = ((map%a1 + z / map%a3 + tmp) &
- sqrt ((map%a1 - z / map%a3)**2 + 2 * tmp * (map%a1 + z / map%a3) &
+ tmp**2)) / 2
f = (map%a2 / (map%a1 - x)**2 + map%a3) * (msq_max - msq_min) / s
@
\subsection{Mappings of the polar angle}
The other type of singularity, a simple pole just outside the
integration region, can occur in the integration over $\cos\theta$.
This applies to exchange of massless (or light) particles.
Double poles (Coulomb scattering) are also possible, but only in
certain cases. These are also handled by the single-pole mapping.
The mapping is analogous to the previous $m^2$ pole mapping, but with
a different normalization and notation of variables:
\begin{equation}
\frac12\int_{-1}^1 d\cos\theta\,g(\theta)
= \int_0^1 dx\,\frac{d\cos\theta}{dx}\,g(\theta(x))
= \int_0^1 dx\,f(x)\,g(x),
\end{equation}
where thus
\begin{equation}
f(x) = \frac12\,\frac{d\cos\theta}{dx}.
\end{equation}
With this mapping, a function of the form
\begin{equation}
g(\theta) = c\frac{dx(\cos\theta)}{d\cos\theta}
\end{equation}
is mapped to a constant:
\begin{equation}
\int_{-1}^1 d\cos\theta\,g(\theta)
= \int_0^1 dx\,f(x)\,g(\theta(x)) = \int_0^1 dx\,c.
\end{equation}
<<Mappings: mapping: TBP>>=
procedure :: compute_ct_from_x => mapping_compute_ct_from_x
<<Mappings: sub interfaces>>=
module subroutine mapping_compute_ct_from_x (map, s, ct, st, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s
real(default), intent(out) :: ct, st, f
real(default), intent(in) :: x
end subroutine mapping_compute_ct_from_x
<<Mappings: procedures>>=
module subroutine mapping_compute_ct_from_x (map, s, ct, st, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s
real(default), intent(out) :: ct, st, f
real(default), intent(in) :: x
real(default) :: tmp, ct1
select case (map%type)
case (NO_MAPPING, S_CHANNEL, INFRARED, RADIATION, &
STEP_MAPPING_E, STEP_MAPPING_H)
<<Apply trivial ct mapping>>
case (T_CHANNEL, U_CHANNEL, COLLINEAR)
<<Constants for ct pole mapping>>
<<Apply ct pole mapping>>
case default
call msg_fatal (" Attempt to apply undefined ct mapping")
end select
end subroutine mapping_compute_ct_from_x
@ %def mapping_compute_ct_from_x
<<Mappings: mapping: TBP>>=
procedure :: compute_x_from_ct => mapping_compute_x_from_ct
<<Mappings: sub interfaces>>=
module subroutine mapping_compute_x_from_ct (map, s, ct, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s
real(default), intent(in) :: ct
real(default), intent(out) :: f, x
end subroutine mapping_compute_x_from_ct
<<Mappings: procedures>>=
module subroutine mapping_compute_x_from_ct (map, s, ct, f, x)
class(mapping_t), intent(inout) :: map
real(default), intent(in) :: s
real(default), intent(in) :: ct
real(default), intent(out) :: f, x
real(default) :: ct1
select case (map%type)
case (NO_MAPPING, S_CHANNEL, INFRARED, RADIATION, &
STEP_MAPPING_E, STEP_MAPPING_H)
<<Apply inverse trivial ct mapping>>
case (T_CHANNEL, U_CHANNEL, COLLINEAR)
<<Constants for ct pole mapping>>
<<Apply inverse ct pole mapping>>
case default
call msg_fatal (" Attempt to apply undefined inverse ct mapping")
end select
end subroutine mapping_compute_x_from_ct
@ %def mapping_compute_x_from_ct
@
\subsubsection{Trivial mapping}
This is just the mapping of the interval $(-1,1)$ to $(0,1)$:
\begin{equation}
\cos\theta = -1 + 2x
\end{equation}
and
\begin{equation}
f(x) = 1
\end{equation}
with the inverse
\begin{equation}
x = \frac{1+\cos\theta}{2}
\end{equation}
<<Apply trivial ct mapping>>=
tmp = 2 * (1-x)
ct = 1 - tmp
st = sqrt (tmp * (2-tmp))
f = 1
<<Apply inverse trivial ct mapping>>=
x = (ct + 1) / 2
f = 1
@
\subsubsection{Pole mapping}
As above for $m^2$, we simultaneously map poles at both ends of the
$\cos\theta$ interval. The formulae are completely analogous:
\begin{equation}
\cos\theta =
\begin{cases}
\frac{M^2}{s}\left[\exp(xL)-1\right] - 1
&
\text{for $x<\frac12$}
\\
-\frac{M^2}{s}\left[\exp((1-x)L)-1\right] + 1
&
\text{for $x\geq\frac12$}
\end{cases}
\end{equation}
where
\begin{equation}
L = 2\ln\frac{M^2+s}{M^2}.
\end{equation}
Inverse:
\begin{equation}
x =
\begin{cases}
\frac{1}{2L}\ln\frac{1 + \cos\theta + M^2/s}{M^2/s}
&
\text{for $\cos\theta < 0$}
\\
1 - \frac{1}{2L}\ln\frac{1 - \cos\theta + M^2/s}{M^2/s}
&
\text{for $\cos\theta \geq 0$}
\end{cases}
\end{equation}
The phase-space factor:
\begin{equation}
f(x) =
\begin{cases}
\frac{M^2}{s}\exp(xL)\,L
&
\text{for $x<\frac12$}
\\
\frac{M^2}{s}\exp((1-x)L)\,L
&
\text{for $x\geq\frac12$}
\end{cases}
\end{equation}
<<Constants for ct pole mapping>>=
if (map%variable_limits .or. map%b_unknown) then
map%b1 = map%mass**2 / s
map%b2 = log ((map%b1 + 1) / map%b1)
map%b3 = 0
map%b_unknown = .false.
end if
<<Apply ct pole mapping>>=
if (x < .5_default) then
ct1 = map%b1 * exp (2 * x * map%b2)
ct = ct1 - map%b1 - 1
else
ct1 = map%b1 * exp (2 * (1-x) * map%b2)
ct = -(ct1 - map%b1) + 1
end if
if (ct >= -1 .and. ct <= 1) then
st = sqrt (1 - ct**2)
f = ct1 * map%b2
else
ct = 1; st = 0; f = 0
end if
<<Apply inverse ct pole mapping>>=
if (ct < 0) then
ct1 = ct + map%b1 + 1
x = log (ct1 / map%b1) / (2 * map%b2)
else
ct1 = -ct + map%b1 + 1
x = 1 - log (ct1 / map%b1) / (2 * map%b2)
end if
f = ct1 * map%b2
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\clearpage
\section{Phase-space trees}
The phase space evaluation is organized in terms of trees, where each
branch corresponds to three integrations: $m^2$, $\cos\theta$, and
$\phi$. The complete tree thus makes up a specific parameterization
of the multidimensional phase-space integral. For the multi-channel
integration, the phase-space tree is a single channel.
The trees imply mappings of formal Feynman tree graphs into arrays of
integer numbers: Each branch, corresponding to a particular line in
the graph, is assigned an integer code $c$ (with kind value [[TC]] =
tree code).
In this integer, each bit determines whether a particular external
momentum flows through the line. The external branches therefore have
codes $1,2,4,8,\ldots$. An internal branch has those bits ORed
corresponding to the momenta flowing through it. For example, a
branch with momentum $p_1+p_4$ has code $2^0+2^3=1+8=9$.
There is a two-fold ambiguity: Momentum conservation implies that the
branch with code
\begin{equation}
c_0 = \sum_{i=1}^{n(\rm{ext})} 2^{i-1}
\end{equation}
i.e. the branch with momentum $p_1+p_2+\ldots p_n$ has momentum zero,
which is equivalent to tree code $0$ by definition. Correspondingly,
\begin{equation}
c \quad\textrm{and}\quad c_0 - c = c\;\textrm{XOR}\;c_0
\end{equation}
are equivalent. E.g., if there are five externals with codes
$c=1,2,4,8,16$, then $c=9$ and $\bar c=31-9=22$ are equivalent.
This ambiguity may be used to assign a direction to the line: If all
momenta are understood as outgoing, $c=9$ in the example above means
$p_1+p_4$, but $c=22$ means $p_2+p_3+p_5 = -(p_1+p_4)$.
Here we make use of the ambiguity in a slightly different way. First,
the initial particles are singled out as those externals with the
highest bits, the IN-bits. (Here: $8$ and $16$ for a $2\to 3$
scattering process, $16$ only for a $1\to 4$ decay.) Then we invert
those codes where all IN-bits are set. For a decay process this maps
each tree of an equivalence class onto a unique representative (that one
with the smallest integer codes). For a scattering process we proceed
further:
The ambiguity remains in all branches where only one IN-bit is set,
including the initial particles. If there are only externals with
this property, we have an $s$-channel graph which we leave as it is.
In all other cases, an internal with only one IN-bit is a $t$-channel
line, which for phase space integration should be associated with one
of the initial momenta as a reference axis. We take that one whose
bit is set in the current tree code. (E.g., for branch $c=9$ we use
the initial particle $c=8$ as reference axis, whereas for the same
branch we would take $c=16$ if it had been assigned $\bar c=31-9=22$
as tree code.) Thus, different ways of coding the same $t$-channel
graph imply different phase space parameterizations.
$s$-channel graphs have a unique parameterization. The same sets of
parameterizations are used for $t$-channel graphs, except for the
reference frames of their angular parts. We map each
$t$-channel graph onto an $s$-channel graph as follows:
Working in ascending order, for each $t$-channel line (whose code has
exactly one IN-bit set) the attached initial line is flipped upstream,
while the outgoing line is flipped downstream. (This works only if
$t$-channel graphs are always parameterized beginning at their outer
vertices, which we require as a restriction.) After all possible
flips have been applied, we have an $s$-channel graph. We only have
to remember the initial particle a vertex was originally attached to.
<<[[phs_trees.f90]]>>=
<<File header>>
module phs_trees
<<Use kinds>>
use kinds, only: TC
<<Use strings>>
use lorentz
use permutations, only: permutation_t, permutation_size
use permutations, only: permutation_init, permutation_find
use permutations, only: tc_decay_level, tc_permute
use model_data
use flavors
use resonances, only: resonance_history_t, resonance_info_t
use mappings
<<Standard module head>>
<<PHS trees: public>>
<<PHS trees: types>>
interface
<<PHS trees: sub interfaces>>
end interface
end module phs_trees
@ %def phs_trees
@
<<[[phs_trees_sub.f90]]>>=
<<File header>>
submodule (phs_trees) phs_trees_s
use io_units
use constants, only: twopi, twopi2, twopi5
use format_defs, only: FMT_19
use numeric_utils, only: vanishes
use diagnostics
implicit none
contains
<<PHS trees: procedures>>
end submodule phs_trees_s
@ %def phs_trees_s
@
\subsection{Particles}
We define a particle type which contains only four-momentum and
invariant mass squared, and a flag that tells whether the momentum is
filled or not.
<<PHS trees: public>>=
public :: phs_prt_t
<<PHS trees: types>>=
type :: phs_prt_t
private
logical :: defined = .false.
type(vector4_t) :: p
real(default) :: p2
contains
<<PHS trees: prt: TBP>>
end type phs_prt_t
@ %def phs_prt_t
@ Set contents:
<<PHS trees: prt: TBP>>=
procedure :: set_defined => phs_prt_set_defined
procedure :: set_undefined => phs_prt_set_undefined
procedure :: set_momentum => phs_prt_set_momentum
procedure :: set_msq => phs_prt_set_msq
<<PHS trees: sub interfaces>>=
elemental module subroutine phs_prt_set_defined (prt)
class(phs_prt_t), intent(inout) :: prt
end subroutine phs_prt_set_defined
elemental module subroutine phs_prt_set_undefined (prt)
class(phs_prt_t), intent(inout) :: prt
end subroutine phs_prt_set_undefined
elemental module subroutine phs_prt_set_momentum (prt, p)
class(phs_prt_t), intent(inout) :: prt
type(vector4_t), intent(in) :: p
end subroutine phs_prt_set_momentum
elemental module subroutine phs_prt_set_msq (prt, p2)
class(phs_prt_t), intent(inout) :: prt
real(default), intent(in) :: p2
end subroutine phs_prt_set_msq
<<PHS trees: procedures>>=
elemental module subroutine phs_prt_set_defined (prt)
class(phs_prt_t), intent(inout) :: prt
prt%defined = .true.
end subroutine phs_prt_set_defined
elemental module subroutine phs_prt_set_undefined (prt)
class(phs_prt_t), intent(inout) :: prt
prt%defined = .false.
end subroutine phs_prt_set_undefined
elemental module subroutine phs_prt_set_momentum (prt, p)
class(phs_prt_t), intent(inout) :: prt
type(vector4_t), intent(in) :: p
prt%p = p
end subroutine phs_prt_set_momentum
elemental module subroutine phs_prt_set_msq (prt, p2)
class(phs_prt_t), intent(inout) :: prt
real(default), intent(in) :: p2
prt%p2 = p2
end subroutine phs_prt_set_msq
@ %def phs_prt_set_defined phs_prt_set_momentum phs_prt_set_msq
@ Access methods:
<<PHS trees: prt: TBP>>=
procedure :: is_defined => phs_prt_is_defined
procedure :: get_momentum => phs_prt_get_momentum
procedure :: get_msq => phs_prt_get_msq
<<PHS trees: sub interfaces>>=
elemental module function phs_prt_is_defined (prt) result (defined)
logical :: defined
class(phs_prt_t), intent(in) :: prt
end function phs_prt_is_defined
elemental module function phs_prt_get_momentum (prt) result (p)
type(vector4_t) :: p
class(phs_prt_t), intent(in) :: prt
end function phs_prt_get_momentum
elemental module function phs_prt_get_msq (prt) result (p2)
real(default) :: p2
class(phs_prt_t), intent(in) :: prt
end function phs_prt_get_msq
<<PHS trees: procedures>>=
elemental module function phs_prt_is_defined (prt) result (defined)
logical :: defined
class(phs_prt_t), intent(in) :: prt
defined = prt%defined
end function phs_prt_is_defined
elemental module function phs_prt_get_momentum (prt) result (p)
type(vector4_t) :: p
class(phs_prt_t), intent(in) :: prt
p = prt%p
end function phs_prt_get_momentum
elemental module function phs_prt_get_msq (prt) result (p2)
real(default) :: p2
class(phs_prt_t), intent(in) :: prt
p2 = prt%p2
end function phs_prt_get_msq
@ %def phs_prt_is_defined phs_prt_get_momentum phs_prt_get_msq
@ Addition of momenta (invariant mass square is computed).
<<PHS trees: prt: TBP>>=
procedure :: combine => phs_prt_combine
<<PHS trees: sub interfaces>>=
elemental module subroutine phs_prt_combine (prt, prt1, prt2)
class(phs_prt_t), intent(inout) :: prt
type(phs_prt_t), intent(in) :: prt1, prt2
end subroutine phs_prt_combine
<<PHS trees: procedures>>=
elemental module subroutine phs_prt_combine (prt, prt1, prt2)
class(phs_prt_t), intent(inout) :: prt
type(phs_prt_t), intent(in) :: prt1, prt2
prt%defined = .true.
prt%p = prt1%p + prt2%p
prt%p2 = prt%p ** 2
call phs_prt_check (prt)
end subroutine phs_prt_combine
@ %def phs_prt_combine
@ Output
<<PHS trees: prt: TBP>>=
procedure :: write => phs_prt_write
<<PHS trees: sub interfaces>>=
module subroutine phs_prt_write (prt, unit)
class(phs_prt_t), intent(in) :: prt
integer, intent(in), optional :: unit
end subroutine phs_prt_write
<<PHS trees: procedures>>=
module subroutine phs_prt_write (prt, unit)
class(phs_prt_t), intent(in) :: prt
integer, intent(in), optional :: unit
integer :: u
u = given_output_unit (unit); if (u < 0) return
if (prt%defined) then
call vector4_write (prt%p, u)
write (u, "(1x,A,1x," // FMT_19 // ")") "T = ", prt%p2
else
write (u, "(3x,A)") "[undefined]"
end if
end subroutine phs_prt_write
@ %def phs_prt_write
<<PHS trees: prt: TBP>>=
procedure :: check => phs_prt_check
<<PHS trees: sub interfaces>>=
elemental module subroutine phs_prt_check (prt)
class(phs_prt_t), intent(inout) :: prt
end subroutine phs_prt_check
<<PHS trees: procedures>>=
elemental module subroutine phs_prt_check (prt)
class(phs_prt_t), intent(inout) :: prt
if (prt%p2 < 0._default) then
prt%p2 = 0._default
end if
end subroutine phs_prt_check
@ %def phs_prt_check
@
\subsection{The phase-space tree type}
\subsubsection{Definition}
In the concrete implementation, each branch $c$ may have two
\emph{daughters} $c_1$ and $c_2$ such that $c_1+c_2=c$, a
\emph{sibling} $c_s$ and a \emph{mother} $c_m$ such that $c+c_s =
c_m$, and a \emph{friend} which is kept during flips, such that it can
indicate a fixed reference frame. Absent entries are set $c=0$.
First, declare the branch type. There is some need to have this
public. Give initializations for all components, so no [[init]]
routine is necessary. The branch has some information about the
associated coordinates and about connections.
<<PHS trees: types>>=
type :: phs_branch_t
private
logical :: set = .false.
logical :: inverted_decay = .false.
logical :: inverted_axis = .false.
integer(TC) :: mother = 0
integer(TC) :: sibling = 0
integer(TC) :: friend = 0
integer(TC) :: origin = 0
integer(TC), dimension(2) :: daughter = 0
integer :: firstborn = 0
logical :: has_children = .false.
logical :: has_friend = .false.
logical :: is_real = .false.
end type phs_branch_t
@ %def phs_branch_t
@ The tree type: No initialization, this is done by
[[phs_tree_init]]. In addition to the branch array which
The branches are collected in an array which holds all possible
branches, of which only a few are set. After flips have been applied,
the branch $c_M=\sum_{i=1}^{n({\rm fin})}2^{i-1}$ must be there,
indicating the mother of all decay products. In addition, we should
check for consistency at the beginning.
[[n_branches]] is the number of those actually set. [[n_externals]]
defines the number of significant bit, and [[mask]] is a code where all
bits are set. Analogous: [[n_in]] and [[mask_in]] for the incoming
particles.
The [[mapping]] array contains the mappings associated to the branches
(corresponding indices). The array [[mass_sum]] contains the sum of
the real masses of the external final-state particles associated to
the branch. During phase-space evaluation, this determines the
boundaries.
<<PHS trees: public>>=
public :: phs_tree_t
<<PHS trees: types>>=
type :: phs_tree_t
private
integer :: n_branches, n_externals, n_in, n_msq, n_angles
integer(TC) :: n_branches_tot, n_branches_out
integer(TC) :: mask, mask_in, mask_out
type(phs_branch_t), dimension(:), allocatable :: branch
type(mapping_t), dimension(:), allocatable :: mapping
real(default), dimension(:), allocatable :: mass_sum
real(default), dimension(:), allocatable :: effective_mass
real(default), dimension(:), allocatable :: effective_width
logical :: real_phsp = .false.
integer, dimension(:), allocatable :: momentum_link
contains
<<PHS trees: phs tree: TBP>>
end type phs_tree_t
@ %def phs_tree_t
@ The maximum number of external particles that can be represented is
related to the bit size of the integer that stores binary codes. With
the default integer of 32 bit on common machines, this is more than
enough space. If [[TC]] is actually the default integer kind, there
is no need to keep it separate, but doing so marks this as a
special type of integer. So, just state that the maximum number is 32:
<<Limits: public parameters>>=
integer, parameter, public :: MAX_EXTERNAL = 32
@ %def MAX_EXTERNAL
@
\subsubsection{Constructor and destructor}
Allocate memory for a phase-space tree with given number of externals and
incoming. The number of allocated branches can easily become large,
but appears manageable for realistic cases, e.g., for [[n_in=2]] and
[[n_out=8]] we get $2^{10}-1=1023$.
Here we set the masks for incoming and for all externals.
<<PHS trees: phs tree: TBP>>=
procedure :: init => phs_tree_init
procedure :: final => phs_tree_final
<<PHS trees: sub interfaces>>=
elemental module subroutine phs_tree_init &
(tree, n_in, n_out, n_masses, n_angles)
class(phs_tree_t), intent(inout) :: tree
integer, intent(in) :: n_in, n_out, n_masses, n_angles
end subroutine phs_tree_init
elemental module subroutine phs_tree_final (tree)
class(phs_tree_t), intent(inout) :: tree
end subroutine phs_tree_final
<<PHS trees: procedures>>=
elemental module subroutine phs_tree_init &
(tree, n_in, n_out, n_masses, n_angles)
class(phs_tree_t), intent(inout) :: tree
integer, intent(in) :: n_in, n_out, n_masses, n_angles
integer(TC) :: i
tree%n_externals = n_in + n_out
tree%n_branches_tot = 2**(n_in+n_out) - 1
tree%n_branches_out = 2**n_out - 1
tree%mask = 0
do i = 0, n_in + n_out - 1
tree%mask = ibset (tree%mask, i)
end do
tree%n_in = n_in
tree%mask_in = 0
do i = n_out, n_in + n_out - 1
tree%mask_in = ibset (tree%mask_in, i)
end do
tree%mask_out = ieor (tree%mask, tree%mask_in)
tree%n_msq = n_masses
tree%n_angles = n_angles
allocate (tree%branch (tree%n_branches_tot))
tree%n_branches = 0
allocate (tree%mapping (tree%n_branches_out))
allocate (tree%mass_sum (tree%n_branches_out))
allocate (tree%effective_mass (tree%n_branches_out))
allocate (tree%effective_width (tree%n_branches_out))
end subroutine phs_tree_init
elemental module subroutine phs_tree_final (tree)
class(phs_tree_t), intent(inout) :: tree
deallocate (tree%branch)
deallocate (tree%mapping)
deallocate (tree%mass_sum)
deallocate (tree%effective_mass)
deallocate (tree%effective_width)
end subroutine phs_tree_final
@ %def phs_tree_init phs_tree_final
@
\subsubsection{Screen output}
Write only the branches that are set:
<<PHS trees: phs tree: TBP>>=
procedure :: write => phs_tree_write
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_write (tree, unit)
class(phs_tree_t), intent(in) :: tree
integer, intent(in), optional :: unit
end subroutine phs_tree_write
<<PHS trees: procedures>>=
module subroutine phs_tree_write (tree, unit)
class(phs_tree_t), intent(in) :: tree
integer, intent(in), optional :: unit
integer :: u
integer(TC) :: k
u = given_output_unit (unit); if (u < 0) return
write (u, '(3X,A,1x,I0,5X,A,I3)') &
'External:', tree%n_externals, 'Mask:', tree%mask
write (u, '(3X,A,1x,I0,5X,A,I3)') &
'Incoming:', tree%n_in, 'Mask:', tree%mask_in
write (u, '(3X,A,1x,I0,5X,A,I3)') &
'Branches:', tree%n_branches
do k = size (tree%branch), 1, -1
if (tree%branch(k)%set) &
call phs_branch_write (tree%branch(k), unit=unit, kval=k)
end do
do k = 1, size (tree%mapping)
call tree%mapping (k)%write (unit, verbose=.true.)
end do
write (u, "(3x,A)") "Arrays: mass_sum, effective_mass, effective_width"
do k = 1, size (tree%mass_sum)
if (tree%branch(k)%set) then
write (u, "(5x,I0,3(2x," // FMT_19 // "))") k, tree%mass_sum(k), &
tree%effective_mass(k), tree%effective_width(k)
end if
end do
end subroutine phs_tree_write
subroutine phs_branch_write (b, unit, kval)
type(phs_branch_t), intent(in) :: b
integer, intent(in), optional :: unit
integer(TC), intent(in), optional :: kval
integer :: u
integer(TC) :: k
character(len=6) :: tmp
character(len=1) :: firstborn(2), sign_decay, sign_axis
integer :: i
u = given_output_unit (unit); if (u < 0) return
k = 0; if (present (kval)) k = kval
if (b%origin /= 0) then
write(tmp, '(A,I4,A)') '(', b%origin, ')'
else
tmp = ' '
end if
do i=1, 2
if (b%firstborn == i) then
firstborn(i) = "*"
else
firstborn(i) = " "
end if
end do
if (b%inverted_decay) then
sign_decay = "-"
else
sign_decay = "+"
end if
if (b%inverted_axis) then
sign_axis = "-"
else
sign_axis = "+"
end if
if (b%has_children) then
if (b%has_friend) then
write(u,'(4X,A1,I0,3x,A,1X,A,I0,A1,1x,I0,A1,1X,A1,1X,A,1x,I0)') &
& '*', k, tmp, &
& 'Daughters: ', &
& b%daughter(1), firstborn(1), &
& b%daughter(2), firstborn(2), sign_decay, &
& 'Friend: ', b%friend
else
write(u,'(4X,A1,I0,3x,A,1X,A,I0,A1,1x,I0,A1,1X,A1,1X,A)') &
& '*', k, tmp, &
& 'Daughters: ', &
& b%daughter(1), firstborn(1), &
& b%daughter(2), firstborn(2), sign_decay, &
& '(axis '//sign_axis//')'
end if
else
write(u,'(5X,I0)') k
end if
end subroutine phs_branch_write
@ %def phs_tree_write phs_branch_write
@
\subsection{PHS tree setup}
\subsubsection{Transformation into an array of branch codes and back}
Assume that the tree/array has been created before with the
appropriate length and is empty.
<<PHS trees: public>>=
public :: phs_tree_from_array
<<PHS trees: phs tree: TBP>>=
procedure :: from_array => phs_tree_from_array
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_from_array (tree, a)
class(phs_tree_t), intent(inout) :: tree
integer(TC), dimension(:), intent(in) :: a
end subroutine phs_tree_from_array
<<PHS trees: procedures>>=
module subroutine phs_tree_from_array (tree, a)
class(phs_tree_t), intent(inout) :: tree
integer(TC), dimension(:), intent(in) :: a
integer :: i
integer(TC) :: k
<<Set branches from array [[a]]>>
<<Set external branches if necessary>>
<<Check number of branches>>
<<Determine the connections>>
contains
<<Subroutine: set relatives>>
end subroutine phs_tree_from_array
@ %def phs_tree_from_array
@ First, set all branches specified by the user. If all IN-bits
are set, we invert the branch code.
<<Set branches from array [[a]]>>=
do i=1, size(a)
k = a(i)
if (iand(k, tree%mask_in) == tree%mask_in) k = ieor(tree%mask, k)
tree%branch(k)%set = .true.
tree%n_branches = tree%n_branches+1
end do
@ The external branches are understood, so set them now if not yet
done. In all cases ensure that the representative with one bit set is
used, except for decays where the in-particle is represented by all
OUT-bits set instead.
<<Set external branches if necessary>>=
do i=0, tree%n_externals-1
k = ibset(0,i)
if (iand(k, tree%mask_in) == tree%mask_in) k = ieor(tree%mask, k)
if (tree%branch(ieor(tree%mask, k))%set) then
tree%branch(ieor(tree%mask, k))%set = .false.
tree%branch(k)%set = .true.
else if (.not.tree%branch(k)%set) then
tree%branch(k)%set = .true.
tree%n_branches = tree%n_branches+1
end if
end do
@ Now the number of branches set can be checked. Here we assume that
the tree is binary. For three externals there are three branches in
total, and for each additional external branch we get another internal
one.
<<Check number of branches>>=
if (tree%n_branches /= tree%n_externals*2-3) then
call phs_tree_write (tree)
call msg_bug &
& (" Wrong number of branches set in phase space tree")
end if
@ For all branches that are set, except for the externals, we try to
find the daughter branches:
<<Determine the connections>>=
do k=1, size (tree%branch)
if (tree%branch(k)%set .and. tc_decay_level (k) /= 1) then
call branch_set_relatives(k)
end if
end do
@ To this end, we scan all codes less than the current code, whether
we can find two branches which are set and which together give the
current code. After that, the tree may still not be connected, but at
least we know if a branch does not have daughters: This indicates some
inconsistency.
The algorithm ensures that, at this stage, the first daughter has a
smaller code value than the second one.
<<Subroutine: set relatives>>=
subroutine branch_set_relatives (k)
integer(TC), intent(in) :: k
integer(TC) :: m,n
do m=1, k-1
if (iand(k,m)==m) then
n = ieor(k,m)
if ( tree%branch(m)%set .and. tree%branch(n)%set ) then
tree%branch(k)%daughter(1) = m; tree%branch(k)%daughter(2) = n
tree%branch(m)%mother = k; tree%branch(n)%mother = k
tree%branch(m)%sibling = n; tree%branch(n)%sibling = m
tree%branch(k)%has_children = .true.
return
end if
end if
end do
call phs_tree_write (tree)
call msg_bug &
& (" Missing daughter branch(es) in phase space tree")
end subroutine branch_set_relatives
@ The inverse: this is trivial, fortunately.
@
\subsubsection{Flip $t$-channel into $s$-channel}
Flipping the tree is done upwards, beginning from the decay products.
First we select a $t$-channel branch [[k]]: one which is set, which
does have an IN-bit, and which is not an external particle.
Next, we determine the adjacent in-particle (called the 'friend' [[f]]
here, since it will provide the reference axis for the angular
integration). In addition, we look for the 'mother' and 'sibling' of
this particle. If the latter field is empty, we select the (unique)
other out-particle which has no mother, calling the internal
subroutine [[find_orphan]].
The flip is done as follows: We assume that the first daughter [[d]]
is an $s$-channel line, which is true if the daughters are sorted.
This will stay the first daughter. The second one is a $t$-channel
line; it is exchanged with the 'sibling' [[s]]. The new line which
replaces the branch [[k]] is just the sum of [[s]] and [[d]]. In
addition, we have to rearrange the relatives of [[s]] and [[d]], as
well of [[f]].
Finally, we flip 'sibling' and 'friend' and set the new $s$-channel
branch [[n]] which replaces the $t$-channel branch [[k]]. After this
is complete, we are ready to execute another flip.
[Although the friend is not needed for the final flip, since it would
be an initial particle anyway, we need to know whether we have $t$- or
$u$-channel.]
<<PHS trees: phs tree: TBP>>=
procedure :: flip_t_to_s_channel => phs_tree_flip_t_to_s_channel
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_flip_t_to_s_channel (tree)
class(phs_tree_t), intent(inout) :: tree
end subroutine phs_tree_flip_t_to_s_channel
<<PHS trees: procedures>>=
module subroutine phs_tree_flip_t_to_s_channel (tree)
class(phs_tree_t), intent(inout) :: tree
integer(TC) :: k, f, m, n, d, s
if (tree%n_in == 2) then
FLIP: do k=3, tree%mask-1
if (.not. tree%branch(k)%set) cycle FLIP
f = iand(k,tree%mask_in)
if (f==0 .or. f==k) cycle FLIP
m = tree%branch(k)%mother
s = tree%branch(k)%sibling
if (s==0) call find_orphan(s)
d = tree%branch(k)%daughter(1)
n = ior(d,s)
tree%branch(k)%set = .false.
tree%branch(n)%set = .true.
tree%branch(n)%origin = k
tree%branch(n)%daughter(1) = d; tree%branch(d)%mother = n
tree%branch(n)%daughter(2) = s; tree%branch(s)%mother = n
tree%branch(n)%has_children = .true.
tree%branch(d)%sibling = s; tree%branch(s)%sibling = d
tree%branch(n)%sibling = f; tree%branch(f)%sibling = n
tree%branch(n)%mother = m
tree%branch(f)%mother = m
if (m/=0) then
tree%branch(m)%daughter(1) = n
tree%branch(m)%daughter(2) = f
end if
tree%branch(n)%friend = f
tree%branch(n)%has_friend = .true.
tree%branch(n)%firstborn = 2
end do FLIP
end if
contains
subroutine find_orphan(s)
integer(TC) :: s
do s=1, tree%mask_out
if (tree%branch(s)%set .and. tree%branch(s)%mother==0) return
end do
call phs_tree_write (tree)
call msg_bug (" Can't flip phase space tree to channel")
end subroutine find_orphan
end subroutine phs_tree_flip_t_to_s_channel
@ %def phs_tree_flip_t_to_s_channel
@ After the tree has been flipped, one may need to determine what has
become of a particular $t$-channel branch. This function gives the
bincode of the flipped tree. If the original bincode does not contain
IN-bits, we leave it as it is.
<<PHS trees: procedures>>=
function tc_flipped (tree, kt) result (ks)
type(phs_tree_t), intent(in) :: tree
integer(TC), intent(in) :: kt
integer(TC) :: ks
if (iand (kt, tree%mask_in) == 0) then
ks = kt
else
ks = tree%branch(iand (kt, tree%mask_out))%mother
end if
end function tc_flipped
@ %def tc_flipped
@ Scan a tree and make sure that the first daughter has always a
smaller code than the second one. Furthermore, delete any [[friend]]
entry in the root branch -- this branching has the incoming particle
direction as axis anyway. Keep track of reordering by updating
[[inverted_axis]], [[inverted_decay]] and [[firstborn]].
<<PHS trees: phs tree: TBP>>=
procedure :: canonicalize => phs_tree_canonicalize
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_canonicalize (tree)
class(phs_tree_t), intent(inout) :: tree
end subroutine phs_tree_canonicalize
<<PHS trees: procedures>>=
module subroutine phs_tree_canonicalize (tree)
class(phs_tree_t), intent(inout) :: tree
integer :: n_out
integer(TC) :: k_out
call branch_canonicalize (tree%branch(tree%mask_out))
n_out = tree%n_externals - tree%n_in
k_out = tree%mask_out
if (tree%branch(k_out)%has_friend &
& .and. tree%branch(k_out)%friend == ibset (0, n_out)) then
tree%branch(k_out)%inverted_axis = .not.tree%branch(k_out)%inverted_axis
end if
tree%branch(k_out)%has_friend = .false.
tree%branch(k_out)%friend = 0
contains
recursive subroutine branch_canonicalize (b)
type(phs_branch_t), intent(inout) :: b
integer(TC) :: d1, d2
if (b%has_children) then
d1 = b%daughter(1)
d2 = b%daughter(2)
if (d1 > d2) then
b%daughter(1) = d2
b%daughter(2) = d1
b%inverted_decay = .not.b%inverted_decay
if (b%firstborn /= 0) b%firstborn = 3 - b%firstborn
end if
call branch_canonicalize (tree%branch(b%daughter(1)))
call branch_canonicalize (tree%branch(b%daughter(2)))
end if
end subroutine branch_canonicalize
end subroutine phs_tree_canonicalize
@ %def phs_tree_canonicalize
@
\subsubsection{Mappings}
Initialize a mapping for the current tree. This is done while reading
from file, so the mapping parameters are read, but applied to the
flipped tree. Thus, the size of the array of mappings is given by the
number of outgoing particles only.
<<PHS trees: phs tree: TBP>>=
procedure :: init_mapping => phs_tree_init_mapping
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_init_mapping (tree, k, type, pdg, model)
class(phs_tree_t), intent(inout) :: tree
integer(TC), intent(in) :: k
type(string_t), intent(in) :: type
integer, intent(in) :: pdg
class(model_data_t), intent(in), target :: model
end subroutine phs_tree_init_mapping
<<PHS trees: procedures>>=
module subroutine phs_tree_init_mapping (tree, k, type, pdg, model)
class(phs_tree_t), intent(inout) :: tree
integer(TC), intent(in) :: k
type(string_t), intent(in) :: type
integer, intent(in) :: pdg
class(model_data_t), intent(in), target :: model
integer(TC) :: kk
kk = tc_flipped (tree, k)
call tree%mapping(kk)%init (kk, type, pdg, model)
end subroutine phs_tree_init_mapping
@ %def phs_tree_init_mapping
@ Set the physical parameters for the mapping, using a specific
parameter set. Also set the mass sum array.
<<PHS trees: phs tree: TBP>>=
procedure :: set_mapping_parameters => phs_tree_set_mapping_parameters
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_set_mapping_parameters &
(tree, mapping_defaults, variable_limits)
class(phs_tree_t), intent(inout) :: tree
type(mapping_defaults_t), intent(in) :: mapping_defaults
logical, intent(in) :: variable_limits
end subroutine phs_tree_set_mapping_parameters
<<PHS trees: procedures>>=
module subroutine phs_tree_set_mapping_parameters &
(tree, mapping_defaults, variable_limits)
class(phs_tree_t), intent(inout) :: tree
type(mapping_defaults_t), intent(in) :: mapping_defaults
logical, intent(in) :: variable_limits
integer(TC) :: k
do k = 1, tree%n_branches_out
call tree%mapping(k)%set_parameters (mapping_defaults, variable_limits)
end do
end subroutine phs_tree_set_mapping_parameters
@ %def phs_tree_set_mapping_parameters
@ Return the mapping for the sum of all outgoing particles. This
should either be no mapping or a global s-channel mapping.
<<PHS trees: phs tree: TBP>>=
procedure :: assign_s_mapping => phs_tree_assign_s_mapping
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_assign_s_mapping (tree, mapping)
class(phs_tree_t), intent(in) :: tree
type(mapping_t), intent(out) :: mapping
end subroutine phs_tree_assign_s_mapping
<<PHS trees: procedures>>=
module subroutine phs_tree_assign_s_mapping (tree, mapping)
class(phs_tree_t), intent(in) :: tree
type(mapping_t), intent(out) :: mapping
mapping = tree%mapping(tree%mask_out)
end subroutine phs_tree_assign_s_mapping
@ %def phs_tree_assign_s_mapping
@
\subsubsection{Kinematics}
Fill the mass sum array, starting from the external particles and
working down to the tree root. For each bincode [[k]] we scan the
bits in [[k]]; if only one is set, we take the physical mass of the
corresponding external particle; if more then one is set, we sum up
the two masses (which we know have already been set).
<<PHS trees: phs tree: TBP>>=
procedure :: set_mass_sum => phs_tree_set_mass_sum
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_set_mass_sum (tree, flv)
class(phs_tree_t), intent(inout) :: tree
type(flavor_t), dimension(:), intent(in) :: flv
end subroutine phs_tree_set_mass_sum
<<PHS trees: procedures>>=
module subroutine phs_tree_set_mass_sum (tree, flv)
class(phs_tree_t), intent(inout) :: tree
type(flavor_t), dimension(:), intent(in) :: flv
integer(TC) :: k
integer :: i
tree%mass_sum = 0
do k = 1, tree%n_branches_out
do i = 0, size (flv) - 1
if (btest(k,i)) then
if (ibclr(k,i) == 0) then
tree%mass_sum(k) = flv(i+1)%get_mass ()
else
tree%mass_sum(k) = &
tree%mass_sum(ibclr(k,i)) + tree%mass_sum(ibset(0,i))
end if
exit
end if
end do
end do
end subroutine phs_tree_set_mass_sum
@ %def phs_tree_set_mass_sum
@ Set the effective masses and widths. For each non-resonant branch
in a tree, the effective mass is equal to the sum of the effective
masses of the children (and analogous for the width). External
particles have their real mass and width zero. For resonant branches,
we insert mass and width from the corresponding mapping.
This routine has [[phs_tree_set_mass_sum]] and
[[phs_tree_set_mapping_parameters]] as prerequisites.
<<PHS trees: phs tree: TBP>>=
procedure :: set_effective_masses => phs_tree_set_effective_masses
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_set_effective_masses (tree)
class(phs_tree_t), intent(inout) :: tree
end subroutine phs_tree_set_effective_masses
<<PHS trees: procedures>>=
module subroutine phs_tree_set_effective_masses (tree)
class(phs_tree_t), intent(inout) :: tree
tree%effective_mass = 0
tree%effective_width = 0
call set_masses_x (tree%mask_out)
contains
recursive subroutine set_masses_x (k)
integer(TC), intent(in) :: k
integer(TC) :: k1, k2
if (tree%branch(k)%has_children) then
k1 = tree%branch(k)%daughter(1)
k2 = tree%branch(k)%daughter(2)
call set_masses_x (k1)
call set_masses_x (k2)
if (tree%mapping(k)%is_s_channel ()) then
tree%effective_mass(k) = tree%mapping(k)%get_mass ()
tree%effective_width(k) = tree%mapping(k)%get_width ()
else
tree%effective_mass(k) = &
tree%effective_mass(k1) + tree%effective_mass(k2)
tree%effective_width(k) = &
tree%effective_width(k1) + tree%effective_width(k2)
end if
else
tree%effective_mass(k) = tree%mass_sum(k)
end if
end subroutine set_masses_x
end subroutine phs_tree_set_effective_masses
@ %def phs_tree_set_effective_masses
@ Define step mappings, recursively, for the decay products of all
intermediate resonances. Step mappings account for the fact that a
branch may originate from a resonance, which almost replaces the
upper limit on the possible invariant mass. The step
mapping implements a smooth cutoff that interpolates between the
resonance and the real kinematic limit. The mapping width determines
the sharpness of the cutoff.
Step mappings are inserted only for branches that are not mapped
otherwise.
At each branch, we record the mass that is effectively available for
phase space, by taking the previous limit and subtracting the
effective mass of the sibling branch. Widths are added, not subtracted.
If we encounter a resonance decay, we discard the previous limit and
replace it by the mass and width of the resonance, also subtracting
the sibling branch.
Initially, the limit is zero, so it becomes negative at any branch. Only
if there is a resonance, the limit becomes positive. Whenever the
limit is positive, and the current branch decays, we activate a step
mapping for the current branch.
As a result, step mappings are implemented for all internal lines that
originate from an intermediate resonance decay.
The flag [[variable_limits]] applies to the ultimate limit from the
available energy, not to the intermediate resonances whose masses are
always fixed.
This routine requires [[phs_tree_set_effective_masses]]
<<PHS trees: phs tree: TBP>>=
procedure :: set_step_mappings => phs_tree_set_step_mappings
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_set_step_mappings &
(tree, exp_type, variable_limits)
class(phs_tree_t), intent(inout) :: tree
logical, intent(in) :: exp_type
logical, intent(in) :: variable_limits
end subroutine phs_tree_set_step_mappings
<<PHS trees: procedures>>=
module subroutine phs_tree_set_step_mappings &
(tree, exp_type, variable_limits)
class(phs_tree_t), intent(inout) :: tree
logical, intent(in) :: exp_type
logical, intent(in) :: variable_limits
type(string_t) :: map_str
integer(TC) :: k
if (exp_type) then
map_str = "step_exp"
else
map_str = "step_hyp"
end if
k = tree%mask_out
call set_step_mappings_x (k, 0._default, 0._default)
contains
recursive subroutine set_step_mappings_x (k, m_limit, w_limit)
integer(TC), intent(in) :: k
real(default), intent(in) :: m_limit, w_limit
integer(TC), dimension(2) :: kk
real(default), dimension(2) :: m, w
if (tree%branch(k)%has_children) then
if (m_limit > 0) then
if (.not. tree%mapping(k)%is_set ()) then
call tree%mapping(k)%init (k, map_str)
call tree%mapping(k)%set_step_mapping_parameters (m_limit, &
w_limit, variable_limits)
end if
end if
kk = tree%branch(k)%daughter
m = tree%effective_mass(kk)
w = tree%effective_width(kk)
if (tree%mapping(k)%is_s_channel ()) then
call set_step_mappings_x (kk(1), &
tree%mapping(k)%get_mass () - m(2), &
tree%mapping(k)%get_width () + w(2))
call set_step_mappings_x (kk(2), &
tree%mapping(k)%get_mass () - m(1), &
tree%mapping(k)%get_width () + w(1))
else if (m_limit > 0) then
call set_step_mappings_x (kk(1), &
m_limit - m(2), &
w_limit + w(2))
call set_step_mappings_x (kk(2), &
m_limit - m(1), &
w_limit + w(1))
else
call set_step_mappings_x (kk(1), &
- m(2), &
+ w(2))
call set_step_mappings_x (kk(2), &
- m(1), &
+ w(1))
end if
end if
end subroutine set_step_mappings_x
end subroutine phs_tree_set_step_mappings
@ %def phs_tree_set_step_mappings
@
\subsubsection{Resonance structure}
We identify the resonances within a tree as the set of s-channel
mappings. The [[resonance_history_t]] type serves as the result
container.
<<PHS trees: phs tree: TBP>>=
procedure :: extract_resonance_history => phs_tree_extract_resonance_history
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_extract_resonance_history (tree, res_history)
class(phs_tree_t), intent(in) :: tree
type(resonance_history_t), intent(out) :: res_history
end subroutine phs_tree_extract_resonance_history
<<PHS trees: procedures>>=
module subroutine phs_tree_extract_resonance_history (tree, res_history)
class(phs_tree_t), intent(in) :: tree
type(resonance_history_t), intent(out) :: res_history
type(resonance_info_t) :: res_info
integer :: i
if (allocated (tree%mapping)) then
do i = 1, size (tree%mapping)
associate (mapping => tree%mapping(i))
if (mapping%is_s_channel ()) then
call res_info%init (mapping%get_bincode (), mapping%get_flv (), &
n_out = tree%n_externals - tree%n_in)
call res_history%add_resonance (res_info)
end if
end associate
end do
end if
end subroutine phs_tree_extract_resonance_history
@ %def phs_tree_extract_resonance_history
@
\subsubsection{Structural comparison}
This function allows to check whether one tree is the permutation of
another one. The permutation is applied to the second tree in the
argument list. We do not make up a temporary permuted tree, but
compare the two trees directly. The branches are scanned recursively,
where for each daughter we check the friend and the mapping as well.
Once a discrepancy is found, the recursion is exited immediately.
<<PHS trees: public>>=
public :: phs_tree_equivalent
<<PHS trees: sub interfaces>>=
module function phs_tree_equivalent (t1, t2, perm) result (is_equal)
type(phs_tree_t), intent(in) :: t1, t2
type(permutation_t), intent(in) :: perm
logical :: equal, is_equal
end function phs_tree_equivalent
<<PHS trees: procedures>>=
module function phs_tree_equivalent (t1, t2, perm) result (is_equal)
type(phs_tree_t), intent(in) :: t1, t2
type(permutation_t), intent(in) :: perm
logical :: equal, is_equal
integer(TC) :: k1, k2, mask_in
k1 = t1%mask_out
k2 = t2%mask_out
mask_in = t1%mask_in
equal = .true.
call check (t1%branch(k1), t2%branch(k2), k1, k2)
is_equal = equal
contains
recursive subroutine check (b1, b2, k1, k2)
type(phs_branch_t), intent(in) :: b1, b2
integer(TC), intent(in) :: k1, k2
integer(TC), dimension(2) :: d1, d2, pd2
integer :: i
if (.not.b1%has_friend .and. .not.b2%has_friend) then
equal = .true.
else if (b1%has_friend .and. b2%has_friend) then
equal = (b1%friend == tc_permute (b2%friend, perm, mask_in))
end if
if (equal) then
if (b1%has_children .and. b2%has_children) then
d1 = b1%daughter
d2 = b2%daughter
do i=1, 2
pd2(i) = tc_permute (d2(i), perm, mask_in)
end do
if (d1(1)==pd2(1) .and. d1(2)==pd2(2)) then
equal = (b1%firstborn == b2%firstborn)
if (equal) call check &
& (t1%branch(d1(1)), t2%branch(d2(1)), d1(1), d2(1))
if (equal) call check &
& (t1%branch(d1(2)), t2%branch(d2(2)), d1(2), d2(2))
else if (d1(1)==pd2(2) .and. d1(2)==pd2(1)) then
equal = ( (b1%firstborn == 0 .and. b2%firstborn == 0) &
& .or. (b1%firstborn == 3 - b2%firstborn) )
if (equal) call check &
& (t1%branch(d1(1)), t2%branch(d2(2)), d1(1), d2(2))
if (equal) call check &
& (t1%branch(d1(2)), t2%branch(d2(1)), d1(2), d2(1))
else
equal = .false.
end if
end if
end if
if (equal) then
equal = (t1%mapping(k1) == t2%mapping(k2))
end if
end subroutine check
end function phs_tree_equivalent
@ %def phs_tree_equivalent
@ Scan two decay trees and determine the correspondence of mass
variables, i.e., the permutation that transfers the ordered list of
mass variables belonging to the second tree into the first one. Mass
variables are assigned beginning from branches and ending at the root.
<<PHS trees: public>>=
public :: phs_tree_find_msq_permutation
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_find_msq_permutation &
(tree1, tree2, perm2, msq_perm)
type(phs_tree_t), intent(in) :: tree1, tree2
type(permutation_t), intent(in) :: perm2
type(permutation_t), intent(out) :: msq_perm
end subroutine phs_tree_find_msq_permutation
<<PHS trees: procedures>>=
module subroutine phs_tree_find_msq_permutation &
(tree1, tree2, perm2, msq_perm)
type(phs_tree_t), intent(in) :: tree1, tree2
type(permutation_t), intent(in) :: perm2
type(permutation_t), intent(out) :: msq_perm
type(permutation_t) :: perm1
integer(TC) :: mask_in, root
integer(TC), dimension(:), allocatable :: index1, index2
integer :: i
allocate (index1 (tree1%n_msq), index2 (tree2%n_msq))
call permutation_init (perm1, permutation_size (perm2))
mask_in = tree1%mask_in
root = tree1%mask_out
i = 0
call tree_scan (tree1, root, perm1, index1)
i = 0
call tree_scan (tree2, root, perm2, index2)
call permutation_find (msq_perm, index1, index2)
contains
recursive subroutine tree_scan (tree, k, perm, index)
type(phs_tree_t), intent(in) :: tree
integer(TC), intent(in) :: k
type(permutation_t), intent(in) :: perm
integer, dimension(:), intent(inout) :: index
if (tree%branch(k)%has_children) then
call tree_scan (tree, tree%branch(k)%daughter(1), perm, index)
call tree_scan (tree, tree%branch(k)%daughter(2), perm, index)
i = i + 1
if (i <= size (index)) index(i) = tc_permute (k, perm, mask_in)
end if
end subroutine tree_scan
end subroutine phs_tree_find_msq_permutation
@ %def phs_tree_find_msq_permutation
<<PHS trees: public>>=
public :: phs_tree_find_angle_permutation
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_find_angle_permutation &
(tree1, tree2, perm2, angle_perm, sig2)
type(phs_tree_t), intent(in) :: tree1, tree2
type(permutation_t), intent(in) :: perm2
type(permutation_t), intent(out) :: angle_perm
logical, dimension(:), allocatable, intent(out) :: sig2
end subroutine phs_tree_find_angle_permutation
<<PHS trees: procedures>>=
module subroutine phs_tree_find_angle_permutation &
(tree1, tree2, perm2, angle_perm, sig2)
type(phs_tree_t), intent(in) :: tree1, tree2
type(permutation_t), intent(in) :: perm2
type(permutation_t), intent(out) :: angle_perm
logical, dimension(:), allocatable, intent(out) :: sig2
type(permutation_t) :: perm1
integer(TC) :: mask_in, root
integer(TC), dimension(:), allocatable :: index1, index2
logical, dimension(:), allocatable :: sig1
integer :: i
allocate (index1 (tree1%n_angles), index2 (tree2%n_angles))
allocate (sig1 (tree1%n_angles), sig2 (tree2%n_angles))
call permutation_init (perm1, permutation_size (perm2))
mask_in = tree1%mask_in
root = tree1%mask_out
i = 0
call tree_scan (tree1, root, perm1, index1, sig1)
i = 0
call tree_scan (tree2, root, perm2, index2, sig2)
call permutation_find (angle_perm, index1, index2)
contains
recursive subroutine tree_scan (tree, k, perm, index, sig)
type(phs_tree_t), intent(in) :: tree
integer(TC), intent(in) :: k
type(permutation_t), intent(in) :: perm
integer, dimension(:), intent(inout) :: index
logical, dimension(:), intent(inout) :: sig
integer(TC) :: k1, k2, kp
logical :: s
if (tree%branch(k)%has_children) then
k1 = tree%branch(k)%daughter(1)
k2 = tree%branch(k)%daughter(2)
s = (tc_permute(k1, perm, mask_in) < tc_permute(k2, perm, mask_in))
kp = tc_permute (k, perm, mask_in)
i = i + 1
index(i) = kp
sig(i) = s
i = i + 1
index(i) = - kp
sig(i) = s
call tree_scan (tree, k1, perm, index, sig)
call tree_scan (tree, k2, perm, index, sig)
end if
end subroutine tree_scan
end subroutine phs_tree_find_angle_permutation
@ %def phs_tree_find_angle_permutation
@
\subsection{Phase-space evaluation}
\subsubsection{Phase-space volume}
We compute the phase-space volume recursively, following the same path
as for computing other kinematical variables. However, the volume
depends just on $\sqrt{\hat s}$, not on the momentum configuration.
Note: counting branches, we may replace this by a simple formula.
<<PHS trees: phs tree: TBP>>=
procedure :: compute_volume => phs_tree_compute_volume
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_compute_volume (tree, sqrts, volume)
class(phs_tree_t), intent(in) :: tree
real(default), intent(in) :: sqrts
real(default), intent(out) :: volume
end subroutine phs_tree_compute_volume
<<PHS trees: procedures>>=
module subroutine phs_tree_compute_volume (tree, sqrts, volume)
class(phs_tree_t), intent(in) :: tree
real(default), intent(in) :: sqrts
real(default), intent(out) :: volume
integer(TC) :: k
k = tree%mask_out
if (tree%branch(k)%has_children) then
call compute_volume_x (tree%branch(k), k, volume, .true.)
else
volume = 1
end if
contains
recursive subroutine compute_volume_x (b, k, volume, initial)
type(phs_branch_t), intent(in) :: b
integer(TC), intent(in) :: k
real(default), intent(out) :: volume
logical, intent(in) :: initial
integer(TC) :: k1, k2
real(default) :: v1, v2
k1 = b%daughter(1); k2 = b%daughter(2)
if (tree%branch(k1)%has_children) then
call compute_volume_x (tree%branch(k1), k1, v1, .false.)
else
v1 = 1
end if
if (tree%branch(k2)%has_children) then
call compute_volume_x (tree%branch(k2), k2, v2, .false.)
else
v2 = 1
end if
if (initial) then
volume = v1 * v2 / (4 * twopi5)
else
volume = v1 * v2 * sqrts**2 / (4 * twopi2)
end if
end subroutine compute_volume_x
end subroutine phs_tree_compute_volume
@ %def phs_tree_compute_volume
@
\subsubsection{Determine momenta}
This is done in two steps: First the masses are determined. This step
may fail, in which case [[ok]] is set to false. If successful, we
generate angles and the actual momenta. The array [[decay_p]] serves
for transferring the individual three-momenta of the daughter
particles in their mother rest frame from the mass generation to the
momentum generation step.
<<PHS trees: phs tree: TBP>>=
procedure :: compute_momenta_from_x => phs_tree_compute_momenta_from_x
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_compute_momenta_from_x &
(tree, prt, factor, volume, sqrts, x, ok)
class(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(inout) :: prt
real(default), intent(out) :: factor, volume
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(in) :: x
logical, intent(out) :: ok
end subroutine phs_tree_compute_momenta_from_x
<<PHS trees: procedures>>=
module subroutine phs_tree_compute_momenta_from_x &
(tree, prt, factor, volume, sqrts, x, ok)
class(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(inout) :: prt
real(default), intent(out) :: factor, volume
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(in) :: x
logical, intent(out) :: ok
real(default), dimension(tree%mask_out) :: decay_p
integer :: n1, n2
integer :: n_out
if (tree%real_phsp) then
n_out = tree%n_externals - tree%n_in - 1
n1 = max (n_out-2, 0)
n2 = n1 + max (2*n_out, 0)
else
n1 = tree%n_msq
n2 = n1 + tree%n_angles
end if
call phs_tree_set_msq &
(tree, prt, factor, volume, decay_p, sqrts, x(1:n1), ok)
if (ok) call phs_tree_set_angles &
(tree, prt, factor, decay_p, sqrts, x(n1+1:n2))
end subroutine phs_tree_compute_momenta_from_x
@ %def phs_tree_compute_momenta_from_x
@ Mass generation is done recursively. The [[ok]] flag causes the
filled tree to be discarded if set to [[.false.]]. This happens if a
three-momentum turns out to be imaginary, indicating impossible
kinematics. The index [[ix]] tells us how far we have used up the
input array [[x]].
<<PHS trees: procedures>>=
subroutine phs_tree_set_msq &
(tree, prt, factor, volume, decay_p, sqrts, x, ok)
type(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(inout) :: prt
real(default), intent(out) :: factor, volume
real(default), dimension(:), intent(out) :: decay_p
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(in) :: x
logical, intent(out) :: ok
integer :: ix
integer(TC) :: k
real(default) :: m_tot
ok =.true.
ix = 1
k = tree%mask_out
m_tot = tree%mass_sum(k)
decay_p(k) = 0.
if (m_tot < sqrts .or. k == 1) then
if (tree%branch(k)%has_children) then
call set_msq_x (tree%branch(k), k, factor, volume, .true.)
else
factor = 1
volume = 1
end if
else
ok = .false.
end if
contains
recursive subroutine set_msq_x (b, k, factor, volume, initial)
type(phs_branch_t), intent(in) :: b
integer(TC), intent(in) :: k
real(default), intent(out) :: factor, volume
logical, intent(in) :: initial
real(default) :: msq, m, m_min, m_max, m1, m2, msq1, msq2, lda, rlda
integer(TC) :: k1, k2
real(default) :: f1, f2, v1, v2
k1 = b%daughter(1); k2 = b%daughter(2)
if (tree%branch(k1)%has_children) then
call set_msq_x (tree%branch(k1), k1, f1, v1, .false.)
if (.not.ok) return
else
f1 = 1; v1 = 1
end if
if (tree%branch(k2)%has_children) then
call set_msq_x (tree%branch(k2), k2, f2, v2, .false.)
if (.not.ok) return
else
f2 = 1; v2 = 1
end if
m_min = tree%mass_sum(k)
if (initial) then
msq = sqrts**2
m = sqrts
m_max = sqrts
factor = f1 * f2
volume = v1 * v2 / (4 * twopi5)
else
m_max = sqrts - m_tot + m_min
call tree%mapping(k)%compute_msq_from_x (sqrts**2, m_min**2, &
m_max**2, msq, factor, x(ix)); ix = ix + 1
if (msq >= 0) then
m = sqrt (msq)
factor = f1 * f2 * factor
volume = v1 * v2 * sqrts**2 / (4 * twopi2)
call prt(k)%set_msq (msq)
call prt(k)%set_defined ()
else
ok = .false.
end if
end if
if (ok) then
msq1 = prt(k1)%get_msq (); m1 = sqrt (msq1)
msq2 = prt(k2)%get_msq (); m2 = sqrt (msq2)
lda = lambda (msq, msq1, msq2)
if (lda > 0 .and. m > m1 + m2 .and. m <= m_max) then
rlda = sqrt (lda)
decay_p(k1) = rlda / (2*m)
decay_p(k2) = - decay_p(k1)
factor = rlda / msq * factor
else
ok = .false.
end if
end if
end subroutine set_msq_x
end subroutine phs_tree_set_msq
@ %def phs_tree_set_msq
@
The heart of phase space generation: Now we have the invariant masses,
let us generate angles. At each branch, we take a Lorentz
transformation and augment it by a boost to the current particle
rest frame, and by rotations $\phi$ and $\theta$ around the $z$ and
$y$ axis, respectively. This transformation is passed down to the
daughter particles, if present.
<<PHS trees: procedures>>=
subroutine phs_tree_set_angles (tree, prt, factor, decay_p, sqrts, x)
type(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(inout) :: prt
real(default), intent(inout) :: factor
real(default), dimension(:), intent(in) :: decay_p
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(in) :: x
integer :: ix
integer(TC) :: k
ix = 1
k = tree%mask_out
call set_angles_x (tree%branch(k), k)
contains
recursive subroutine set_angles_x (b, k, L0)
type(phs_branch_t), intent(in) :: b
integer(TC), intent(in) :: k
type(lorentz_transformation_t), intent(in), optional :: L0
real(default) :: m, msq, ct, st, phi, f, E, p, bg
type(lorentz_transformation_t) :: L, LL
integer(TC) :: k1, k2
type(vector3_t) :: axis
p = decay_p(k)
msq = prt(k)%get_msq (); m = sqrt (msq)
E = sqrt (msq + p**2)
if (present (L0)) then
call prt(k)%set_momentum (L0 * vector4_moving (E,p,3))
else
call prt(k)%set_momentum (vector4_moving (E,p,3))
end if
call prt(k)%set_defined ()
if (b%has_children) then
k1 = b%daughter(1)
k2 = b%daughter(2)
if (m > 0) then
bg = p / m
else
bg = 0
end if
phi = x(ix) * twopi; ix = ix + 1
call tree%mapping(k)%compute_ct_from_x (sqrts**2, ct, st, f, &
x(ix)); ix = ix + 1
factor = factor * f
if (.not. b%has_friend) then
L = LT_compose_r2_r3_b3 (ct, st, cos(phi), sin(phi), bg)
!!! The function above is equivalent to:
! L = boost (bg,3) * rotation (phi,3) * rotation (ct,st,2)
else
LL = boost (-bg,3); if (present (L0)) LL = LL * inverse(L0)
axis = space_part ( &
LL * prt(tree%branch(k)%friend)%get_momentum () )
L = boost(bg,3) * rotation_to_2nd (vector3_canonical(3), axis) &
* LT_compose_r2_r3_b3 (ct, st, cos(phi), sin(phi), 0._default)
end if
if (present (L0)) L = L0 * L
call set_angles_x (tree%branch(k1), k1, L)
call set_angles_x (tree%branch(k2), k2, L)
end if
end subroutine set_angles_x
end subroutine phs_tree_set_angles
@ %def phs_tree_set_angles
@
\subsubsection{Recover random numbers}
For the other channels we want to compute the random numbers that
would have generated the momenta that we already know.
<<PHS trees: phs tree: TBP>>=
procedure :: compute_x_from_momenta => phs_tree_compute_x_from_momenta
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_compute_x_from_momenta &
(tree, prt, factor, sqrts, x)
class(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(in) :: prt
real(default), intent(out) :: factor
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(inout) :: x
end subroutine phs_tree_compute_x_from_momenta
<<PHS trees: procedures>>=
module subroutine phs_tree_compute_x_from_momenta &
(tree, prt, factor, sqrts, x)
class(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(in) :: prt
real(default), intent(out) :: factor
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(inout) :: x
real(default), dimension(tree%mask_out) :: decay_p
integer :: n1, n2
n1 = tree%n_msq
n2 = n1 + tree%n_angles
call phs_tree_get_msq &
(tree, prt, factor, decay_p, sqrts, x(1:n1))
call phs_tree_get_angles &
(tree, prt, factor, decay_p, sqrts, x(n1+1:n2))
end subroutine phs_tree_compute_x_from_momenta
@ %def phs_tree_compute_x_from_momenta
@ The inverse operation follows exactly the same steps. The tree is
[[inout]] because it contains mappings whose parameters can be reset
when the mapping is applied.
<<PHS trees: procedures>>=
subroutine phs_tree_get_msq (tree, prt, factor, decay_p, sqrts, x)
type(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(in) :: prt
real(default), intent(out) :: factor
real(default), dimension(:), intent(out) :: decay_p
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(inout) :: x
integer :: ix
integer(TC) :: k
real(default) :: m_tot
ix = 1
k = tree%mask_out
m_tot = tree%mass_sum(k)
decay_p(k) = 0.
if (tree%branch(k)%has_children) then
call get_msq_x (tree%branch(k), k, factor, .true.)
else
factor = 1
end if
contains
recursive subroutine get_msq_x (b, k, factor, initial)
type(phs_branch_t), intent(in) :: b
integer(TC), intent(in) :: k
real(default), intent(out) :: factor
logical, intent(in) :: initial
real(default) :: msq, m, m_min, m_max, msq1, msq2, lda, rlda
integer(TC) :: k1, k2
real(default) :: f1, f2
k1 = b%daughter(1); k2 = b%daughter(2)
if (tree%branch(k1)%has_children) then
call get_msq_x (tree%branch(k1), k1, f1, .false.)
else
f1 = 1
end if
if (tree%branch(k2)%has_children) then
call get_msq_x (tree%branch(k2), k2, f2, .false.)
else
f2 = 1
end if
m_min = tree%mass_sum(k)
m_max = sqrts - m_tot + m_min
msq = prt(k)%get_msq (); m = sqrt (msq)
if (initial) then
factor = f1 * f2
else
call tree%mapping(k)%compute_x_from_msq (sqrts**2, m_min**2, &
m_max**2, msq, factor, x(ix)); ix = ix + 1
factor = f1 * f2 * factor
end if
msq1 = prt(k1)%get_msq ()
msq2 = prt(k2)%get_msq ()
lda = lambda (msq, msq1, msq2)
if (lda > 0) then
rlda = sqrt (lda)
decay_p(k1) = rlda / (2 * m)
decay_p(k2) = - decay_p(k1)
factor = rlda / msq * factor
else
decay_p(k1) = 0
decay_p(k2) = 0
factor = 0
end if
end subroutine get_msq_x
end subroutine phs_tree_get_msq
@ %def phs_tree_get_msq
@ This subroutine is the most time-critical part of the whole
program. Therefore, we do not exactly parallel the angle generation
routine above but make sure that things get evaluated only if they are
really needed, at the expense of readability. Particularly important
is to have as few multiplications of Lorentz transformations as
possible.
<<PHS trees: procedures>>=
subroutine phs_tree_get_angles (tree, prt, factor, decay_p, sqrts, x)
type(phs_tree_t), intent(inout) :: tree
type(phs_prt_t), dimension(:), intent(in) :: prt
real(default), intent(inout) :: factor
real(default), dimension(:), intent(in) :: decay_p
real(default), intent(in) :: sqrts
real(default), dimension(:), intent(out) :: x
integer :: ix
integer(TC) :: k
ix = 1
k = tree%mask_out
if (tree%branch(k)%has_children) then
call get_angles_x (tree%branch(k), k)
end if
contains
recursive subroutine get_angles_x (b, k, ct0, st0, phi0, L0)
type(phs_branch_t), intent(in) :: b
integer(TC), intent(in) :: k
real(default), intent(in), optional :: ct0, st0, phi0
type(lorentz_transformation_t), intent(in), optional :: L0
real(default) :: cp0, sp0, m, msq, ct, st, phi, bg, f
type(lorentz_transformation_t) :: L, LL
type(vector4_t) :: p1, pf
type(vector3_t) :: n, axis
integer(TC) :: k1, k2, kf
logical :: has_friend, need_L
k1 = b%daughter(1)
k2 = b%daughter(2)
kf = b%friend
has_friend = b%has_friend
if (present(L0)) then
p1 = L0 * prt(k1)%get_momentum ()
if (has_friend) pf = L0 * prt(kf)%get_momentum ()
else
p1 = prt(k1)%get_momentum ()
if (has_friend) pf = prt(kf)%get_momentum ()
end if
if (present(phi0)) then
cp0 = cos (phi0)
sp0 = sin (phi0)
end if
msq = prt(k)%get_msq (); m = sqrt (msq)
if (m > 0) then
bg = decay_p(k) / m
else
bg = 0
end if
if (has_friend) then
if (present (phi0)) then
axis = axis_from_p_r3_r2_b3 (pf, cp0, -sp0, ct0, -st0, -bg)
LL = rotation_to_2nd (axis, vector3_canonical (3)) &
* LT_compose_r3_r2_b3 (cp0, -sp0, ct0, -st0, -bg)
else
axis = axis_from_p_b3 (pf, -bg)
LL = rotation_to_2nd (axis, vector3_canonical(3))
if (.not. vanishes (bg)) LL = LL * boost(-bg, 3)
end if
n = space_part (LL * p1)
else if (present (phi0)) then
n = axis_from_p_r3_r2_b3 (p1, cp0, -sp0, ct0, -st0, -bg)
else
n = axis_from_p_b3 (p1, -bg)
end if
phi = azimuthal_angle (n)
x(ix) = phi / twopi; ix = ix + 1
ct = polar_angle_ct (n)
st = sqrt (1 - ct**2)
call tree%mapping(k)%compute_x_from_ct (sqrts**2, ct, f, &
x(ix)); ix = ix + 1
factor = factor * f
if (tree%branch(k1)%has_children .or. tree%branch(k2)%has_children) then
need_L = .true.
if (has_friend) then
if (present (L0)) then
L = LL * L0
else
L = LL
end if
else if (present (L0)) then
L = LT_compose_r3_r2_b3 (cp0, -sp0, ct0, -st0, -bg) * L0
else if (present (phi0)) then
L = LT_compose_r3_r2_b3 (cp0, -sp0, ct0, -st0, -bg)
else if (bg /= 0) then
L = boost(-bg, 3)
else
need_L = .false.
end if
if (need_L) then
if (tree%branch(k1)%has_children) &
call get_angles_x (tree%branch(k1), k1, ct, st, phi, L)
if (tree%branch(k2)%has_children) &
call get_angles_x (tree%branch(k2), k2, ct, st, phi, L)
else
if (tree%branch(k1)%has_children) &
call get_angles_x (tree%branch(k1), k1, ct, st, phi)
if (tree%branch(k2)%has_children) &
call get_angles_x (tree%branch(k2), k2, ct, st, phi)
end if
end if
end subroutine get_angles_x
end subroutine phs_tree_get_angles
@ %def phs_tree_get_angles
@
\subsubsection{Auxiliary stuff}
This calculates all momenta that are not yet known by summing up
daughter particle momenta. The external particles must be known.
Only composite particles not yet known are calculated.
<<PHS trees: public>>=
public :: phs_tree_combine_particles
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_combine_particles (tree, prt)
type(phs_tree_t), intent(in) :: tree
type(phs_prt_t), dimension(:), intent(inout) :: prt
end subroutine phs_tree_combine_particles
<<PHS trees: procedures>>=
module subroutine phs_tree_combine_particles (tree, prt)
type(phs_tree_t), intent(in) :: tree
type(phs_prt_t), dimension(:), intent(inout) :: prt
call combine_particles_x (tree%mask_out)
contains
recursive subroutine combine_particles_x (k)
integer(TC), intent(in) :: k
integer :: k1, k2
if (tree%branch(k)%has_children) then
k1 = tree%branch(k)%daughter(1); k2 = tree%branch(k)%daughter(2)
call combine_particles_x (k1)
call combine_particles_x (k2)
if (.not. prt(k)%defined) then
call prt(k)%combine (prt(k1), prt(k2))
end if
end if
end subroutine combine_particles_x
end subroutine phs_tree_combine_particles
@ %def phs_tree_combine_particles
@ The previous routine is to be evaluated at runtime. Instead of
scanning trees, we can as well set up a multiplication table. This is
generated here. Note that the table is [[intent(out)]].
<<PHS trees: public>>=
public :: phs_tree_setup_prt_combinations
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_setup_prt_combinations (tree, comb)
type(phs_tree_t), intent(in) :: tree
integer, dimension(:,:), intent(out) :: comb
end subroutine phs_tree_setup_prt_combinations
<<PHS trees: procedures>>=
module subroutine phs_tree_setup_prt_combinations (tree, comb)
type(phs_tree_t), intent(in) :: tree
integer, dimension(:,:), intent(out) :: comb
comb = 0
call setup_prt_combinations_x (tree%mask_out)
contains
recursive subroutine setup_prt_combinations_x (k)
integer(TC), intent(in) :: k
integer, dimension(2) :: kk
if (tree%branch(k)%has_children) then
kk = tree%branch(k)%daughter
call setup_prt_combinations_x (kk(1))
call setup_prt_combinations_x (kk(2))
comb(:,k) = kk
end if
end subroutine setup_prt_combinations_x
end subroutine phs_tree_setup_prt_combinations
@ %def phs_tree_setup_prt_combinations
@ JRR: 2022-01-22 [[reshuffle_mappings]] is commented out, and no
longer used, not clear why?
<<PHS trees: phs tree: TBP>>=
procedure :: reshuffle_mappings => phs_tree_reshuffle_mappings
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_reshuffle_mappings (tree)
class(phs_tree_t), intent(inout) :: tree
end subroutine phs_tree_reshuffle_mappings
<<PHS trees: procedures>>=
module subroutine phs_tree_reshuffle_mappings (tree)
class(phs_tree_t), intent(inout) :: tree
integer(TC) :: k0, k_old, k_new, k2
integer :: i
type(mapping_t) :: mapping_tmp
real(default) :: mass_tmp
do i = 1, size (tree%momentum_link)
if (i /= tree%momentum_link (i)) then
k_old = 2**(i-tree%n_in-1)
k_new = 2**(tree%momentum_link(i)-tree%n_in-1)
k0 = tree%branch(k_old)%mother
k2 = k_new + tree%branch(k_old)%sibling
mapping_tmp = tree%mapping(k0)
mass_tmp = tree%mass_sum(k0)
tree%mapping(k0) = tree%mapping(k2)
tree%mapping(k2) = mapping_tmp
tree%mass_sum(k0) = tree%mass_sum(k2)
tree%mass_sum(k2) = mass_tmp
end if
end do
end subroutine phs_tree_reshuffle_mappings
@ %def phs_tree_reshuffle_mappings
@
<<PHS trees: public>>=
public :: phs_tree_set_momentum_links
<<PHS trees: sub interfaces>>=
module subroutine phs_tree_set_momentum_links (tree, list)
type(phs_tree_t), intent(inout) :: tree
integer, dimension(:), allocatable :: list
end subroutine phs_tree_set_momentum_links
<<PHS trees: procedures>>=
module subroutine phs_tree_set_momentum_links (tree, list)
type(phs_tree_t), intent(inout) :: tree
integer, dimension(:), allocatable :: list
tree%momentum_link = list
end subroutine phs_tree_set_momentum_links
@ %def phs_tree_set_momentum_links
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_trees_ut.f90]]>>=
<<File header>>
module phs_trees_ut
use unit_tests
use phs_trees_uti
<<Standard module head>>
<<PHS trees: public test>>
contains
<<PHS trees: test driver>>
end module phs_trees_ut
@ %def phs_trees_ut
@
<<[[phs_trees_uti.f90]]>>=
<<File header>>
module phs_trees_uti
!!!<<Use kinds>>
use kinds, only: TC
<<Use strings>>
use flavors, only: flavor_t
use model_data, only: model_data_t
use resonances, only: resonance_history_t
use mappings, only: mapping_defaults_t
use phs_trees
<<Standard module head>>
<<PHS trees: test declarations>>
contains
<<PHS trees: tests>>
end module phs_trees_uti
@ %def phs_trees_ut
@ API: driver for the unit tests below.
<<PHS trees: public test>>=
public :: phs_trees_test
<<PHS trees: test driver>>=
subroutine phs_trees_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS trees: execute tests>>
end subroutine phs_trees_test
@ %def phs_trees_test
@
Create a simple $2\to 3$ PHS tree and display it.
<<PHS trees: execute tests>>=
call test (phs_tree_1, "phs_tree_1", &
"check phs tree setup", &
u, results)
<<PHS trees: test declarations>>=
public :: phs_tree_1
<<PHS trees: tests>>=
subroutine phs_tree_1 (u)
integer, intent(in) :: u
type(phs_tree_t) :: tree
type(model_data_t), target :: model
type(flavor_t), dimension(5) :: flv
integer :: i
write (u, "(A)") "* Test output: phs_tree_1"
write (u, "(A)") "* Purpose: test PHS tree routines"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Set up flavors"
write (u, "(A)")
call flv%init ([1, -2, 24, 5, -5], model)
do i = 1, 5
write (u, "(1x)", advance="no")
call flv(i)%write (u)
end do
write (u, *)
write (u, "(A)")
write (u, "(A)") "* Create tree"
write (u, "(A)")
call tree%init (2, 3, 0, 0)
call tree%from_array ([integer(TC) :: 1, 2, 3, 4, 7, 8, 16])
call tree%set_mass_sum (flv)
call tree%set_effective_masses ()
call tree%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call tree%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_tree_1"
end subroutine phs_tree_1
@ %def phs_tree_1
@ The analogous tree with resonance (s-channel) mappings.
<<PHS trees: execute tests>>=
call test (phs_tree_2, "phs_tree_2", &
"check phs tree with resonances", &
u, results)
<<PHS trees: test declarations>>=
public :: phs_tree_2
<<PHS trees: tests>>=
subroutine phs_tree_2 (u)
integer, intent(in) :: u
type(phs_tree_t) :: tree
type(model_data_t), target :: model
type(mapping_defaults_t) :: mapping_defaults
type(flavor_t), dimension(5) :: flv
type(resonance_history_t) :: res_history
integer :: i
write (u, "(A)") "* Test output: phs_tree_2"
write (u, "(A)") "* Purpose: test PHS tree with resonances"
write (u, "(A)")
write (u, "(A)") "* Read model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Set up flavors"
write (u, "(A)")
call flv%init ([1, -2, 24, 5, -5], model)
do i = 1, 5
write (u, "(1x)", advance="no")
call flv(i)%write (u)
end do
write (u, *)
write (u, "(A)")
write (u, "(A)") "* Create tree with mappings"
write (u, "(A)")
call tree%init (2, 3, 0, 0)
call tree%from_array ([integer(TC) :: 1, 2, 3, 4, 7, 8, 16])
call tree%set_mass_sum (flv)
call tree%init_mapping (3_TC, var_str ("s_channel"), -24, model)
call tree%init_mapping (7_TC, var_str ("s_channel"), 23, model)
call tree%set_mapping_parameters (mapping_defaults, variable_limits=.false.)
call tree%set_effective_masses ()
call tree%write (u)
write (u, "(A)")
write (u, "(A)") "* Extract resonances from mappings"
write (u, "(A)")
call tree%extract_resonance_history (res_history)
call res_history%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call tree%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_tree_2"
end subroutine phs_tree_2
@ %def phs_tree_2
@
\clearpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{The phase-space forest}
Simply stated, a phase-space forest is a collection of phase-space
trees. More precisely, a [[phs_forest]] object contains all
parameterizations of phase space that \whizard\ will use for a single
hard process, prepared in the form of [[phs_tree]] objects. This is
suitable for evaluation by the \vamp\ integration package: each
parameterization (tree) is a valid channel in the multi-channel
adaptive integration, and each variable in a tree corresponds to an
integration dimension, defined by an appropriate mapping of the
$(0,1)$ interval to the allowed range of the integration variable.
The trees are grouped in groves. The trees (integration channels)
within a grove share a common weight, assuming that they are related
by some approximate symmetry.
Trees/channels that are related by an exact symmetry are connected by
an array of equivalences; each equivalence object holds the data that
relate one channel to another.
The phase-space setup, i.e., the detailed structure of trees and
forest, are read from a file. Therefore, this module also contains
the syntax definition and the parser needed for interpreting this
file.
<<[[phs_forests.f90]]>>=
<<File header>>
module phs_forests
<<Use kinds>>
use kinds, only: TC
<<Use strings>>
use lorentz
use permutations
use syntax_rules
use parser
use model_data
use flavors
use interactions
use phs_base
use resonances, only: resonance_history_t
use resonances, only: resonance_history_set_t
use mappings
use phs_trees
<<Standard module head>>
<<PHS forests: public>>
<<PHS forests: types>>
<<PHS forests: interfaces>>
<<PHS forests: variables>>
interface
<<PHS forests: sub interfaces>>
end interface
contains
<<PHS forests: main procedures>>
end module phs_forests
@ %def phs_forests
@
<<[[phs_forests_sub.f90]]>>=
<<File header>>
submodule (phs_forests) phs_forests_s
use io_units
use format_defs, only: FMT_19
use diagnostics
use numeric_utils
use ifiles
use lexers
!!! Intel oneAPI 2022/23 regression workaround
use resonances, only: resonance_history_t
implicit none
contains
<<PHS forests: procedures>>
end submodule phs_forests_s
@ %def phs_forests_s
@
\subsection{Phase-space setup parameters}
This transparent container holds the parameters that the algorithm
needs for phase-space setup, with reasonable defaults.
The threshold mass (for considering a particle as effectively
massless) is specified separately for s- and t-channel. The default is
to treat $W$ and $Z$ bosons as massive in the s-channel, but as
massless in the t-channel. The $b$-quark is treated always massless,
the $t$-quark always massive.
<<PHS forests: public>>=
public :: phs_parameters_t
<<PHS forests: types>>=
type :: phs_parameters_t
real(default) :: sqrts = 0
real(default) :: m_threshold_s = 50._default
real(default) :: m_threshold_t = 100._default
integer :: off_shell = 1
integer :: t_channel = 2
logical :: keep_nonresonant = .true.
contains
<<PHS forests: phs parameters: TBP>>
end type phs_parameters_t
@ %def phs_parameters_t
@ Write phase-space parameters to file.
<<PHS forests: phs parameters: TBP>>=
procedure :: write => phs_parameters_write
<<PHS forests: sub interfaces>>=
module subroutine phs_parameters_write (phs_par, unit)
class(phs_parameters_t), intent(in) :: phs_par
integer, intent(in), optional :: unit
end subroutine phs_parameters_write
<<PHS forests: procedures>>=
module subroutine phs_parameters_write (phs_par, unit)
class(phs_parameters_t), intent(in) :: phs_par
integer, intent(in), optional :: unit
integer :: u
u = given_output_unit (unit)
write (u, "(3x,A," // FMT_19 // ")") "sqrts = ", phs_par%sqrts
write (u, "(3x,A," // FMT_19 // ")") "m_threshold_s = ", phs_par%m_threshold_s
write (u, "(3x,A," // FMT_19 // ")") "m_threshold_t = ", phs_par%m_threshold_t
write (u, "(3x,A,I0)") "off_shell = ", phs_par%off_shell
write (u, "(3x,A,I0)") "t_channel = ", phs_par%t_channel
write (u, "(3x,A,L1)") "keep_nonresonant = ", phs_par%keep_nonresonant
end subroutine phs_parameters_write
@ %def phs_parameters_write
@ Read phase-space parameters from file.
<<PHS forests: phs parameters: TBP>>=
procedure :: read => phs_parameters_read
<<PHS forests: sub interfaces>>=
module subroutine phs_parameters_read (phs_par, unit)
class(phs_parameters_t), intent(out) :: phs_par
integer, intent(in) :: unit
end subroutine phs_parameters_read
<<PHS forests: procedures>>=
module subroutine phs_parameters_read (phs_par, unit)
class(phs_parameters_t), intent(out) :: phs_par
integer, intent(in) :: unit
character(20) :: dummy
character :: equals
read (unit, *) dummy, equals, phs_par%sqrts
read (unit, *) dummy, equals, phs_par%m_threshold_s
read (unit, *) dummy, equals, phs_par%m_threshold_t
read (unit, *) dummy, equals, phs_par%off_shell
read (unit, *) dummy, equals, phs_par%t_channel
read (unit, *) dummy, equals, phs_par%keep_nonresonant
end subroutine phs_parameters_read
@ %def phs_parameters_write
@ Comparison.
<<PHS forests: interfaces>>=
interface operator(==)
module procedure phs_parameters_eq
end interface
interface operator(/=)
module procedure phs_parameters_ne
end interface
<<PHS forests: sub interfaces>>=
module function phs_parameters_eq (phs_par1, phs_par2) result (equal)
logical :: equal
type(phs_parameters_t), intent(in) :: phs_par1, phs_par2
end function phs_parameters_eq
module function phs_parameters_ne (phs_par1, phs_par2) result (ne)
logical :: ne
type(phs_parameters_t), intent(in) :: phs_par1, phs_par2
end function phs_parameters_ne
<<PHS forests: procedures>>=
module function phs_parameters_eq (phs_par1, phs_par2) result (equal)
logical :: equal
type(phs_parameters_t), intent(in) :: phs_par1, phs_par2
equal = phs_par1%sqrts == phs_par2%sqrts &
.and. phs_par1%m_threshold_s == phs_par2%m_threshold_s &
.and. phs_par1%m_threshold_t == phs_par2%m_threshold_t &
.and. phs_par1%off_shell == phs_par2%off_shell &
.and. phs_par1%t_channel == phs_par2%t_channel &
.and.(phs_par1%keep_nonresonant .eqv. phs_par2%keep_nonresonant)
end function phs_parameters_eq
module function phs_parameters_ne (phs_par1, phs_par2) result (ne)
logical :: ne
type(phs_parameters_t), intent(in) :: phs_par1, phs_par2
ne = phs_par1%sqrts /= phs_par2%sqrts &
.or. phs_par1%m_threshold_s /= phs_par2%m_threshold_s &
.or. phs_par1%m_threshold_t /= phs_par2%m_threshold_t &
.or. phs_par1%off_shell /= phs_par2%off_shell &
.or. phs_par1%t_channel /= phs_par2%t_channel &
.or.(phs_par1%keep_nonresonant .neqv. phs_par2%keep_nonresonant)
end function phs_parameters_ne
@ %def phs_parameters_eq phs_parameters_ne
@
\subsection{Equivalences}
This type holds information about equivalences between phase-space
trees. We make a linked list, where each node contains the two
trees which are equivalent and the corresponding permutation of
external particles. Two more arrays are to be filled: The permutation
of mass variables and the permutation of angular variables, where the
signature indicates a necessary exchange of daughter branches.
<<PHS forests: types>>=
type :: equivalence_t
private
integer :: left, right
type(permutation_t) :: perm
type(permutation_t) :: msq_perm, angle_perm
logical, dimension(:), allocatable :: angle_sig
type(equivalence_t), pointer :: next => null ()
end type equivalence_t
@ %def equivalence_t
<<PHS forests: types>>=
type :: equivalence_list_t
private
integer :: length = 0
type(equivalence_t), pointer :: first => null ()
type(equivalence_t), pointer :: last => null ()
end type equivalence_list_t
@ %def equivalence_list_t
@ Append an equivalence to the list
<<PHS forests: procedures>>=
subroutine equivalence_list_add (eql, left, right, perm)
type(equivalence_list_t), intent(inout) :: eql
integer, intent(in) :: left, right
type(permutation_t), intent(in) :: perm
type(equivalence_t), pointer :: eq
allocate (eq)
eq%left = left
eq%right = right
eq%perm = perm
if (associated (eql%last)) then
eql%last%next => eq
else
eql%first => eq
end if
eql%last => eq
eql%length = eql%length + 1
end subroutine equivalence_list_add
@ %def equivalence_list_add
@ Delete the list contents. Has to be pure because it is called from
an elemental subroutine.
<<PHS forests: procedures>>=
pure subroutine equivalence_list_final (eql)
type(equivalence_list_t), intent(inout) :: eql
type(equivalence_t), pointer :: eq
do while (associated (eql%first))
eq => eql%first
eql%first => eql%first%next
deallocate (eq)
end do
eql%last => null ()
eql%length = 0
end subroutine equivalence_list_final
@ %def equivalence_list_final
@ Make a deep copy of the equivalence list. This allows for deep
copies of groves and forests.
<<PHS forests: interfaces>>=
interface assignment(=)
module procedure equivalence_list_assign
end interface
<<PHS forests: main procedures>>=
subroutine equivalence_list_assign (eql_out, eql_in)
type(equivalence_list_t), intent(out) :: eql_out
type(equivalence_list_t), intent(in) :: eql_in
type(equivalence_t), pointer :: eq, eq_copy
eq => eql_in%first
do while (associated (eq))
allocate (eq_copy)
eq_copy = eq
eq_copy%next => null ()
if (associated (eql_out%first)) then
eql_out%last%next => eq_copy
else
eql_out%first => eq_copy
end if
eql_out%last => eq_copy
eq => eq%next
end do
end subroutine equivalence_list_assign
@ %def equivalence_list_assign
@ The number of list entries
<<PHS forests: procedures>>=
elemental function equivalence_list_length (eql) result (length)
integer :: length
type(equivalence_list_t), intent(in) :: eql
length = eql%length
end function equivalence_list_length
@ %def equivalence_list_length
@ Recursively write the equivalences list
<<PHS forests: procedures>>=
subroutine equivalence_list_write (eql, unit)
type(equivalence_list_t), intent(in) :: eql
integer, intent(in), optional :: unit
integer :: u
u = given_output_unit (unit); if (u < 0) return
if (associated (eql%first)) then
call equivalence_write_rec (eql%first, u)
else
write (u, *) " [empty]"
end if
contains
recursive subroutine equivalence_write_rec (eq, u)
type(equivalence_t), intent(in) :: eq
integer, intent(in) :: u
integer :: i
write (u, "(3x,A,1x,I0,1x,I0,2x,A)", advance="no") &
"Equivalence:", eq%left, eq%right, "Final state permutation:"
call permutation_write (eq%perm, u)
write (u, "(1x,12x,1x,A,1x)", advance="no") &
" msq permutation: "
call permutation_write (eq%msq_perm, u)
write (u, "(1x,12x,1x,A,1x)", advance="no") &
" angle permutation:"
call permutation_write (eq%angle_perm, u)
write (u, "(1x,12x,1x,26x)", advance="no")
do i = 1, size (eq%angle_sig)
if (eq%angle_sig(i)) then
write (u, "(1x,A)", advance="no") "+"
else
write (u, "(1x,A)", advance="no") "-"
end if
end do
write (u, *)
if (associated (eq%next)) call equivalence_write_rec (eq%next, u)
end subroutine equivalence_write_rec
end subroutine equivalence_list_write
@ %def equivalence_list_write
@
\subsection{Groves}
A grove is a group of trees (phase-space channels) that share a common
weight in the integration. Within a grove, channels can be declared
equivalent, so they also share their integration grids (up to
symmetries). The grove contains a list of equivalences. The
[[tree_count_offset]] is the total number of trees of the preceding
groves; when the trees are counted per forest (integration channels),
the offset has to be added to all tree indices.
<<PHS forests: types>>=
type :: phs_grove_t
private
integer :: tree_count_offset
type(phs_tree_t), dimension(:), allocatable :: tree
type(equivalence_list_t) :: equivalence_list
end type phs_grove_t
@ %def phs_grove_t
@ Call [[phs_tree_init]] which is also elemental:
<<PHS forests: procedures>>=
elemental subroutine phs_grove_init &
(grove, n_trees, n_in, n_out, n_masses, n_angles)
type(phs_grove_t), intent(inout) :: grove
integer, intent(in) :: n_trees, n_in, n_out, n_masses, n_angles
grove%tree_count_offset = 0
allocate (grove%tree (n_trees))
call grove%tree%init (n_in, n_out, n_masses, n_angles)
end subroutine phs_grove_init
@ %def phs_grove_init
@ The trees do not have pointer components, thus no call to
[[phs_tree_final]]:
<<PHS forests: procedures>>=
elemental subroutine phs_grove_final (grove)
type(phs_grove_t), intent(inout) :: grove
deallocate (grove%tree)
call equivalence_list_final (grove%equivalence_list)
end subroutine phs_grove_final
@ %def phs_grove_final
@ Deep copy. This triggers double free corruption with the Intel
compiler and hence has to remain in the main module.
<<PHS forests: interfaces>>=
interface assignment(=)
module procedure phs_grove_assign0
module procedure phs_grove_assign1
end interface
<<PHS forests: main procedures>>=
subroutine phs_grove_assign0 (grove_out, grove_in)
type(phs_grove_t), intent(out) :: grove_out
type(phs_grove_t), intent(in) :: grove_in
grove_out%tree_count_offset = grove_in%tree_count_offset
if (allocated (grove_in%tree)) then
allocate (grove_out%tree (size (grove_in%tree)))
grove_out%tree = grove_in%tree
end if
grove_out%equivalence_list = grove_in%equivalence_list
end subroutine phs_grove_assign0
subroutine phs_grove_assign1 (grove_out, grove_in)
type(phs_grove_t), dimension(:), intent(out) :: grove_out
type(phs_grove_t), dimension(:), intent(in) :: grove_in
integer :: i
do i = 1, size (grove_in)
call phs_grove_assign0 (grove_out(i), grove_in(i))
end do
end subroutine phs_grove_assign1
@ %def phs_grove_assign
@ Get the global (s-channel) mappings. Implemented as a subroutine
which returns an array (slice).
<<PHS forests: procedures>>=
subroutine phs_grove_assign_s_mappings (grove, mapping)
type(phs_grove_t), intent(in) :: grove
type(mapping_t), dimension(:), intent(out) :: mapping
integer :: i
if (size (mapping) == size (grove%tree)) then
do i = 1, size (mapping)
call grove%tree(i)%assign_s_mapping (mapping(i))
end do
else
call msg_bug ("phs_grove_assign_s_mappings: array size mismatch")
end if
end subroutine phs_grove_assign_s_mappings
@ %def phs_grove_assign_s_mappings
@
\subsection{The forest type}
This is a collection of trees and associated particles. In a given
tree, each branch code corresponds to a particle in the [[prt]] array.
Furthermore, we have an array of mass sums which is independent of the
decay tree and of the particular event. The mappings directly
correspond to the decay trees, and the decay groves collect the trees
in classes. The permutation list consists of all permutations of
outgoing particles that map the decay forest onto itself.
The particle codes [[flv]] (one for each external particle) are needed
for determining masses and such. The trees and associated information
are collected in the [[grove]] array, together with a lookup table
that associates tree indices to groves. Finally, the [[prt]] array
serves as workspace for phase-space evaluation.
The [[prt_combination]] is a list of index pairs, namely the particle
momenta pairs that need to be combined in order to provide all
momentum combinations that the phase-space trees need to know.
<<PHS forests: public>>=
public :: phs_forest_t
<<PHS forests: types>>=
type :: phs_forest_t
private
integer :: n_in, n_out, n_tot
integer :: n_masses, n_angles, n_dimensions
integer :: n_trees, n_equivalences
type(flavor_t), dimension(:), allocatable :: flv
type(phs_grove_t), dimension(:), allocatable :: grove
integer, dimension(:), allocatable :: grove_lookup
type(phs_prt_t), dimension(:), allocatable :: prt_in
type(phs_prt_t), dimension(:), allocatable :: prt_out
type(phs_prt_t), dimension(:), allocatable :: prt
integer(TC), dimension(:,:), allocatable :: prt_combination
type(mapping_t), dimension(:), allocatable :: s_mapping
contains
<<PHS forests: phs forest: TBP>>
end type phs_forest_t
@ %def phs_forest_t
@
The initialization merely allocates memory. We have to know how many
trees there are in each grove, so we can initialize everything. The
number of groves is the size of the [[n_tree]] array.
In the [[grove_lookup]] table we store the grove index that belongs to
each absolute tree index. The difference between the absolute index
and the relative (to the grove) index is stored, for each grove, as
[[tree_count_offset]].
The particle array is allocated according to the total number of
branches each tree has, but not filled.
<<PHS forests: phs forest: TBP>>=
procedure :: init => phs_forest_init
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_init (forest, n_tree, n_in, n_out)
class(phs_forest_t), intent(inout) :: forest
integer, dimension(:), intent(in) :: n_tree
integer, intent(in) :: n_in, n_out
end subroutine phs_forest_init
<<PHS forests: procedures>>=
module subroutine phs_forest_init (forest, n_tree, n_in, n_out)
class(phs_forest_t), intent(inout) :: forest
integer, dimension(:), intent(in) :: n_tree
integer, intent(in) :: n_in, n_out
integer :: g, count, k_root
forest%n_in = n_in
forest%n_out = n_out
forest%n_tot = n_in + n_out
forest%n_masses = max (n_out - 2, 0)
forest%n_angles = max (2*n_out - 2, 0)
forest%n_dimensions = forest%n_masses + forest%n_angles
forest%n_trees = sum (n_tree)
forest%n_equivalences = 0
allocate (forest%grove (size (n_tree)))
call phs_grove_init &
(forest%grove, n_tree, n_in, n_out, forest%n_masses, &
forest%n_angles)
allocate (forest%grove_lookup (forest%n_trees))
count = 0
do g = 1, size (forest%grove)
forest%grove(g)%tree_count_offset = count
forest%grove_lookup (count+1:count+n_tree(g)) = g
count = count + n_tree(g)
end do
allocate (forest%prt_in (n_in))
allocate (forest%prt_out (forest%n_out))
k_root = 2**forest%n_tot - 1
allocate (forest%prt (k_root))
allocate (forest%prt_combination (2, k_root))
allocate (forest%s_mapping (forest%n_trees))
end subroutine phs_forest_init
@ %def phs_forest_init
@ Assign the global (s-channel) mappings.
<<PHS forests: phs forest: TBP>>=
procedure :: set_s_mappings => phs_forest_set_s_mappings
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_s_mappings (forest)
class(phs_forest_t), intent(inout) :: forest
end subroutine phs_forest_set_s_mappings
<<PHS forests: procedures>>=
module subroutine phs_forest_set_s_mappings (forest)
class(phs_forest_t), intent(inout) :: forest
integer :: g, i0, i1, n
do g = 1, size (forest%grove)
call forest%get_grove_bounds (g, i0, i1, n)
call phs_grove_assign_s_mappings &
(forest%grove(g), forest%s_mapping(i0:i1))
end do
end subroutine phs_forest_set_s_mappings
@ %def phs_forest_set_s_mappings
@ The grove finalizer is called because it contains the equivalence list:
<<PHS forests: phs forest: TBP>>=
procedure :: final => phs_forest_final
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_final (forest)
class(phs_forest_t), intent(inout) :: forest
end subroutine phs_forest_final
<<PHS forests: procedures>>=
module subroutine phs_forest_final (forest)
class(phs_forest_t), intent(inout) :: forest
if (allocated (forest%grove)) then
call phs_grove_final (forest%grove)
deallocate (forest%grove)
end if
if (allocated (forest%grove_lookup)) deallocate (forest%grove_lookup)
if (allocated (forest%prt)) deallocate (forest%prt)
if (allocated (forest%s_mapping)) deallocate (forest%s_mapping)
end subroutine phs_forest_final
@ %def phs_forest_final
@
\subsection{Screen output}
Write the particles that are non-null, then the trees which point to
them:
<<PHS forests: phs forest: TBP>>=
procedure :: write => phs_forest_write
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_write (forest, unit)
class(phs_forest_t), intent(in) :: forest
integer, intent(in), optional :: unit
end subroutine phs_forest_write
<<PHS forests: procedures>>=
module subroutine phs_forest_write (forest, unit)
class(phs_forest_t), intent(in) :: forest
integer, intent(in), optional :: unit
integer :: u
integer :: i, g, k
u = given_output_unit (unit); if (u < 0) return
write (u, "(1x,A)") "Phase space forest:"
write (u, "(3x,A,I0)") "n_in = ", forest%n_in
write (u, "(3x,A,I0)") "n_out = ", forest%n_out
write (u, "(3x,A,I0)") "n_tot = ", forest%n_tot
write (u, "(3x,A,I0)") "n_masses = ", forest%n_masses
write (u, "(3x,A,I0)") "n_angles = ", forest%n_angles
write (u, "(3x,A,I0)") "n_dim = ", forest%n_dimensions
write (u, "(3x,A,I0)") "n_trees = ", forest%n_trees
write (u, "(3x,A,I0)") "n_equiv = ", forest%n_equivalences
write (u, "(3x,A)", advance="no") "flavors ="
if (allocated (forest%flv)) then
do i = 1, size (forest%flv)
write (u, "(1x,I0)", advance="no") forest%flv(i)%get_pdg ()
end do
write (u, "(A)")
else
write (u, "(1x,A)") "[empty]"
end if
write (u, "(1x,A)") "Particle combinations:"
if (allocated (forest%prt_combination)) then
do k = 1, size (forest%prt_combination, 2)
if (forest%prt_combination(1, k) /= 0) then
write (u, "(3x,I0,1x,'<=',1x,I0,1x,'+',1x,I0)") &
k, forest%prt_combination(:,k)
end if
end do
else
write (u, "(3x,A)") " [empty]"
end if
write (u, "(1x,A)") "Groves and trees:"
if (allocated (forest%grove)) then
do g = 1, size (forest%grove)
write (u, "(3x,A,1x,I0)") "Grove ", g
call phs_grove_write (forest%grove(g), unit)
end do
else
write (u, "(3x,A)") " [empty]"
end if
write (u, "(1x,A,I0)") "Total number of equivalences: ", &
forest%n_equivalences
write (u, "(A)")
write (u, "(1x,A)") "Global s-channel mappings:"
if (allocated (forest%s_mapping)) then
do i = 1, size (forest%s_mapping)
associate (mapping => forest%s_mapping(i))
if (mapping%is_s_channel () .or. mapping%is_on_shell ()) then
write (u, "(1x,I0,':',1x)", advance="no") i
call forest%s_mapping(i)%write (unit)
end if
end associate
end do
else
write (u, "(3x,A)") " [empty]"
end if
write (u, "(A)")
write (u, "(1x,A)") "Incoming particles:"
if (allocated (forest%prt_in)) then
if (any (forest%prt_in%is_defined ())) then
do i = 1, size (forest%prt_in)
if (forest%prt_in(i)%is_defined ()) then
write (u, "(1x,A,1x,I0)") "Particle", i
call forest%prt_in(i)%write (u)
end if
end do
else
write (u, "(3x,A)") "[all undefined]"
end if
else
write (u, "(3x,A)") " [empty]"
end if
write (u, "(A)")
write (u, "(1x,A)") "Outgoing particles:"
if (allocated (forest%prt_out)) then
if (any (forest%prt_out%is_defined ())) then
do i = 1, size (forest%prt_out)
if (forest%prt_out(i)%is_defined ()) then
write (u, "(1x,A,1x,I0)") "Particle", i
call forest%prt_out(i)%write (u)
end if
end do
else
write (u, "(3x,A)") "[all undefined]"
end if
else
write (u, "(1x,A)") " [empty]"
end if
write (u, "(A)")
write (u, "(1x,A)") "Tree particles:"
if (allocated (forest%prt)) then
if (any (forest%prt%is_defined ())) then
do i = 1, size (forest%prt)
if (forest%prt(i)%is_defined ()) then
write (u, "(1x,A,1x,I0)") "Particle", i
call forest%prt(i)%write (u)
end if
end do
else
write (u, "(3x,A)") "[all undefined]"
end if
else
write (u, "(3x,A)") " [empty]"
end if
end subroutine phs_forest_write
subroutine phs_grove_write (grove, unit)
type(phs_grove_t), intent(in) :: grove
integer, intent(in), optional :: unit
integer :: u
integer :: t
u = given_output_unit (unit); if (u < 0) return
do t = 1, size (grove%tree)
write (u, "(3x,A,I0)") "Tree ", t
call grove%tree(t)%write (unit)
end do
write (u, "(1x,A)") "Equivalence list:"
call equivalence_list_write (grove%equivalence_list, unit)
end subroutine phs_grove_write
@ %def phs_grove_write phs_forest_write
@ Deep copy.
<<PHS forests: public>>=
public :: assignment(=)
<<PHS forests: interfaces>>=
interface assignment(=)
module procedure phs_forest_assign
end interface
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_assign (forest_out, forest_in)
type(phs_forest_t), intent(out) :: forest_out
type(phs_forest_t), intent(in) :: forest_in
end subroutine phs_forest_assign
<<PHS forests: procedures>>=
module subroutine phs_forest_assign (forest_out, forest_in)
type(phs_forest_t), intent(out) :: forest_out
type(phs_forest_t), intent(in) :: forest_in
forest_out%n_in = forest_in%n_in
forest_out%n_out = forest_in%n_out
forest_out%n_tot = forest_in%n_tot
forest_out%n_masses = forest_in%n_masses
forest_out%n_angles = forest_in%n_angles
forest_out%n_dimensions = forest_in%n_dimensions
forest_out%n_trees = forest_in%n_trees
forest_out%n_equivalences = forest_in%n_equivalences
if (allocated (forest_in%flv)) then
allocate (forest_out%flv (size (forest_in%flv)))
forest_out%flv = forest_in%flv
end if
if (allocated (forest_in%grove)) then
allocate (forest_out%grove (size (forest_in%grove)))
forest_out%grove = forest_in%grove
end if
if (allocated (forest_in%grove_lookup)) then
allocate (forest_out%grove_lookup (size (forest_in%grove_lookup)))
forest_out%grove_lookup = forest_in%grove_lookup
end if
if (allocated (forest_in%prt_in)) then
allocate (forest_out%prt_in (size (forest_in%prt_in)))
forest_out%prt_in = forest_in%prt_in
end if
if (allocated (forest_in%prt_out)) then
allocate (forest_out%prt_out (size (forest_in%prt_out)))
forest_out%prt_out = forest_in%prt_out
end if
if (allocated (forest_in%prt)) then
allocate (forest_out%prt (size (forest_in%prt)))
forest_out%prt = forest_in%prt
end if
if (allocated (forest_in%s_mapping)) then
allocate (forest_out%s_mapping (size (forest_in%s_mapping)))
forest_out%s_mapping = forest_in%s_mapping
end if
if (allocated (forest_in%prt_combination)) then
allocate (forest_out%prt_combination &
(2, size (forest_in%prt_combination, 2)))
forest_out%prt_combination = forest_in%prt_combination
end if
end subroutine phs_forest_assign
@ %def phs_forest_assign
@
\subsection{Accessing contents}
Get the number of integration parameters
<<PHS forests: phs forest: TBP>>=
procedure :: get_n_parameters => phs_forest_get_n_parameters
<<PHS forests: sub interfaces>>=
module function phs_forest_get_n_parameters (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
end function phs_forest_get_n_parameters
<<PHS forests: procedures>>=
module function phs_forest_get_n_parameters (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
n = forest%n_dimensions
end function phs_forest_get_n_parameters
@ %def phs_forest_get_n_parameters
@ Get the number of integration channels
<<PHS forests: phs forest: TBP>>=
procedure :: get_n_channels => phs_forest_get_n_channels
<<PHS forests: sub interfaces>>=
module function phs_forest_get_n_channels (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
end function phs_forest_get_n_channels
<<PHS forests: procedures>>=
module function phs_forest_get_n_channels (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
n = forest%n_trees
end function phs_forest_get_n_channels
@ %def phs_forest_get_n_channels
@ Get the number of groves
<<PHS forests: phs forest: TBP>>=
procedure :: get_n_groves => phs_forest_get_n_groves
<<PHS forests: sub interfaces>>=
module function phs_forest_get_n_groves (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
end function phs_forest_get_n_groves
<<PHS forests: procedures>>=
module function phs_forest_get_n_groves (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
n = size (forest%grove)
end function phs_forest_get_n_groves
@ %def phs_forest_get_n_groves
@ Get the index bounds for a specific grove.
<<PHS forests: phs forest: TBP>>=
procedure :: get_grove_bounds => phs_forest_get_grove_bounds
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_get_grove_bounds (forest, g, i0, i1, n)
class(phs_forest_t), intent(in) :: forest
integer, intent(in) :: g
integer, intent(out) :: i0, i1, n
end subroutine phs_forest_get_grove_bounds
<<PHS forests: procedures>>=
module subroutine phs_forest_get_grove_bounds (forest, g, i0, i1, n)
class(phs_forest_t), intent(in) :: forest
integer, intent(in) :: g
integer, intent(out) :: i0, i1, n
n = size (forest%grove(g)%tree)
i0 = forest%grove(g)%tree_count_offset + 1
i1 = forest%grove(g)%tree_count_offset + n
end subroutine phs_forest_get_grove_bounds
@ %def phs_forest_get_grove_bounds
@ Get the number of equivalences
<<PHS forests: phs forest: TBP>>=
procedure :: get_n_equivalences => phs_forest_get_n_equivalences
<<PHS forests: sub interfaces>>=
module function phs_forest_get_n_equivalences (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
end function phs_forest_get_n_equivalences
<<PHS forests: procedures>>=
module function phs_forest_get_n_equivalences (forest) result (n)
integer :: n
class(phs_forest_t), intent(in) :: forest
n = forest%n_equivalences
end function phs_forest_get_n_equivalences
@ %def phs_forest_get_n_equivalences
@ Return true if a particular channel has a global (s-channel)
mapping; also return the resonance mass and width for this mapping.
<<PHS forests: phs forest: TBP>>=
procedure :: get_s_mapping => phs_forest_get_s_mapping
procedure :: get_on_shell => phs_forest_get_on_shell
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_get_s_mapping &
(forest, channel, flag, mass, width)
class(phs_forest_t), intent(in) :: forest
integer, intent(in) :: channel
logical, intent(out) :: flag
real(default), intent(out) :: mass, width
end subroutine phs_forest_get_s_mapping
module subroutine phs_forest_get_on_shell (forest, channel, flag, mass)
class(phs_forest_t), intent(in) :: forest
integer, intent(in) :: channel
logical, intent(out) :: flag
real(default), intent(out) :: mass
end subroutine phs_forest_get_on_shell
<<PHS forests: procedures>>=
module subroutine phs_forest_get_s_mapping &
(forest, channel, flag, mass, width)
class(phs_forest_t), intent(in) :: forest
integer, intent(in) :: channel
logical, intent(out) :: flag
real(default), intent(out) :: mass, width
flag = forest%s_mapping(channel)%is_s_channel ()
if (flag) then
mass = forest%s_mapping(channel)%get_mass ()
width = forest%s_mapping(channel)%get_width ()
else
mass = 0
width = 0
end if
end subroutine phs_forest_get_s_mapping
module subroutine phs_forest_get_on_shell (forest, channel, flag, mass)
class(phs_forest_t), intent(in) :: forest
integer, intent(in) :: channel
logical, intent(out) :: flag
real(default), intent(out) :: mass
flag = forest%s_mapping(channel)%is_on_shell ()
if (flag) then
mass = forest%s_mapping(channel)%get_mass ()
else
mass = 0
end if
end subroutine phs_forest_get_on_shell
@ %def phs_forest_get_s_mapping
@ %def phs_forest_get_on_shell
@
Extract the set of unique resonance histories, in form of an array.
<<PHS forests: phs forest: TBP>>=
procedure :: extract_resonance_history_set &
=> phs_forest_extract_resonance_history_set
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_extract_resonance_history_set &
(forest, res_set, include_trivial)
class(phs_forest_t), intent(in) :: forest
type(resonance_history_set_t), intent(out) :: res_set
logical, intent(in), optional :: include_trivial
end subroutine phs_forest_extract_resonance_history_set
<<PHS forests: procedures>>=
module subroutine phs_forest_extract_resonance_history_set &
(forest, res_set, include_trivial)
class(phs_forest_t), intent(in) :: forest
type(resonance_history_set_t), intent(out) :: res_set
logical, intent(in), optional :: include_trivial
type(resonance_history_t) :: rh
integer :: g, t
logical :: triv
triv = .false.; if (present (include_trivial)) triv = include_trivial
call res_set%init ()
do g = 1, size (forest%grove)
associate (grove => forest%grove(g))
do t = 1, size (grove%tree)
call grove%tree(t)%extract_resonance_history (rh)
call res_set%enter (rh, include_trivial)
end do
end associate
end do
call res_set%freeze ()
end subroutine phs_forest_extract_resonance_history_set
@ %def phs_forest_extract_resonance_history_set
@
\subsection{Read the phase space setup from file}
The phase space setup is stored in a file. The file may be generated
by the [[cascades]] module below, or by other means. This file has to
be read and parsed to create the PHS forest as the internal
phase-space representation.
Create lexer and syntax:
<<PHS forests: procedures>>=
subroutine define_phs_forest_syntax (ifile)
type(ifile_t) :: ifile
call ifile_append (ifile, "SEQ phase_space_list = process_phase_space*")
call ifile_append (ifile, "SEQ process_phase_space = " &
// "process_def process_header phase_space")
call ifile_append (ifile, "SEQ process_def = process process_list")
call ifile_append (ifile, "KEY process")
call ifile_append (ifile, "LIS process_list = process_tag*")
call ifile_append (ifile, "IDE process_tag")
call ifile_append (ifile, "SEQ process_header = " &
// "md5sum_process = md5sum " &
// "md5sum_model_par = md5sum " &
// "md5sum_phs_config = md5sum " &
// "sqrts = real " &
// "m_threshold_s = real " &
// "m_threshold_t = real " &
// "off_shell = integer " &
// "t_channel = integer " &
// "keep_nonresonant = logical")
call ifile_append (ifile, "KEY '='")
call ifile_append (ifile, "KEY '-'")
call ifile_append (ifile, "KEY md5sum_process")
call ifile_append (ifile, "KEY md5sum_model_par")
call ifile_append (ifile, "KEY md5sum_phs_config")
call ifile_append (ifile, "KEY sqrts")
call ifile_append (ifile, "KEY m_threshold_s")
call ifile_append (ifile, "KEY m_threshold_t")
call ifile_append (ifile, "KEY off_shell")
call ifile_append (ifile, "KEY t_channel")
call ifile_append (ifile, "KEY keep_nonresonant")
call ifile_append (ifile, "QUO md5sum = '""' ... '""'")
call ifile_append (ifile, "REA real")
call ifile_append (ifile, "INT integer")
call ifile_append (ifile, "IDE logical")
call ifile_append (ifile, "SEQ phase_space = grove_def+")
call ifile_append (ifile, "SEQ grove_def = grove tree_def+")
call ifile_append (ifile, "KEY grove")
call ifile_append (ifile, "SEQ tree_def = tree bincodes mapping*")
call ifile_append (ifile, "KEY tree")
call ifile_append (ifile, "SEQ bincodes = bincode*")
call ifile_append (ifile, "INT bincode")
call ifile_append (ifile, "SEQ mapping = map bincode channel signed_pdg")
call ifile_append (ifile, "KEY map")
call ifile_append (ifile, "ALT channel = &
&s_channel | t_channel | u_channel | &
&collinear | infrared | radiation | on_shell")
call ifile_append (ifile, "KEY s_channel")
! call ifile_append (ifile, "KEY t_channel") !!! Key already exists
call ifile_append (ifile, "KEY u_channel")
call ifile_append (ifile, "KEY collinear")
call ifile_append (ifile, "KEY infrared")
call ifile_append (ifile, "KEY radiation")
call ifile_append (ifile, "KEY on_shell")
call ifile_append (ifile, "ALT signed_pdg = &
&pdg | negative_pdg")
call ifile_append (ifile, "SEQ negative_pdg = '-' pdg")
call ifile_append (ifile, "INT pdg")
end subroutine define_phs_forest_syntax
@ %def define_phs_forest_syntax
@ The model-file syntax and lexer are fixed, therefore stored as
module variables:
<<PHS forests: variables>>=
type(syntax_t), target, save :: syntax_phs_forest
@ %def syntax_phs_forest
<<PHS forests: public>>=
public :: syntax_phs_forest_init
<<PHS forests: sub interfaces>>=
module subroutine syntax_phs_forest_init ()
end subroutine syntax_phs_forest_init
<<PHS forests: procedures>>=
module subroutine syntax_phs_forest_init ()
type(ifile_t) :: ifile
call define_phs_forest_syntax (ifile)
call syntax_init (syntax_phs_forest, ifile)
call ifile_final (ifile)
end subroutine syntax_phs_forest_init
@ %def syntax_phs_forest_init
<<PHS forests: procedures>>=
subroutine lexer_init_phs_forest (lexer)
type(lexer_t), intent(out) :: lexer
call lexer_init (lexer, &
comment_chars = "#!", &
quote_chars = '"', &
quote_match = '"', &
single_chars = "-", &
special_class = ["="] , &
keyword_list = syntax_get_keyword_list_ptr (syntax_phs_forest))
end subroutine lexer_init_phs_forest
@ %def lexer_init_phs_forest
<<PHS forests: public>>=
public :: syntax_phs_forest_final
<<PHS forests: sub interfaces>>=
module subroutine syntax_phs_forest_final ()
end subroutine syntax_phs_forest_final
<<PHS forests: procedures>>=
module subroutine syntax_phs_forest_final ()
call syntax_final (syntax_phs_forest)
end subroutine syntax_phs_forest_final
@ %def syntax_phs_forest_final
<<PHS forests: public>>=
public :: syntax_phs_forest_write
<<PHS forests: sub interfaces>>=
module subroutine syntax_phs_forest_write (unit)
integer, intent(in), optional :: unit
end subroutine syntax_phs_forest_write
<<PHS forests: procedures>>=
module subroutine syntax_phs_forest_write (unit)
integer, intent(in), optional :: unit
call syntax_write (syntax_phs_forest, unit)
end subroutine syntax_phs_forest_write
@ %def syntax_phs_forest_write
@ The concrete parser and interpreter. Generate an input stream for
the external [[unit]], read the parse tree (with given [[syntax]] and
[[lexer]]) from this stream, and transfer the contents of the parse
tree to the PHS [[forest]].
We look for the matching [[process]] tag, count groves and trees for
initializing the [[forest]], and fill the trees.
If the optional parameters are set, compare the parameters stored in
the file to those. Set [[match]] true if everything agrees.
<<PHS forests: phs forest: TBP>>=
generic :: read => read_file, read_unit, read_parse_tree
procedure :: read_file => phs_forest_read_file
procedure :: read_unit => phs_forest_read_unit
procedure :: read_parse_tree => phs_forest_read_parse_tree
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_read_file &
(forest, filename, process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, &
md5sum_phs_config, phs_par, match)
class(phs_forest_t), intent(out) :: forest
type(string_t), intent(in) :: filename
type(string_t), intent(in) :: process_id
integer, intent(in) :: n_in, n_out
class(model_data_t), intent(in), target :: model
logical, intent(out) :: found
character(32), intent(in), optional :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out), optional :: match
end subroutine phs_forest_read_file
module subroutine phs_forest_read_unit &
(forest, unit, process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, md5sum_phs_config, &
phs_par, match)
class(phs_forest_t), intent(out) :: forest
integer, intent(in) :: unit
type(string_t), intent(in) :: process_id
integer, intent(in) :: n_in, n_out
class(model_data_t), intent(in), target :: model
logical, intent(out) :: found
character(32), intent(in), optional :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out), optional :: match
end subroutine phs_forest_read_unit
module subroutine phs_forest_read_parse_tree &
(forest, parse_tree, process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, md5sum_phs_config, &
phs_par, match)
class(phs_forest_t), intent(out) :: forest
type(parse_tree_t), intent(in), target :: parse_tree
type(string_t), intent(in) :: process_id
integer, intent(in) :: n_in, n_out
class(model_data_t), intent(in), target :: model
logical, intent(out) :: found
character(32), intent(in), optional :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out), optional :: match
end subroutine phs_forest_read_parse_tree
<<PHS forests: procedures>>=
module subroutine phs_forest_read_file &
(forest, filename, process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, &
md5sum_phs_config, phs_par, match)
class(phs_forest_t), intent(out) :: forest
type(string_t), intent(in) :: filename
type(string_t), intent(in) :: process_id
integer, intent(in) :: n_in, n_out
class(model_data_t), intent(in), target :: model
logical, intent(out) :: found
character(32), intent(in), optional :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out), optional :: match
type(parse_tree_t), target :: parse_tree
type(stream_t), target :: stream
type(lexer_t) :: lexer
call lexer_init_phs_forest (lexer)
call stream_init (stream, char (filename))
call lexer_assign_stream (lexer, stream)
call parse_tree_init (parse_tree, syntax_phs_forest, lexer)
call phs_forest_read_parse_tree (forest, parse_tree, &
process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, md5sum_phs_config, phs_par, match)
call stream_final (stream)
call lexer_final (lexer)
call parse_tree_final (parse_tree)
end subroutine phs_forest_read_file
module subroutine phs_forest_read_unit &
(forest, unit, process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, md5sum_phs_config, &
phs_par, match)
class(phs_forest_t), intent(out) :: forest
integer, intent(in) :: unit
type(string_t), intent(in) :: process_id
integer, intent(in) :: n_in, n_out
class(model_data_t), intent(in), target :: model
logical, intent(out) :: found
character(32), intent(in), optional :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out), optional :: match
type(parse_tree_t), target :: parse_tree
type(stream_t), target :: stream
type(lexer_t) :: lexer
call lexer_init_phs_forest (lexer)
call stream_init (stream, unit)
call lexer_assign_stream (lexer, stream)
call parse_tree_init (parse_tree, syntax_phs_forest, lexer)
call phs_forest_read_parse_tree (forest, parse_tree, &
process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, md5sum_phs_config, &
phs_par, match)
call stream_final (stream)
call lexer_final (lexer)
call parse_tree_final (parse_tree)
end subroutine phs_forest_read_unit
module subroutine phs_forest_read_parse_tree &
(forest, parse_tree, process_id, n_in, n_out, model, found, &
md5sum_process, md5sum_model_par, md5sum_phs_config, &
phs_par, match)
class(phs_forest_t), intent(out) :: forest
type(parse_tree_t), intent(in), target :: parse_tree
type(string_t), intent(in) :: process_id
integer, intent(in) :: n_in, n_out
class(model_data_t), intent(in), target :: model
logical, intent(out) :: found
character(32), intent(in), optional :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out), optional :: match
type(parse_node_t), pointer :: node_header, node_phs, node_grove
integer :: n_grove, g
integer, dimension(:), allocatable :: n_tree
integer :: t
node_header => parse_tree_get_process_ptr (parse_tree, process_id)
found = associated (node_header); if (.not. found) return
if (present (match)) then
call phs_forest_check_input (node_header, &
md5sum_process, md5sum_model_par, md5sum_phs_config, phs_par, match)
if (.not. match) return
end if
node_phs => parse_node_get_next_ptr (node_header)
n_grove = parse_node_get_n_sub (node_phs)
allocate (n_tree (n_grove))
do g = 1, n_grove
node_grove => parse_node_get_sub_ptr (node_phs, g)
n_tree(g) = parse_node_get_n_sub (node_grove) - 1
end do
call forest%init (n_tree, n_in, n_out)
do g = 1, n_grove
node_grove => parse_node_get_sub_ptr (node_phs, g)
do t = 1, n_tree(g)
call phs_tree_set (forest%grove(g)%tree(t), &
parse_node_get_sub_ptr (node_grove, t+1), model)
end do
end do
end subroutine phs_forest_read_parse_tree
@ %def phs_forest
@ Check the input for consistency. If any MD5 sum or phase-space
parameter disagrees, the phase-space file cannot be used. The MD5
sum checks are skipped if the stored MD5 sum is empty.
<<PHS forests: procedures>>=
subroutine phs_forest_check_input (pn_header, &
md5sum_process, md5sum_model_par, md5sum_phs_config, phs_par, match)
type(parse_node_t), intent(in), target :: pn_header
character(32), intent(in) :: &
md5sum_process, md5sum_model_par, md5sum_phs_config
type(phs_parameters_t), intent(in), optional :: phs_par
logical, intent(out) :: match
type(parse_node_t), pointer :: pn_md5sum, pn_rval, pn_ival, pn_lval
character(32) :: md5sum
type(phs_parameters_t) :: phs_par_old
character(1) :: lstr
pn_md5sum => parse_node_get_sub_ptr (pn_header, 3)
md5sum = parse_node_get_string (pn_md5sum)
if (md5sum /= "" .and. md5sum /= md5sum_process) then
call msg_message ("Phase space: discarding old configuration &
&(process changed)")
match = .false.; return
end if
pn_md5sum => parse_node_get_next_ptr (pn_md5sum, 3)
md5sum = parse_node_get_string (pn_md5sum)
if (md5sum /= "" .and. md5sum /= md5sum_model_par) then
call msg_message ("Phase space: discarding old configuration &
&(model parameters changed)")
match = .false.; return
end if
pn_md5sum => parse_node_get_next_ptr (pn_md5sum, 3)
md5sum = parse_node_get_string (pn_md5sum)
if (md5sum /= "" .and. md5sum /= md5sum_phs_config) then
call msg_message ("Phase space: discarding old configuration &
&(configuration parameters changed)")
match = .false.; return
end if
if (present (phs_par)) then
pn_rval => parse_node_get_next_ptr (pn_md5sum, 3)
phs_par_old%sqrts = parse_node_get_real (pn_rval)
pn_rval => parse_node_get_next_ptr (pn_rval, 3)
phs_par_old%m_threshold_s = parse_node_get_real (pn_rval)
pn_rval => parse_node_get_next_ptr (pn_rval, 3)
phs_par_old%m_threshold_t = parse_node_get_real (pn_rval)
pn_ival => parse_node_get_next_ptr (pn_rval, 3)
phs_par_old%off_shell = parse_node_get_integer (pn_ival)
pn_ival => parse_node_get_next_ptr (pn_ival, 3)
phs_par_old%t_channel = parse_node_get_integer (pn_ival)
pn_lval => parse_node_get_next_ptr (pn_ival, 3)
lstr = parse_node_get_string (pn_lval)
read (lstr, "(L1)") phs_par_old%keep_nonresonant
if (phs_par_old /= phs_par) then
call msg_message &
("Phase space: discarding old configuration &
&(configuration parameters changed)")
match = .false.; return
end if
end if
match = .true.
end subroutine phs_forest_check_input
@ %def phs_forest_check_input
@ Initialize a specific tree in the forest, using the contents of the
'tree' node. First, count the bincodes, allocate an array and read
them in, and make the tree. Each $t$-channel tree is flipped to
$s$-channel. Then, find mappings and initialize them.
<<PHS forests: procedures>>=
subroutine phs_tree_set (tree, node, model)
type(phs_tree_t), intent(inout) :: tree
type(parse_node_t), intent(in), target :: node
class(model_data_t), intent(in), target :: model
type(parse_node_t), pointer :: node_bincodes, node_mapping, pn_pdg
integer :: n_bincodes, offset
integer(TC), dimension(:), allocatable :: bincode
integer :: b, n_mappings, m
integer(TC) :: k
type(string_t) :: type
integer :: pdg
node_bincodes => parse_node_get_sub_ptr (node, 2)
if (associated (node_bincodes)) then
select case (char (parse_node_get_rule_key (node_bincodes)))
case ("bincodes")
n_bincodes = parse_node_get_n_sub (node_bincodes)
offset = 2
case default
n_bincodes = 0
offset = 1
end select
else
n_bincodes = 0
offset = 2
end if
allocate (bincode (n_bincodes))
do b = 1, n_bincodes
bincode(b) = parse_node_get_integer &
(parse_node_get_sub_ptr (node_bincodes, b))
end do
call phs_tree_from_array (tree, bincode)
call tree%flip_t_to_s_channel ()
call tree%canonicalize ()
n_mappings = parse_node_get_n_sub (node) - offset
do m = 1, n_mappings
node_mapping => parse_node_get_sub_ptr (node, m + offset)
k = parse_node_get_integer &
(parse_node_get_sub_ptr (node_mapping, 2))
type = parse_node_get_key &
(parse_node_get_sub_ptr (node_mapping, 3))
pn_pdg => parse_node_get_sub_ptr (node_mapping, 4)
select case (char (pn_pdg%get_rule_key ()))
case ("pdg")
pdg = pn_pdg%get_integer ()
case ("negative_pdg")
pdg = - parse_node_get_integer (pn_pdg%get_sub_ptr (2))
end select
call tree%init_mapping (k, type, pdg, model)
end do
end subroutine phs_tree_set
@ %def phs_tree_set
@
\subsection{Preparation}
The trees that we read from file do not carry flavor information.
This is set separately:
The flavor list must be unique for a unique set of masses; if a given
particle can have different flavor, the mass must be degenerate, so we
can choose one of the possible flavor combinations.
<<PHS forests: phs forest: TBP>>=
procedure :: set_flavors => phs_forest_set_flavors
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_flavors (forest, flv, reshuffle, flv_extra)
class(phs_forest_t), intent(inout) :: forest
type(flavor_t), dimension(:), intent(in) :: flv
integer, intent(in), dimension(:), allocatable, optional :: reshuffle
type(flavor_t), intent(in), optional :: flv_extra
end subroutine phs_forest_set_flavors
<<PHS forests: procedures>>=
module subroutine phs_forest_set_flavors (forest, flv, reshuffle, flv_extra)
class(phs_forest_t), intent(inout) :: forest
type(flavor_t), dimension(:), intent(in) :: flv
integer, intent(in), dimension(:), allocatable, optional :: reshuffle
type(flavor_t), intent(in), optional :: flv_extra
integer :: i, n_flv0
if (present (reshuffle) .and. present (flv_extra)) then
n_flv0 = size (flv)
do i = 1, n_flv0
if (reshuffle(i) <= n_flv0) then
forest%flv(i) = flv (reshuffle(i))
else
forest%flv(i) = flv_extra
end if
end do
else
allocate (forest%flv (size (flv)))
forest%flv = flv
end if
end subroutine phs_forest_set_flavors
@ %def phs_forest_set_flavors
@
<<PHS forests: phs forest: TBP>>=
procedure :: set_momentum_links => phs_forest_set_momentum_links
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_momentum_links (forest, list)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in), dimension(:), allocatable :: list
end subroutine phs_forest_set_momentum_links
<<PHS forests: procedures>>=
module subroutine phs_forest_set_momentum_links (forest, list)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in), dimension(:), allocatable :: list
integer :: g, t
do g = 1, size (forest%grove)
do t = 1, size (forest%grove(g)%tree)
associate (tree => forest%grove(g)%tree(t))
call phs_tree_set_momentum_links (tree, list)
!!! call tree%reshuffle_mappings ()
end associate
end do
end do
end subroutine phs_forest_set_momentum_links
@ %def phs_forest_set_momentum_links
@ Once the parameter set is fixed, the masses and the widths of the
particles are known and the [[mass_sum]] arrays as well as the mapping
parameters can be computed. Note that order is important: we first
compute the mass sums, then the ordinary mappings. The resonances
obtained here determine the effective masses, which in turn are used
to implement step mappings for resonance decay products that are not
mapped otherwise.
<<PHS forests: phs forest: TBP>>=
procedure :: set_parameters => phs_forest_set_parameters
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_parameters &
(forest, mapping_defaults, variable_limits)
class(phs_forest_t), intent(inout) :: forest
type(mapping_defaults_t), intent(in) :: mapping_defaults
logical, intent(in) :: variable_limits
end subroutine phs_forest_set_parameters
<<PHS forests: procedures>>=
module subroutine phs_forest_set_parameters &
(forest, mapping_defaults, variable_limits)
class(phs_forest_t), intent(inout) :: forest
type(mapping_defaults_t), intent(in) :: mapping_defaults
logical, intent(in) :: variable_limits
integer :: g, t
do g = 1, size (forest%grove)
do t = 1, size (forest%grove(g)%tree)
call forest%grove(g)%tree(t)%set_mass_sum (forest%flv(forest%n_in+1:))
call forest%grove(g)%tree(t)%set_mapping_parameters &
(mapping_defaults, variable_limits)
call forest%grove(g)%tree(t)%set_effective_masses ()
if (mapping_defaults%step_mapping) then
call forest%grove(g)%tree(t)%set_step_mappings &
(mapping_defaults%step_mapping_exp, variable_limits)
end if
end do
end do
end subroutine phs_forest_set_parameters
@ %def phs_forest_set_parameters
@ Generate the particle combination table. Scan all trees and merge
their individual combination tables. At the end, valid entries are
non-zero, and they indicate the indices of a pair of particles to be
combined to a new particle. If a particle is accessible by more than
one tree (this is usual), only keep the first possibility.
<<PHS forests: phs forest: TBP>>=
procedure :: setup_prt_combinations => phs_forest_setup_prt_combinations
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_setup_prt_combinations (forest)
class(phs_forest_t), intent(inout) :: forest
end subroutine phs_forest_setup_prt_combinations
<<PHS forests: procedures>>=
module subroutine phs_forest_setup_prt_combinations (forest)
class(phs_forest_t), intent(inout) :: forest
integer :: g, t
integer, dimension(:,:), allocatable :: tree_prt_combination
forest%prt_combination = 0
allocate (tree_prt_combination (2, size (forest%prt_combination, 2)))
do g = 1, size (forest%grove)
do t = 1, size (forest%grove(g)%tree)
call phs_tree_setup_prt_combinations &
(forest%grove(g)%tree(t), tree_prt_combination)
where (tree_prt_combination /= 0 .and. forest%prt_combination == 0)
forest%prt_combination = tree_prt_combination
end where
end do
end do
end subroutine phs_forest_setup_prt_combinations
@ %def phs_forest_setup_prt_combinations
@
\subsection{Accessing the particle arrays}
Set the incoming particles from the contents of an interaction.
<<PHS forests: phs forest: TBP>>=
generic :: set_prt_in => set_prt_in_int, set_prt_in_mom
procedure :: set_prt_in_int => phs_forest_set_prt_in_int
procedure :: set_prt_in_mom => phs_forest_set_prt_in_mom
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_prt_in_int (forest, int, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(interaction_t), intent(in) :: int
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
end subroutine phs_forest_set_prt_in_int
module subroutine phs_forest_set_prt_in_mom (forest, mom, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(vector4_t), dimension(size (forest%prt_in)), intent(in) :: mom
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
end subroutine phs_forest_set_prt_in_mom
<<PHS forests: procedures>>=
module subroutine phs_forest_set_prt_in_int (forest, int, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(interaction_t), intent(in) :: int
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
if (present (lt_cm_to_lab)) then
call forest%prt_in%set_momentum (inverse (lt_cm_to_lab) * &
int%get_momenta (outgoing=.false.))
else
call forest%prt_in%set_momentum (int%get_momenta (outgoing=.false.))
end if
associate (m_in => forest%flv(:forest%n_in)%get_mass ())
call forest%prt_in%set_msq (m_in ** 2)
end associate
call forest%prt_in%set_defined ()
end subroutine phs_forest_set_prt_in_int
module subroutine phs_forest_set_prt_in_mom (forest, mom, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(vector4_t), dimension(size (forest%prt_in)), intent(in) :: mom
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
if (present (lt_cm_to_lab)) then
call forest%prt_in%set_momentum (inverse (lt_cm_to_lab) * mom)
else
call forest%prt_in%set_momentum (mom)
end if
associate (m_in => forest%flv(:forest%n_in)%get_mass ())
call forest%prt_in%set_msq (m_in ** 2)
end associate
call forest%prt_in%set_defined ()
end subroutine phs_forest_set_prt_in_mom
@ %def phs_forest_set_prt_in
@ Set the outgoing particles from the contents of an interaction.
<<PHS forests: phs forest: TBP>>=
generic :: set_prt_out => set_prt_out_int, set_prt_out_mom
procedure :: set_prt_out_int => phs_forest_set_prt_out_int
procedure :: set_prt_out_mom => phs_forest_set_prt_out_mom
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_prt_out_int (forest, int, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(interaction_t), intent(in) :: int
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
end subroutine phs_forest_set_prt_out_int
module subroutine phs_forest_set_prt_out_mom (forest, mom, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(vector4_t), dimension(size (forest%prt_out)), intent(in) :: mom
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
end subroutine phs_forest_set_prt_out_mom
<<PHS forests: procedures>>=
module subroutine phs_forest_set_prt_out_int (forest, int, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(interaction_t), intent(in) :: int
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
if (present (lt_cm_to_lab)) then
call forest%prt_out%set_momentum (inverse (lt_cm_to_lab) * &
int%get_momenta (outgoing=.true.))
else
call forest%prt_out%set_momentum (int%get_momenta (outgoing=.true.))
end if
associate (m_out => forest%flv(forest%n_in+1:)%get_mass ())
call forest%prt_out%set_msq (m_out ** 2)
end associate
call forest%prt_out%set_defined ()
end subroutine phs_forest_set_prt_out_int
module subroutine phs_forest_set_prt_out_mom (forest, mom, lt_cm_to_lab)
class(phs_forest_t), intent(inout) :: forest
type(vector4_t), dimension(size (forest%prt_out)), intent(in) :: mom
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
if (present (lt_cm_to_lab)) then
call forest%prt_out%set_momentum (inverse (lt_cm_to_lab) * mom)
else
call forest%prt_out%set_momentum (mom)
end if
associate (m_out => forest%flv(forest%n_in+1:)%get_mass ())
call forest%prt_out%set_msq (m_out ** 2)
end associate
call forest%prt_out%set_defined ()
end subroutine phs_forest_set_prt_out_mom
@ %def phs_forest_set_prt_out
@ Combine particles as described by the particle combination table.
Particle momentum sums will be calculated only if the resulting
particle is contained in at least one of the trees in the current
forest. The others are kept undefined.
<<PHS forests: phs forest: TBP>>=
procedure :: combine_particles => phs_forest_combine_particles
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_combine_particles (forest)
class(phs_forest_t), intent(inout) :: forest
end subroutine phs_forest_combine_particles
<<PHS forests: procedures>>=
module subroutine phs_forest_combine_particles (forest)
class(phs_forest_t), intent(inout) :: forest
integer :: k
integer, dimension(2) :: kk
do k = 1, size (forest%prt_combination, 2)
kk = forest%prt_combination(:,k)
if (kk(1) /= 0) then
call forest%prt(k)%combine (forest%prt(kk(1)), forest%prt(kk(2)))
end if
end do
end subroutine phs_forest_combine_particles
@ %def phs_forest_combine_particles
@ Extract the outgoing particles and insert into an interaction.
<<PHS forests: phs forest: TBP>>=
procedure :: get_prt_out => phs_forest_get_prt_out
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_get_prt_out (forest, int, lt_cm_to_lab)
class(phs_forest_t), intent(in) :: forest
type(interaction_t), intent(inout) :: int
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
end subroutine phs_forest_get_prt_out
<<PHS forests: procedures>>=
module subroutine phs_forest_get_prt_out (forest, int, lt_cm_to_lab)
class(phs_forest_t), intent(in) :: forest
type(interaction_t), intent(inout) :: int
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
if (present (lt_cm_to_lab)) then
call int%set_momenta (lt_cm_to_lab * &
forest%prt_out%get_momentum (), outgoing=.true.)
else
call int%set_momenta (forest%prt_out%get_momentum (), &
outgoing=.true.)
end if
end subroutine phs_forest_get_prt_out
@ %def phs_forest_get_prt_out
@ Extract the outgoing particle momenta
<<PHS forests: phs forest: TBP>>=
procedure :: get_momenta_out => phs_forest_get_momenta_out
<<PHS forests: sub interfaces>>=
module function phs_forest_get_momenta_out (forest, lt_cm_to_lab) result (p)
class(phs_forest_t), intent(in) :: forest
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
type(vector4_t), dimension(size (forest%prt_out)) :: p
end function phs_forest_get_momenta_out
<<PHS forests: procedures>>=
module function phs_forest_get_momenta_out (forest, lt_cm_to_lab) result (p)
class(phs_forest_t), intent(in) :: forest
type(lorentz_transformation_t), intent(in), optional :: lt_cm_to_lab
type(vector4_t), dimension(size (forest%prt_out)) :: p
p = forest%prt_out%get_momentum ()
if (present (lt_cm_to_lab)) p = p * lt_cm_to_lab
end function phs_forest_get_momenta_out
@ %def phs_forest_get_momenta_out
@
\subsection{Find equivalences among phase-space trees}
Scan phase space for equivalences. We generate the complete set of
unique permutations for the given list of outgoing particles, and use
this for scanning equivalences within each grove.
@ We scan all pairs of trees, using all permutations. This implies
that trivial equivalences are included, and equivalences between
different trees are recorded twice. This is intentional.
<<PHS forests: procedures>>=
subroutine phs_grove_set_equivalences (grove, perm_array)
type(phs_grove_t), intent(inout) :: grove
type(permutation_t), dimension(:), intent(in) :: perm_array
type(equivalence_t), pointer :: eq
integer :: t1, t2, i
do t1 = 1, size (grove%tree)
do t2 = 1, size (grove%tree)
SCAN_PERM: do i = 1, size (perm_array)
if (phs_tree_equivalent &
(grove%tree(t1), grove%tree(t2), perm_array(i))) then
call equivalence_list_add &
(grove%equivalence_list, t1, t2, perm_array(i))
eq => grove%equivalence_list%last
call phs_tree_find_msq_permutation &
(grove%tree(t1), grove%tree(t2), eq%perm, &
eq%msq_perm)
call phs_tree_find_angle_permutation &
(grove%tree(t1), grove%tree(t2), eq%perm, &
eq%angle_perm, eq%angle_sig)
end if
end do SCAN_PERM
end do
end do
end subroutine phs_grove_set_equivalences
@ %def phs_grove_set_equivalences
<<PHS forests: phs forest: TBP>>=
procedure :: set_equivalences => phs_forest_set_equivalences
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_set_equivalences (forest)
class(phs_forest_t), intent(inout) :: forest
end subroutine phs_forest_set_equivalences
<<PHS forests: procedures>>=
module subroutine phs_forest_set_equivalences (forest)
class(phs_forest_t), intent(inout) :: forest
type(permutation_t), dimension(:), allocatable :: perm_array
integer :: i
call permutation_array_make &
(perm_array, forest%flv(forest%n_in+1:)%get_pdg ())
do i = 1, size (forest%grove)
call phs_grove_set_equivalences (forest%grove(i), perm_array)
end do
forest%n_equivalences = sum (forest%grove%equivalence_list%length)
end subroutine phs_forest_set_equivalences
@ %def phs_forest_set_equivalences
@
\subsection{Interface for channel equivalences}
Here, we store the equivalence list in the appropriate containers that
the [[phs_base]] module provides. There is one separate list for each
channel.
<<PHS forests: phs forest: TBP>>=
procedure :: get_equivalences => phs_forest_get_equivalences
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_get_equivalences &
(forest, channel, azimuthal_dependence)
class(phs_forest_t), intent(in) :: forest
type(phs_channel_t), dimension(:), intent(out) :: channel
logical, intent(in) :: azimuthal_dependence
end subroutine phs_forest_get_equivalences
<<PHS forests: procedures>>=
module subroutine phs_forest_get_equivalences &
(forest, channel, azimuthal_dependence)
class(phs_forest_t), intent(in) :: forest
type(phs_channel_t), dimension(:), intent(out) :: channel
logical, intent(in) :: azimuthal_dependence
integer :: n_masses, n_angles
integer :: mode_azimuthal_angle
integer, dimension(:), allocatable :: n_eq
type(equivalence_t), pointer :: eq
integer, dimension(:), allocatable :: perm, mode
integer :: g, c, j, left, right
n_masses = forest%n_masses
n_angles = forest%n_angles
allocate (n_eq (forest%n_trees), source = 0)
allocate (perm (forest%n_dimensions))
allocate (mode (forest%n_dimensions), source = EQ_IDENTITY)
do g = 1, size (forest%grove)
eq => forest%grove(g)%equivalence_list%first
do while (associated (eq))
left = eq%left + forest%grove(g)%tree_count_offset
n_eq(left) = n_eq(left) + 1
eq => eq%next
end do
end do
do c = 1, size (channel)
allocate (channel(c)%eq (n_eq(c)))
do j = 1, n_eq(c)
call channel(c)%eq(j)%init (forest%n_dimensions)
end do
end do
n_eq = 0
if (azimuthal_dependence) then
mode_azimuthal_angle = EQ_IDENTITY
else
mode_azimuthal_angle = EQ_INVARIANT
end if
do g = 1, size (forest%grove)
eq => forest%grove(g)%equivalence_list%first
do while (associated (eq))
left = eq%left + forest%grove(g)%tree_count_offset
right = eq%right + forest%grove(g)%tree_count_offset
do j = 1, n_masses
perm(j) = permute (j, eq%msq_perm)
mode(j) = EQ_IDENTITY
end do
do j = 1, n_angles
perm(n_masses+j) = n_masses + permute (j, eq%angle_perm)
if (j == 1) then
mode(n_masses+j) = mode_azimuthal_angle ! first az. angle
else if (mod(j,2) == 1) then
mode(n_masses+j) = EQ_SYMMETRIC ! other az. angles
else if (eq%angle_sig(j)) then
mode(n_masses+j) = EQ_IDENTITY ! polar angle +
else
mode(n_masses+j) = EQ_INVERT ! polar angle -
end if
end do
n_eq(left) = n_eq(left) + 1
associate (eq_cur => channel(left)%eq(n_eq(left)))
eq_cur%c = right
eq_cur%perm = perm
eq_cur%mode = mode
end associate
eq => eq%next
end do
end do
end subroutine phs_forest_get_equivalences
@ %def phs_forest_get_equivalences
@
\subsection{Phase-space evaluation}
Given one row of the [[x]] parameter array and the corresponding
channel index, compute first all relevant momenta and then recover the
remainder of the [[x]] array, the Jacobians [[phs_factor]], and the
phase-space [[volume]].
The output argument [[ok]] indicates whether this was successful.
<<PHS forests: phs forest: TBP>>=
procedure :: evaluate_selected_channel => phs_forest_evaluate_selected_channel
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_evaluate_selected_channel &
(forest, channel, active, sqrts, x, phs_factor, volume, ok)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in) :: channel
logical, dimension(:), intent(in) :: active
real(default), intent(in) :: sqrts
real(default), dimension(:,:), intent(inout) :: x
real(default), dimension(:), intent(out) :: phs_factor
real(default), intent(out) :: volume
logical, intent(out) :: ok
end subroutine phs_forest_evaluate_selected_channel
<<PHS forests: procedures>>=
module subroutine phs_forest_evaluate_selected_channel &
(forest, channel, active, sqrts, x, phs_factor, volume, ok)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in) :: channel
logical, dimension(:), intent(in) :: active
real(default), intent(in) :: sqrts
real(default), dimension(:,:), intent(inout) :: x
real(default), dimension(:), intent(out) :: phs_factor
real(default), intent(out) :: volume
logical, intent(out) :: ok
integer :: g, t
integer(TC) :: k, k_root, k_in
g = forest%grove_lookup (channel)
t = channel - forest%grove(g)%tree_count_offset
call forest%prt%set_undefined ()
call forest%prt_out%set_undefined ()
k_in = forest%n_tot
do k = 1,forest%n_in
forest%prt(ibset(0,k_in-k)) = forest%prt_in(k)
end do
do k = 1, forest%n_out
call forest%prt(ibset(0,k-1))%set_msq &
(forest%flv(forest%n_in+k)%get_mass () ** 2)
end do
k_root = 2**forest%n_out - 1
select case (forest%n_in)
case (1)
forest%prt(k_root) = forest%prt_in(1)
case (2)
call forest%prt(k_root)%combine (forest%prt_in(1), forest%prt_in(2))
end select
call forest%grove(g)%tree(t)%compute_momenta_from_x (forest%prt, &
phs_factor(channel), volume, sqrts, x(:,channel), ok)
if (ok) then
do k = 1, forest%n_out
forest%prt_out(k) = forest%prt(ibset(0,k-1))
end do
end if
end subroutine phs_forest_evaluate_selected_channel
@ %def phs_forest_evaluate_selected_channel
@ The remainder: recover $x$ values for all channels except for the current
channel.
NOTE: OpenMP not used for the first loop. [[combine_particles]] is not a
channel-local operation.
<<PHS forests: phs forest: TBP>>=
procedure :: evaluate_other_channels => phs_forest_evaluate_other_channels
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_evaluate_other_channels &
(forest, channel, active, sqrts, x, phs_factor, combine)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in) :: channel
logical, dimension(:), intent(in) :: active
real(default), intent(in) :: sqrts
real(default), dimension(:,:), intent(inout) :: x
real(default), dimension(:), intent(inout) :: phs_factor
logical, intent(in) :: combine
end subroutine phs_forest_evaluate_other_channels
<<PHS forests: procedures>>=
module subroutine phs_forest_evaluate_other_channels &
(forest, channel, active, sqrts, x, phs_factor, combine)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in) :: channel
logical, dimension(:), intent(in) :: active
real(default), intent(in) :: sqrts
real(default), dimension(:,:), intent(inout) :: x
real(default), dimension(:), intent(inout) :: phs_factor
logical, intent(in) :: combine
integer :: g, t, ch, n_channel
g = forest%grove_lookup (channel)
t = channel - forest%grove(g)%tree_count_offset
n_channel = forest%n_trees
if (combine) then
do ch = 1, n_channel
if (ch == channel) cycle
if (active(ch)) then
g = forest%grove_lookup(ch)
t = ch - forest%grove(g)%tree_count_offset
call phs_tree_combine_particles &
(forest%grove(g)%tree(t), forest%prt)
end if
end do
end if
!OMP PARALLEL PRIVATE (g,t,ch) SHARED(active,forest,sqrts,x,channel)
!OMP DO SCHEDULE(STATIC)
do ch = 1, n_channel
if (ch == channel) cycle
if (active(ch)) then
g = forest%grove_lookup(ch)
t = ch - forest%grove(g)%tree_count_offset
call forest%grove(g)%tree(t)%compute_x_from_momenta (forest%prt, &
phs_factor(ch), sqrts, x(:,ch))
end if
end do
!OMP END DO
!OMP END PARALLEL
end subroutine phs_forest_evaluate_other_channels
@ %def phs_forest_evaluate_other_channels
@ The complement: recover one row of the [[x]] array and the
associated Jacobian entry, corresponding to
[[channel]], from incoming and outgoing momenta. Also compute the
phase-space volume.
<<PHS forests: phs forest: TBP>>=
procedure :: recover_channel => phs_forest_recover_channel
<<PHS forests: sub interfaces>>=
module subroutine phs_forest_recover_channel &
(forest, channel, sqrts, x, phs_factor, volume)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in) :: channel
real(default), intent(in) :: sqrts
real(default), dimension(:,:), intent(inout) :: x
real(default), dimension(:), intent(inout) :: phs_factor
real(default), intent(out) :: volume
end subroutine phs_forest_recover_channel
<<PHS forests: procedures>>=
module subroutine phs_forest_recover_channel &
(forest, channel, sqrts, x, phs_factor, volume)
class(phs_forest_t), intent(inout) :: forest
integer, intent(in) :: channel
real(default), intent(in) :: sqrts
real(default), dimension(:,:), intent(inout) :: x
real(default), dimension(:), intent(inout) :: phs_factor
real(default), intent(out) :: volume
integer :: g, t
integer(TC) :: k, k_in
g = forest%grove_lookup (channel)
t = channel - forest%grove(g)%tree_count_offset
call forest%prt%set_undefined ()
k_in = forest%n_tot
forall (k = 1:forest%n_in)
forest%prt(ibset(0,k_in-k)) = forest%prt_in(k)
end forall
forall (k = 1:forest%n_out)
forest%prt(ibset(0,k-1)) = forest%prt_out(k)
end forall
call forest%combine_particles ()
call forest%grove(g)%tree(t)%compute_volume (sqrts, volume)
call forest%grove(g)%tree(t)%compute_x_from_momenta (forest%prt, &
phs_factor(channel), sqrts, x(:,channel))
end subroutine phs_forest_recover_channel
@ %def phs_forest_recover_channel
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_forests_ut.f90]]>>=
<<File header>>
module phs_forests_ut
use unit_tests
use phs_forests_uti
<<Standard module head>>
<<PHS forests: public test>>
contains
<<PHS forests: test driver>>
end module phs_forests_ut
@ %def phs_forests_ut
@
<<[[phs_forests_uti.f90]]>>=
<<File header>>
module phs_forests_uti
<<Use kinds>>
<<Use strings>>
use io_units
use format_defs, only: FMT_12
use lorentz
use flavors
use interactions
use model_data
use mappings
use phs_base
use resonances, only: resonance_history_set_t
use phs_forests
<<Standard module head>>
<<PHS forests: test declarations>>
contains
<<PHS forests: tests>>
end module phs_forests_uti
@ %def phs_forests_ut
@ API: driver for the unit tests below.
<<PHS forests: public test>>=
public :: phs_forests_test
<<PHS forests: test driver>>=
subroutine phs_forests_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS forests: execute tests>>
end subroutine phs_forests_test
@ %def phs_forests_test
@
\subsubsection{Basic universal test}
Write a possible phase-space file for a $2\to 3$ process and make the
corresponding forest, print the forest. Choose some in-particle
momenta and a random-number array and evaluate out-particles and
phase-space factors.
<<PHS forests: execute tests>>=
call test (phs_forest_1, "phs_forest_1", &
"check phs forest setup", &
u, results)
<<PHS forests: test declarations>>=
public :: phs_forest_1
<<PHS forests: tests>>=
subroutine phs_forest_1 (u)
use os_interface
integer, intent(in) :: u
type(phs_forest_t) :: forest
type(phs_channel_t), dimension(:), allocatable :: channel
type(model_data_t), target :: model
type(string_t) :: process_id
type(flavor_t), dimension(5) :: flv
type(string_t) :: filename
type(interaction_t) :: int
integer :: unit_fix
type(mapping_defaults_t) :: mapping_defaults
logical :: found_process, ok
integer :: n_channel, ch, i
logical, dimension(4) :: active = .true.
real(default) :: sqrts = 1000
real(default), dimension(5,4) :: x
real(default), dimension(4) :: factor
real(default) :: volume
write (u, "(A)") "* Test output: PHS forest"
write (u, "(A)") "* Purpose: test PHS forest routines"
write (u, "(A)")
write (u, "(A)") "* Reading model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Create phase-space file 'phs_forest_test.phs'"
write (u, "(A)")
call flv%init ([11, -11, 11, -11, 22], model)
unit_fix = free_unit ()
open (file="phs_forest_test.phs", unit=unit_fix, action="write")
write (unit_fix, *) "process foo"
write (unit_fix, *) 'md5sum_process = "6ABA33BC2927925D0F073B1C1170780A"'
write (unit_fix, *) 'md5sum_model_par = "1A0B151EE6E2DEB92D880320355A3EAB"'
write (unit_fix, *) 'md5sum_phs_config = "B6A8877058809A8BDD54753CDAB83ACE"'
write (unit_fix, *) "sqrts = 100.00000000000000"
write (unit_fix, *) "m_threshold_s = 50.000000000000000"
write (unit_fix, *) "m_threshold_t = 100.00000000000000"
write (unit_fix, *) "off_shell = 2"
write (unit_fix, *) "t_channel = 6"
write (unit_fix, *) "keep_nonresonant = F"
write (unit_fix, *) ""
write (unit_fix, *) " grove"
write (unit_fix, *) " tree 3 7"
write (unit_fix, *) " map 3 s_channel 23"
write (unit_fix, *) " tree 5 7"
write (unit_fix, *) " tree 6 7"
write (unit_fix, *) " grove"
write (unit_fix, *) " tree 9 11"
write (unit_fix, *) " map 9 t_channel 22"
close (unit_fix)
write (u, "(A)")
write (u, "(A)") "* Read phase-space file 'phs_forest_test.phs'"
call syntax_phs_forest_init ()
process_id = "foo"
filename = "phs_forest_test.phs"
call forest%read (filename, process_id, 2, 3, model, found_process)
write (u, "(A)")
write (u, "(A)") "* Set parameters, flavors, equiv, momenta"
write (u, "(A)")
call forest%set_flavors (flv)
call forest%set_parameters (mapping_defaults, .false.)
call forest%setup_prt_combinations ()
call forest%set_equivalences ()
call int%basic_init (2, 0, 3)
call int%set_momentum &
(vector4_moving (500._default, 500._default, 3), 1)
call int%set_momentum &
(vector4_moving (500._default,-500._default, 3), 2)
call forest%set_prt_in (int)
n_channel = 2
x = 0
x(:,n_channel) = [0.3, 0.4, 0.1, 0.9, 0.6]
write (u, "(A)") " Input values:"
write (u, "(3x,5(1x," // FMT_12 // "))") x(:,n_channel)
write (u, "(A)")
write (u, "(A)") "* Evaluating phase space"
call forest%evaluate_selected_channel (n_channel, active, sqrts, &
x, factor, volume, ok)
call forest%evaluate_other_channels (n_channel, active, sqrts, &
x, factor, combine=.true.)
call forest%get_prt_out (int)
write (u, "(A)") " Output values:"
do ch = 1, 4
write (u, "(3x,5(1x," // FMT_12 // "))") x(:,ch)
end do
call int%basic_write (u)
write (u, "(A)") " Factors:"
write (u, "(3x,5(1x," // FMT_12 // "))") factor
write (u, "(A)") " Volume:"
write (u, "(3x,5(1x," // FMT_12 // "))") volume
call forest%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute equivalences"
n_channel = 4
allocate (channel (n_channel))
call forest%get_equivalences (channel, .true.)
do i = 1, n_channel
write (u, "(1x,I0,':')", advance = "no") ch
call channel(i)%write (u)
end do
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
call forest%final ()
call syntax_phs_forest_final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_forest_1"
end subroutine phs_forest_1
@ %def phs_forest_1
@
\subsubsection{Resonance histories}
Read a suitably nontrivial forest from file and recover the set of
resonance histories.
<<PHS forests: execute tests>>=
call test (phs_forest_2, "phs_forest_2", &
"handle phs forest resonance content", &
u, results)
<<PHS forests: test declarations>>=
public :: phs_forest_2
<<PHS forests: tests>>=
subroutine phs_forest_2 (u)
use os_interface
integer, intent(in) :: u
integer :: unit_fix
type(phs_forest_t) :: forest
type(model_data_t), target :: model
type(string_t) :: process_id
type(string_t) :: filename
logical :: found_process
type(resonance_history_set_t) :: res_set
integer :: i
write (u, "(A)") "* Test output: phs_forest_2"
write (u, "(A)") "* Purpose: test PHS forest routines"
write (u, "(A)")
write (u, "(A)") "* Reading model file"
call model%init_sm_test ()
write (u, "(A)")
write (u, "(A)") "* Create phase-space file 'phs_forest_2.phs'"
write (u, "(A)")
unit_fix = free_unit ()
open (file="phs_forest_2.phs", unit=unit_fix, action="write")
write (unit_fix, *) "process foo"
write (unit_fix, *) 'md5sum_process = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"'
write (unit_fix, *) 'md5sum_model_par = "1A0B151EE6E2DEB92D880320355A3EAB"'
write (unit_fix, *) 'md5sum_phs_config = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"'
write (unit_fix, *) "sqrts = 100.00000000000000"
write (unit_fix, *) "m_threshold_s = 50.000000000000000"
write (unit_fix, *) "m_threshold_t = 100.00000000000000"
write (unit_fix, *) "off_shell = 2"
write (unit_fix, *) "t_channel = 6"
write (unit_fix, *) "keep_nonresonant = F"
write (unit_fix, *) ""
write (unit_fix, *) " grove"
write (unit_fix, *) " tree 3 7"
write (unit_fix, *) " tree 3 7"
write (unit_fix, *) " map 3 s_channel -24"
write (unit_fix, *) " tree 5 7"
write (unit_fix, *) " tree 3 7"
write (unit_fix, *) " map 3 s_channel -24"
write (unit_fix, *) " map 7 s_channel 23"
write (unit_fix, *) " tree 5 7"
write (unit_fix, *) " map 7 s_channel 25"
write (unit_fix, *) " tree 3 11"
write (unit_fix, *) " map 3 s_channel -24"
close (unit_fix)
write (u, "(A)") "* Read phase-space file 'phs_forest_2.phs'"
call syntax_phs_forest_init ()
process_id = "foo"
filename = "phs_forest_2.phs"
call forest%read (filename, process_id, 2, 3, model, found_process)
write (u, "(A)")
write (u, "(A)") "* Extract resonance history set"
write (u, "(A)")
call forest%extract_resonance_history_set (res_set)
call res_set%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call model%final ()
call forest%final ()
call syntax_phs_forest_final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_forest_2"
end subroutine phs_forest_2
@ %def phs_forest_2
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{Finding phase space parameterizations}
If the phase space configuration is not found in the appropriate file,
we should generate one.
The idea is to construct all Feynman diagrams subject to certain
constraints which eliminate everything that is probably irrelevant for
the integration. These Feynman diagrams (cascades) are grouped in
groves by finding equivalence classes related by symmetry and ordered
with respect to their importance (resonances). Finally, the result
(or part of it) is written to file and used for the integration.
This module may eventually disappear and be replaced by CAML code.
In particular, we need here a set of Feynman rules (vertices with
particle codes, but not the factors). Thus, the module works for the
Standard Model only.
Note that this module is stand-alone, it communicates to the main
program only via the generated ASCII phase-space configuration file.
<<[[cascades.f90]]>>=
<<File header>>
module cascades
<<Use kinds>>
use kinds, only: TC, i8, i32
<<Use strings>>
<<Use debug>>
use physics_defs, only: SCALAR, SPINOR, VECTOR, VECTORSPINOR, TENSOR
use physics_defs, only: UNDEFINED
use model_data
use flavors
use resonances, only: resonance_info_t
use resonances, only: resonance_history_t
use resonances, only: resonance_history_set_t
use phs_forests
<<Standard module head>>
<<Cascades: public>>
<<Cascades: parameters>>
<<Cascades: types>>
<<Cascades: interfaces>>
interface
<<Cascades: sub interfaces>>
end interface
end module cascades
@ %def cascades
@
<<[[cascades_sub.f90]]>>=
<<File header>>
submodule (cascades) cascades_s
use io_units
use constants, only: one
use format_defs, only: FMT_12, FMT_19
use numeric_utils
use diagnostics
use hashes
use sorting
use lorentz
!!! Intel oneAPI 2022/23 regression workaround
use resonances, only: resonance_info_t
use resonances, only: resonance_history_t
use resonances, only: resonance_history_set_t
implicit none
contains
<<Cascades: procedures>>
end submodule cascades_s
@ %def cascades_s
@
\subsection{The mapping modes}
The valid mapping modes, to be used below. We will make use of the convention
that mappings of internal particles have a positive value. Only for positive
values, the flavor code is propagated when combining cascades.
<<Mapping modes>>=
integer, parameter :: &
& EXTERNAL_PRT = -1, &
& NO_MAPPING = 0, S_CHANNEL = 1, T_CHANNEL = 2, U_CHANNEL = 3, &
& RADIATION = 4, COLLINEAR = 5, INFRARED = 6, &
& STEP_MAPPING_E = 11, STEP_MAPPING_H = 12, &
& ON_SHELL = 99
@ %def EXTERNAL_PRT
@ %def NO_MAPPING S_CHANNEL T_CHANNEL U_CHANNEL
@ %def RADIATION COLLINEAR INFRARED
@ %def STEP_MAPPING_E STEP_MAPPING_H
@ %def ON_SHELL
<<Cascades: parameters>>=
<<Mapping modes>>
@
\subsection{The cascade type}
A cascade is essentially the same as a decay tree (both definitions
may be merged in a later version). It contains a linked tree of
nodes, each of which representing an internal particle. In contrast
to decay trees, each node has a definite particle code. These nodes
need not be modified, therefore we can use pointers and do not have to
copy them. Thus, physically each cascades has only a single node, the
mother particle. However, to be able to compare trees quickly, we
store in addition an array of binary codes which is always sorted in
ascending order. This is accompanied by a corresponding list of
particle codes. The index is the location of the corresponding
cascade in the cascade set, this may be used to access the daughters
directly.
The real mass is the particle mass belonging to the particle code.
The minimal mass is the sum of the real masses of all its daughters;
this is the kinematical cutoff. The effective mass may be zero if the
particle mass is below a certain threshold; it may be the real mass if
the particle is resonant; or it may be some other value.
The logical [[t_channel]] is set if this a $t$-channel line, while
[[initial]] is true only for an initial particle. Note that both
initial particles are also [[t_channel]] by definition, and that they
are distinguished by the direction of the tree: One of them decays
and is the root of the tree, while the other one is one of the leaves.
The cascade is a list of nodes (particles) which are linked via the
[[daughter]] entries. The node is the mother particle of
the decay cascade. Much of the information in the nodes is repeated
in arrays, to be accessible more easily. The arrays will be kept
sorted by binary codes.
The counter [[n_off_shell]] is increased for each internal line that
is neither resonant nor log-enhanced. It is set to zero if the
current line is resonant, since this implies on-shell particle production
and subsequent decay.
The counter [[n_t_channel]] is non-negative once an initial particle
is included in the tree: then, it counts the number of $t$-channel lines.
The [[multiplicity]] is the number of branchings to follow until all
daughters are on-shell. A resonant or non-decaying particle has
multiplicity one. Merging nodes, the multiplicities add unless the
mother is a resonance. An initial or final node has multiplicity
zero.
The arrays correspond to the subnode tree [[tree]] of the current
cascade. PDG codes are stored only for those positions which are
resonant, with the exception of the last entry, i.e., the current node.
Other positions, in particular external legs, are assigned undefined
PDG code.
A cascade is uniquely identified by its tree, the tree of PDG codes,
and the tree of mappings. The tree of resonances is kept only to mask
the PDG tree as described above.
<<Cascades: types>>=
type :: cascade_t
private
! counters
integer :: index = 0
integer :: grove = 0
! status
logical :: active = .false.
logical :: complete = .false.
logical :: incoming = .false.
! this node
integer(TC) :: bincode = 0
type(flavor_t) :: flv
integer :: pdg = UNDEFINED
logical :: is_vector = .false.
real(default) :: m_min = 0
real(default) :: m_rea = 0
real(default) :: m_eff = 0
integer :: mapping = NO_MAPPING
logical :: on_shell = .false.
logical :: resonant = .false.
logical :: log_enhanced = .false.
logical :: t_channel = .false.
! global tree properties
integer :: multiplicity = 0
integer :: internal = 0
integer :: n_off_shell = 0
integer :: n_resonances = 0
integer :: n_log_enhanced = 0
integer :: n_t_channel = 0
integer :: res_hash = 0
! the sub-node tree
integer :: depth = 0
integer(TC), dimension(:), allocatable :: tree
integer, dimension(:), allocatable :: tree_pdg
integer, dimension(:), allocatable :: tree_mapping
logical, dimension(:), allocatable :: tree_resonant
! branch connections
logical :: has_children = .false.
type(cascade_t), pointer :: daughter1 => null ()
type(cascade_t), pointer :: daughter2 => null ()
type(cascade_t), pointer :: mother => null ()
! next in list
type(cascade_t), pointer :: next => null ()
contains
<<Cascades: cascade: TBP>>
end type cascade_t
@ %def cascade_t
<<Cascades: procedures>>=
subroutine cascade_init (cascade, depth)
type(cascade_t), intent(out) :: cascade
integer, intent(in) :: depth
integer, save :: index = 0
index = cascade_index ()
cascade%index = index
cascade%depth = depth
cascade%active = .true.
allocate (cascade%tree (depth))
allocate (cascade%tree_pdg (depth))
allocate (cascade%tree_mapping (depth))
allocate (cascade%tree_resonant (depth))
end subroutine cascade_init
@ %def cascade_init
@ Keep and increment a global index
<<Cascades: procedures>>=
function cascade_index (seed) result (index)
integer :: index
integer, intent(in), optional :: seed
integer, save :: i = 0
if (present (seed)) i = seed
i = i + 1
index = i
end function cascade_index
@ %def cascade_index
@ We need three versions of writing cascades. This goes to the
phase-space file.
For t/u channel mappings, we use the absolute value of the PDG code.
<<Cascades: procedures>>=
subroutine cascade_write_file_format (cascade, model, unit)
type(cascade_t), intent(in) :: cascade
class(model_data_t), intent(in), target :: model
integer, intent(in), optional :: unit
type(flavor_t) :: flv
integer :: u, i
2 format(3x,A,1x,I3,1x,A,1x,I9,1x,'!',1x,A)
u = given_output_unit (unit); if (u < 0) return
call write_reduced (cascade%tree, u)
write (u, "(A)")
do i = 1, cascade%depth
call flv%init (cascade%tree_pdg(i), model)
select case (cascade%tree_mapping(i))
case (NO_MAPPING, EXTERNAL_PRT)
case (S_CHANNEL)
write(u,2) 'map', &
cascade%tree(i), 's_channel', cascade%tree_pdg(i), &
char (flv%get_name ())
case (T_CHANNEL)
write(u,2) 'map', &
cascade%tree(i), 't_channel', abs (cascade%tree_pdg(i)), &
char (flv%get_name ())
case (U_CHANNEL)
write(u,2) 'map', &
cascade%tree(i), 'u_channel', abs (cascade%tree_pdg(i)), &
char (flv%get_name ())
case (RADIATION)
write(u,2) 'map', &
cascade%tree(i), 'radiation', cascade%tree_pdg(i), &
char (flv%get_name ())
case (COLLINEAR)
write(u,2) 'map', &
cascade%tree(i), 'collinear', cascade%tree_pdg(i), &
char (flv%get_name ())
case (INFRARED)
write(u,2) 'map', &
cascade%tree(i), 'infrared ', cascade%tree_pdg(i), &
char (flv%get_name ())
case (ON_SHELL)
write(u,2) 'map', &
cascade%tree(i), 'on_shell ', cascade%tree_pdg(i), &
char (flv%get_name ())
case default
call msg_bug (" Impossible mapping mode encountered")
end select
end do
contains
subroutine write_reduced (array, unit)
integer(TC), dimension(:), intent(in) :: array
integer, intent(in) :: unit
integer :: i
write (u, "(3x,A,1x)", advance="no") "tree"
do i = 1, size (array)
if (decay_level (array(i)) > 1) then
write (u, "(1x,I0)", advance="no") array(i)
end if
end do
end subroutine write_reduced
elemental function decay_level (k) result (l)
integer(TC), intent(in) :: k
integer :: l
integer :: i
l = 0
do i = 0, bit_size(k) - 1
if (btest(k,i)) l = l + 1
end do
end function decay_level
subroutine start_comment (u)
integer, intent(in) :: u
write(u, '(1x,A)', advance='no') '!'
end subroutine start_comment
end subroutine cascade_write_file_format
@ %def cascade_write_file_format
@ This creates metapost source for graphical display:
<<Cascades: procedures>>=
subroutine cascade_write_graph_format (cascade, count, unit)
type(cascade_t), intent(in) :: cascade
integer, intent(in) :: count
integer, intent(in), optional :: unit
integer :: u
integer(TC) :: mask
type(string_t) :: left_str, right_str
u = given_output_unit (unit); if (u < 0) return
mask = 2**((cascade%depth+3)/2) - 1
left_str = ""
right_str = ""
write (u, '(A)') "\begin{minipage}{105pt}"
write (u, '(A)') "\vspace{30pt}"
write (u, '(A)') "\begin{center}"
write (u, '(A)') "\begin{fmfgraph*}(55,55)"
call graph_write (cascade, mask)
write (u, '(A)') "\fmfleft{" // char (extract (left_str, 2)) // "}"
write (u, '(A)') "\fmfright{" // char (extract (right_str, 2)) // "}"
write (u, '(A)') "\end{fmfgraph*}\\"
write (u, '(A,I5,A)') "\fbox{$", count, "$}"
write (u, '(A)') "\end{center}"
write (u, '(A)') "\end{minipage}"
write (u, '(A)') "%"
contains
recursive subroutine graph_write (cascade, mask, reverse)
type(cascade_t), intent(in) :: cascade
integer(TC), intent(in) :: mask
logical, intent(in), optional :: reverse
type(flavor_t) :: anti
logical :: rev
rev = .false.; if (present(reverse)) rev = reverse
if (cascade%has_children) then
if (.not.rev) then
call vertex_write (cascade, cascade%daughter1, mask)
call vertex_write (cascade, cascade%daughter2, mask)
else
call vertex_write (cascade, cascade%daughter2, mask, .true.)
call vertex_write (cascade, cascade%daughter1, mask, .true.)
end if
if (cascade%complete) then
call vertex_write (cascade, cascade%mother, mask, .true.)
write (u, '(A,I0,A)') "\fmfv{d.shape=square}{v0}"
end if
else
if (cascade%incoming) then
anti = cascade%flv%anti ()
call external_write (cascade%bincode, anti%get_tex_name (), &
left_str)
else
call external_write (cascade%bincode, cascade%flv%get_tex_name (), &
right_str)
end if
end if
end subroutine graph_write
recursive subroutine vertex_write (cascade, daughter, mask, reverse)
type(cascade_t), intent(in) :: cascade, daughter
integer(TC), intent(in) :: mask
logical, intent(in), optional :: reverse
integer :: bincode
if (cascade%complete) then
bincode = 0
else
bincode = cascade%bincode
end if
call graph_write (daughter, mask, reverse)
if (daughter%has_children) then
call line_write (bincode, daughter%bincode, daughter%flv, &
mapping=daughter%mapping)
else
call line_write (bincode, daughter%bincode, daughter%flv)
end if
end subroutine vertex_write
subroutine line_write (i1, i2, flv, mapping)
integer(TC), intent(in) :: i1, i2
type(flavor_t), intent(in) :: flv
integer, intent(in), optional :: mapping
integer :: k1, k2
type(string_t) :: prt_type
select case (flv%get_spin_type ())
case (SCALAR); prt_type = "plain"
case (SPINOR); prt_type = "fermion"
case (VECTOR); prt_type = "boson"
case (VECTORSPINOR); prt_type = "fermion"
case (TENSOR); prt_type = "dbl_wiggly"
case default; prt_type = "dashes"
end select
if (flv%is_antiparticle ()) then
k1 = i2; k2 = i1
else
k1 = i1; k2 = i2
end if
if (present (mapping)) then
select case (mapping)
case (S_CHANNEL)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=blue,lab=\sm\blue$" // &
& char (flv%get_tex_name ()) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (T_CHANNEL, U_CHANNEL)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=cyan,lab=\sm\cyan$" // &
& char (flv%get_tex_name ()) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (RADIATION)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=green,lab=\sm\green$" // &
& char (flv%get_tex_name ()) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (COLLINEAR)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=magenta,lab=\sm\magenta$" // &
& char (flv%get_tex_name ()) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (INFRARED)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=red,lab=\sm\red$" // &
& char (flv%get_tex_name ()) // "$}" // &
& "{v", k1, ",v", k2, "}"
case default
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=black}" // &
& "{v", k1, ",v", k2, "}"
end select
else
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& "}" // &
& "{v", k1, ",v", k2, "}"
end if
end subroutine line_write
subroutine external_write (bincode, name, ext_str)
integer(TC), intent(in) :: bincode
type(string_t), intent(in) :: name
type(string_t), intent(inout) :: ext_str
character(len=20) :: str
write (str, '(A2,I0)') ",v", bincode
ext_str = ext_str // trim (str)
write (u, '(A,I0,A,I0,A)') "\fmflabel{\sm$" &
// char (name) &
// "\,(", bincode, ")" &
// "$}{v", bincode, "}"
end subroutine external_write
end subroutine cascade_write_graph_format
@ %def cascade_write_graph_format
@ This is for screen/debugging output:
<<Cascades: procedures>>=
subroutine cascade_write (cascade, unit)
type(cascade_t), intent(in) :: cascade
integer, intent(in), optional :: unit
integer :: u
character(9) :: depth
u = given_output_unit (unit); if (u < 0) return
write (u, "(A,(1x,I7))") 'Cascade #', cascade%index
write (u, "(A,(1x,I7))") ' Grove: #', cascade%grove
write (u, "(A,3(1x,L1))") ' act/cmp/inc: ', &
cascade%active, cascade%complete, cascade%incoming
write (u, "(A,I0)") ' Bincode: ', cascade%bincode
write (u, "(A)", advance="no") ' Flavor: '
call cascade%flv%write (unit)
write (u, "(A,I9)") ' Active flavor:', cascade%pdg
write (u, "(A,L1)") ' Is vector: ', cascade%is_vector
write (u, "(A,3(1x," // FMT_19 // "))") ' Mass (m/r/e): ', &
cascade%m_min, cascade%m_rea, cascade%m_eff
write (u, "(A,I1)") ' Mapping: ', cascade%mapping
write (u, "(A,3(1x,L1))") ' res/log/tch: ', &
cascade%resonant, cascade%log_enhanced, cascade%t_channel
write (u, "(A,(1x,I7))") ' Multiplicity: ', cascade%multiplicity
write (u, "(A,2(1x,I7))") ' n intern/off: ', &
cascade%internal, cascade%n_off_shell
write (u, "(A,3(1x,I7))") ' n res/log/tch:', &
cascade%n_resonances, cascade%n_log_enhanced, cascade%n_t_channel
write (u, "(A,I7)") ' Depth: ', cascade%depth
write (depth, "(I7)") cascade%depth
write (u, "(A," // depth // "(1x,I7))") &
' Tree: ', cascade%tree
write (u, "(A," // depth // "(1x,I7))") &
' Tree(PDG): ', cascade%tree_pdg
write (u, "(A," // depth // "(1x,I7))") &
' Tree(mapping):', cascade%tree_mapping
write (u, "(A," // depth // "(1x,L1))") &
' Tree(res): ', cascade%tree_resonant
if (cascade%has_children) then
write (u, "(A,I7,1x,I7)") ' Daughter1/2: ', &
cascade%daughter1%index, cascade%daughter2%index
end if
if (associated (cascade%mother)) then
write (u, "(A,I7)") ' Mother: ', cascade%mother%index
end if
end subroutine cascade_write
@ %def cascade_write
@
\subsection{Creating new cascades}
This initializes a single-particle cascade (external, final state).
The PDG entry in the tree is set undefined because the cascade is not
resonant. However, the flavor entry is set, so the cascade flavor
is identified nevertheless.
<<Cascades: procedures>>=
subroutine cascade_init_outgoing (cascade, flv, pos, m_thr)
type(cascade_t), intent(out) :: cascade
type(flavor_t), intent(in) :: flv
integer, intent(in) :: pos
real(default), intent(in) :: m_thr
call cascade_init (cascade, 1)
cascade%bincode = ibset (0_TC, pos-1)
cascade%flv = flv
cascade%pdg = cascade%flv%get_pdg ()
cascade%is_vector = flv%get_spin_type () == VECTOR
cascade%m_min = flv%get_mass ()
cascade%m_rea = cascade%m_min
if (cascade%m_rea >= m_thr) then
cascade%m_eff = cascade%m_rea
end if
cascade%on_shell = .true.
cascade%multiplicity = 1
cascade%tree(1) = cascade%bincode
cascade%tree_pdg(1) = cascade%pdg
cascade%tree_mapping(1) = EXTERNAL_PRT
cascade%tree_resonant(1) = .false.
end subroutine cascade_init_outgoing
@ %def cascade_init_outgoing
@ The same for an incoming line:
<<Cascades: procedures>>=
subroutine cascade_init_incoming (cascade, flv, pos, m_thr)
type(cascade_t), intent(out) :: cascade
type(flavor_t), intent(in) :: flv
integer, intent(in) :: pos
real(default), intent(in) :: m_thr
call cascade_init (cascade, 1)
cascade%incoming = .true.
cascade%bincode = ibset (0_TC, pos-1)
cascade%flv = flv%anti ()
cascade%pdg = cascade%flv%get_pdg ()
cascade%is_vector = flv%get_spin_type () == VECTOR
cascade%m_min = flv%get_mass ()
cascade%m_rea = cascade%m_min
if (cascade%m_rea >= m_thr) then
cascade%m_eff = cascade%m_rea
end if
cascade%on_shell = .true.
cascade%n_t_channel = 0
cascade%n_off_shell = 0
cascade%tree(1) = cascade%bincode
cascade%tree_pdg(1) = cascade%pdg
cascade%tree_mapping(1) = EXTERNAL_PRT
cascade%tree_resonant(1) = .false.
end subroutine cascade_init_incoming
@ %def cascade_init_outgoing
@
\subsection{Tools}
This function returns true if the two cascades share no common
external particle. This is a requirement for joining them.
<<Cascades: interfaces>>=
interface operator(.disjunct.)
module procedure cascade_disjunct
end interface
<<Cascades: sub interfaces>>=
module function cascade_disjunct (cascade1, cascade2) result (flag)
logical :: flag
type(cascade_t), intent(in) :: cascade1, cascade2
end function cascade_disjunct
<<Cascades: procedures>>=
module function cascade_disjunct (cascade1, cascade2) result (flag)
logical :: flag
type(cascade_t), intent(in) :: cascade1, cascade2
flag = iand (cascade1%bincode, cascade2%bincode) == 0
end function cascade_disjunct
@ %def cascade_disjunct
@ %def .disjunct.
@ Compute a hash code for the resonance pattern of a cascade. We count the
number of times each particle appears as a resonance.
We pack the PDG codes of the resonances in two arrays (s-channel and
t-channel), sort them both, concatenate the results, transfer to
[[i8]] integers, and compute the hash code from this byte stream.
For t/u-channel, we remove the sign for antiparticles since this is not
well-defined.
<<Cascades: procedures>>=
subroutine cascade_assign_resonance_hash (cascade)
type(cascade_t), intent(inout) :: cascade
integer(i8), dimension(1) :: mold
cascade%res_hash = hash (transfer &
([sort (pack (cascade%tree_pdg, &
cascade%tree_resonant)), &
sort (pack (abs (cascade%tree_pdg), &
cascade%tree_mapping == T_CHANNEL .or. &
cascade%tree_mapping == U_CHANNEL))], &
mold))
end subroutine cascade_assign_resonance_hash
@ %def cascade_assign_resonance_hash
@
\subsection{Hash entries for cascades}
We will set up a hash array which contains keys of and pointers to
cascades. We hold a list of cascade (pointers) within each bucket.
This is not for collision resolution, but for keeping similar, but
unequal cascades together.
<<Cascades: types>>=
type :: cascade_p
type(cascade_t), pointer :: cascade => null ()
type(cascade_p), pointer :: next => null ()
end type cascade_p
@ %def cascade_p
@ Here is the bucket or hash entry type:
<<Cascades: types>>=
type :: hash_entry_t
integer(i32) :: hashval = 0
integer(i8), dimension(:), allocatable :: key
type(cascade_p), pointer :: first => null ()
type(cascade_p), pointer :: last => null ()
end type hash_entry_t
@ %def hash_entry_t
<<Cascades: public>>=
public :: hash_entry_init
<<Cascades: sub interfaces>>=
module subroutine hash_entry_init (entry, entry_in)
type(hash_entry_t), intent(out) :: entry
type(hash_entry_t), intent(in) :: entry_in
end subroutine hash_entry_init
<<Cascades: procedures>>=
module subroutine hash_entry_init (entry, entry_in)
type(hash_entry_t), intent(out) :: entry
type(hash_entry_t), intent(in) :: entry_in
type(cascade_p), pointer :: casc_iter, casc_copy
entry%hashval = entry_in%hashval
entry%key = entry_in%key
casc_iter => entry_in%first
do while (associated (casc_iter))
allocate (casc_copy)
casc_copy = casc_iter
casc_copy%next => null ()
if (associated (entry%first)) then
entry%last%next => casc_copy
else
entry%first => casc_copy
end if
entry%last => casc_copy
casc_iter => casc_iter%next
end do
end subroutine hash_entry_init
@ %def hash_entry_init
@ Finalize: just deallocate the list; the contents are just pointers.
<<Cascades: procedures>>=
subroutine hash_entry_final (hash_entry)
type(hash_entry_t), intent(inout) :: hash_entry
type(cascade_p), pointer :: current
do while (associated (hash_entry%first))
current => hash_entry%first
hash_entry%first => current%next
deallocate (current)
end do
end subroutine hash_entry_final
@ %def hash_entry_final
@ Output: concise format for debugging, just list cascade indices.
<<Cascades: procedures>>=
subroutine hash_entry_write (hash_entry, unit)
type(hash_entry_t), intent(in) :: hash_entry
integer, intent(in), optional :: unit
type(cascade_p), pointer :: current
integer :: u, i
u = given_output_unit (unit); if (u < 0) return
write (u, "(1x,A)", advance="no") "Entry:"
do i = 1, size (hash_entry%key)
write (u, "(1x,I0)", advance="no") hash_entry%key(i)
end do
write (u, "(1x,A)", advance="no") "->"
current => hash_entry%first
do while (associated (current))
write (u, "(1x,I7)", advance="no") current%cascade%index
current => current%next
end do
write (u, *)
end subroutine hash_entry_write
@ %def hash_entry_write
@ This function adds a cascade pointer to the bucket. If [[ok]] is
present, check first if it is already there and return failure if yes.
If [[cascade_ptr]] is also present, set it to the current cascade if
successful. If not, set it to the cascade that is already there.
<<Cascades: procedures>>=
subroutine hash_entry_add_cascade_ptr (hash_entry, cascade, ok, cascade_ptr)
type(hash_entry_t), intent(inout) :: hash_entry
type(cascade_t), intent(in), target :: cascade
logical, intent(out), optional :: ok
type(cascade_t), optional, pointer :: cascade_ptr
type(cascade_p), pointer :: current
if (present (ok)) then
call hash_entry_check_cascade (hash_entry, cascade, ok, cascade_ptr)
if (.not. ok) return
end if
allocate (current)
current%cascade => cascade
if (associated (hash_entry%last)) then
hash_entry%last%next => current
else
hash_entry%first => current
end if
hash_entry%last => current
end subroutine hash_entry_add_cascade_ptr
@ %def hash_entry_add_cascade_ptr
@ This function checks whether a cascade is already in the bucket.
For incomplete cascades, we look for an exact match. It should suffice
to verify the tree, the PDG codes, and the mapping modes. This is the
information that is written to the phase space file.
For complete cascades, we ignore the PDG code at positions with
mappings infrared, collinear, or t/u-channel. Thus a cascade which is
distinguished only by PDG code at such places, is flagged existent.
If the convention is followed that light particles come before heavier
ones (in the model definition), this ensures that the lightest
particle is kept in the appropriate place, corresponding to the
strongest peak.
For external cascades (incoming/outgoing) we take the PDG code into
account even though it is zeroed in the PDG-code tree.
<<Cascades: procedures>>=
subroutine hash_entry_check_cascade (hash_entry, cascade, ok, cascade_ptr)
type(hash_entry_t), intent(in), target :: hash_entry
type(cascade_t), intent(in), target :: cascade
logical, intent(out) :: ok
type(cascade_t), optional, pointer :: cascade_ptr
type(cascade_p), pointer :: current
integer, dimension(:), allocatable :: tree_pdg
ok = .true.
allocate (tree_pdg (size (cascade%tree_pdg)))
if (cascade%complete) then
where (cascade%tree_mapping == INFRARED .or. &
cascade%tree_mapping == COLLINEAR .or. &
cascade%tree_mapping == T_CHANNEL .or. &
cascade%tree_mapping == U_CHANNEL)
tree_pdg = 0
elsewhere
tree_pdg = cascade%tree_pdg
end where
else
tree_pdg = cascade%tree_pdg
end if
current => hash_entry%first
do while (associated (current))
if (current%cascade%depth == cascade%depth) then
if (all (current%cascade%tree == cascade%tree)) then
if (all (current%cascade%tree_mapping == cascade%tree_mapping)) &
then
if (all (current%cascade%tree_pdg .match. tree_pdg)) then
if (present (cascade_ptr)) cascade_ptr => current%cascade
ok = .false.; return
end if
end if
end if
end if
current => current%next
end do
if (present (cascade_ptr)) cascade_ptr => cascade
end subroutine hash_entry_check_cascade
@ %def hash_entry_check_cascade
@ For PDG codes, we specify that the undefined code matches any code.
This is already defined for flavor objects, but here we need it for
the codes themselves.
<<Cascades: interfaces>>=
interface operator(.match.)
module procedure pdg_match
end interface
<<Cascades: sub interfaces>>=
elemental module function pdg_match (pdg1, pdg2) result (flag)
logical :: flag
integer(TC), intent(in) :: pdg1, pdg2
end function pdg_match
<<Cascades: procedures>>=
elemental module function pdg_match (pdg1, pdg2) result (flag)
logical :: flag
integer(TC), intent(in) :: pdg1, pdg2
select case (pdg1)
case (0)
flag = .true.
case default
select case (pdg2)
case (0)
flag = .true.
case default
flag = pdg1 == pdg2
end select
end select
end function pdg_match
@ %def .match.
@
\subsection{The cascade set}
The cascade set will later be transformed into the decay forest. It
is set up as a linked list. In addition to the usual [[first]] and
[[last]] pointers, there is a [[first_t]] pointer which points to the
first t-channel cascade (after all s-channel cascades), and a
[[first_k]] pointer which points to the first final cascade (with a
keystone).
As an auxiliary device, the object contains a hash array with
associated parameters where an additional pointer is stored for each
cascade. The keys are made from the relevant cascade data. This hash
is used for fast detection (and thus avoidance) of double entries in
the cascade list.
<<Cascades: public>>=
public :: cascade_set_t
<<Cascades: types>>=
type :: cascade_set_t
private
class(model_data_t), pointer :: model
integer :: n_in, n_out, n_tot
type(flavor_t), dimension(:,:), allocatable :: flv
integer :: depth_out, depth_tot
real(default) :: sqrts = 0
real(default) :: m_threshold_s = 0
real(default) :: m_threshold_t = 0
integer :: off_shell = 0
integer :: t_channel = 0
logical :: keep_nonresonant
integer :: n_groves = 0
! The cascade list
type(cascade_t), pointer :: first => null ()
type(cascade_t), pointer :: last => null ()
type(cascade_t), pointer :: first_t => null ()
type(cascade_t), pointer :: first_k => null ()
! The hashtable
integer :: n_entries = 0
real :: fill_ratio = 0
integer :: n_entries_max = 0
integer(i32) :: mask = 0
logical :: fatal_beam_decay = .true.
type(hash_entry_t), dimension(:), allocatable :: entry
end type cascade_set_t
@ %def cascade_set_t
@
<<Cascades: public>>=
interface cascade_set_init
module procedure cascade_set_init_base
module procedure cascade_set_init_from_cascade
end interface
@ %def cascade_set_init
@ This might be broken. Test before using.
<<Cascades: sub interfaces>>=
module subroutine cascade_set_init_from_cascade &
(cascade_set, cascade_set_in)
type(cascade_set_t), intent(out) :: cascade_set
type(cascade_set_t), intent(in), target :: cascade_set_in
end subroutine cascade_set_init_from_cascade
<<Cascades: procedures>>=
module subroutine cascade_set_init_from_cascade &
(cascade_set, cascade_set_in)
type(cascade_set_t), intent(out) :: cascade_set
type(cascade_set_t), intent(in), target :: cascade_set_in
type(cascade_t), pointer :: casc_iter, casc_copy
cascade_set%model => cascade_set_in%model
cascade_set%n_in = cascade_set_in%n_in
cascade_set%n_out = cascade_set_in%n_out
cascade_set%n_tot = cascade_set_in%n_tot
cascade_set%flv = cascade_set_in%flv
cascade_set%depth_out = cascade_set_in%depth_out
cascade_set%depth_tot = cascade_set_in%depth_tot
cascade_set%sqrts = cascade_set_in%sqrts
cascade_set%m_threshold_s = cascade_set_in%m_threshold_s
cascade_set%m_threshold_t = cascade_set_in%m_threshold_t
cascade_set%off_shell = cascade_set_in%off_shell
cascade_set%t_channel = cascade_set_in%t_channel
cascade_set%keep_nonresonant = cascade_set_in%keep_nonresonant
cascade_set%n_groves = cascade_set_in%n_groves
casc_iter => cascade_set_in%first
do while (associated (casc_iter))
allocate (casc_copy)
casc_copy = casc_iter
casc_copy%next => null ()
if (associated (cascade_set%first)) then
cascade_set%last%next => casc_copy
else
cascade_set%first => casc_copy
end if
cascade_set%last => casc_copy
casc_iter => casc_iter%next
end do
cascade_set%n_entries = cascade_set_in%n_entries
cascade_set%fill_ratio = cascade_set_in%fill_ratio
cascade_set%n_entries_max = cascade_set_in%n_entries_max
cascade_set%mask = cascade_set_in%mask
cascade_set%fatal_beam_decay = cascade_set_in%fatal_beam_decay
allocate (cascade_set%entry (0:cascade_set%mask))
cascade_set%entry = cascade_set_in%entry
end subroutine cascade_set_init_from_cascade
@ %def cascade_set_init_from_cascade
@ Return true if there are cascades which are active and complete, so
the phase space file would be nonempty.
<<Cascades: public>>=
public :: cascade_set_is_valid
<<Cascades: sub interfaces>>=
module function cascade_set_is_valid (cascade_set) result (flag)
logical :: flag
type(cascade_set_t), intent(in) :: cascade_set
end function cascade_set_is_valid
<<Cascades: procedures>>=
module function cascade_set_is_valid (cascade_set) result (flag)
logical :: flag
type(cascade_set_t), intent(in) :: cascade_set
type(cascade_t), pointer :: cascade
flag = .false.
cascade => cascade_set%first_k
do while (associated (cascade))
if (cascade%active .and. cascade%complete) then
flag = .true.
return
end if
cascade => cascade%next
end do
end function cascade_set_is_valid
@ %def cascade_set_is_valid
@ The initializer sets up the hash table with some initial size
guessed by looking at the number of external particles. We choose 256
for 3 external particles and a factor of 4 for each additional
particle, limited at $2^{30}$=1G.
<<Cascades: parameters>>=
real, parameter, public :: CASCADE_SET_FILL_RATIO = 0.1
<<Cascades: sub interfaces>>=
module subroutine cascade_set_init_base (cascade_set, model, &
n_in, n_out, phs_par, fatal_beam_decay, flv)
type(cascade_set_t), intent(out) :: cascade_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
type(flavor_t), dimension(:,:), intent(in), optional :: flv
end subroutine cascade_set_init_base
<<Cascades: procedures>>=
module subroutine cascade_set_init_base (cascade_set, model, &
n_in, n_out, phs_par, fatal_beam_decay, flv)
type(cascade_set_t), intent(out) :: cascade_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
type(flavor_t), dimension(:,:), intent(in), optional :: flv
integer :: size_guess
integer :: i, j
cascade_set%model => model
cascade_set%n_in = n_in
cascade_set%n_out = n_out
cascade_set%n_tot = n_in + n_out
if (present (flv)) then
allocate (cascade_set%flv (size (flv, 1), size (flv, 2)))
do i = 1, size (flv, 2)
do j = 1, size (flv, 1)
call cascade_set%flv(j,i)%init (flv(j,i)%get_pdg (), model)
end do
end do
end if
select case (n_in)
case (1); cascade_set%depth_out = 2 * n_out - 3
case (2); cascade_set%depth_out = 2 * n_out - 1
end select
cascade_set%depth_tot = 2 * cascade_set%n_tot - 3
cascade_set%sqrts = phs_par%sqrts
cascade_set%m_threshold_s = phs_par%m_threshold_s
cascade_set%m_threshold_t = phs_par%m_threshold_t
cascade_set%off_shell = phs_par%off_shell
cascade_set%t_channel = phs_par%t_channel
cascade_set%keep_nonresonant = phs_par%keep_nonresonant
cascade_set%fill_ratio = CASCADE_SET_FILL_RATIO
size_guess = ishft (256, min (2 * (cascade_set%n_tot - 3), 22))
cascade_set%n_entries_max = size_guess * cascade_set%fill_ratio
cascade_set%mask = size_guess - 1
allocate (cascade_set%entry (0:cascade_set%mask))
cascade_set%fatal_beam_decay = fatal_beam_decay
end subroutine cascade_set_init_base
@ %def cascade_set_init_base
@ The finalizer has to delete both the hash and the list.
<<Cascades: public>>=
public :: cascade_set_final
<<Cascades: sub interfaces>>=
module subroutine cascade_set_final (cascade_set)
type(cascade_set_t), intent(inout), target :: cascade_set
end subroutine cascade_set_final
<<Cascades: procedures>>=
module subroutine cascade_set_final (cascade_set)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), pointer :: current
integer :: i
if (allocated (cascade_set%entry)) then
do i = 0, cascade_set%mask
call hash_entry_final (cascade_set%entry(i))
end do
deallocate (cascade_set%entry)
end if
do while (associated (cascade_set%first))
current => cascade_set%first
cascade_set%first => cascade_set%first%next
deallocate (current)
end do
end subroutine cascade_set_final
@ %def cascade_set_final
@ Write the process in ASCII format, in columns that are headed by the
corresponding bincode.
<<Cascades: public>>=
public :: cascade_set_write_process_bincode_format
<<Cascades: sub interfaces>>=
module subroutine cascade_set_write_process_bincode_format &
(cascade_set, unit)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
end subroutine cascade_set_write_process_bincode_format
<<Cascades: procedures>>=
module subroutine cascade_set_write_process_bincode_format &
(cascade_set, unit)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
integer, dimension(:), allocatable :: bincode, field_width
integer :: n_in, n_out, n_tot, n_flv
integer :: u, f, i, bc
character(20) :: str
type(string_t) :: fmt_head
type(string_t), dimension(:), allocatable :: fmt_proc
u = given_output_unit (unit); if (u < 0) return
if (.not. allocated (cascade_set%flv)) return
write (u, "('!',1x,A)") "List of subprocesses with particle bincodes:"
n_in = cascade_set%n_in
n_out = cascade_set%n_out
n_tot = cascade_set%n_tot
n_flv = size (cascade_set%flv, 2)
allocate (bincode (n_tot), field_width (n_tot), fmt_proc (n_tot))
bc = 1
do i = 1, n_out
bincode(n_in + i) = bc
bc = 2 * bc
end do
do i = n_in, 1, -1
bincode(i) = bc
bc = 2 * bc
end do
do i = 1, n_tot
write (str, "(I0)") bincode(i)
field_width(i) = len_trim (str)
do f = 1, n_flv
field_width(i) = max (field_width(i), &
len (cascade_set%flv(i,f)%get_name ()))
end do
end do
fmt_head = "('!'"
do i = 1, n_tot
fmt_head = fmt_head // ",1x,"
fmt_proc(i) = "(1x,"
write (str, "(I0)") field_width(i)
fmt_head = fmt_head // "I" // trim(str)
fmt_proc(i) = fmt_proc(i) // "A" // trim(str)
if (i == n_in) then
fmt_head = fmt_head // ",1x,' '"
end if
end do
do i = 1, n_tot
fmt_proc(i) = fmt_proc(i) // ")"
end do
fmt_head = fmt_head // ")"
write (u, char (fmt_head)) bincode
do f = 1, n_flv
write (u, "('!')", advance="no")
do i = 1, n_tot
write (u, char (fmt_proc(i)), advance="no") &
char (cascade_set%flv(i,f)%get_name ())
if (i == n_in) write (u, "(1x,'=>')", advance="no")
end do
write (u, *)
end do
write (u, char (fmt_head)) bincode
end subroutine cascade_set_write_process_bincode_format
@ %def cascade_set_write_process_tex_format
@ Write the process as a \LaTeX\ expression.
<<Cascades: procedures>>=
subroutine cascade_set_write_process_tex_format (cascade_set, unit)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
integer :: u, f, i
u = given_output_unit (unit); if (u < 0) return
if (.not. allocated (cascade_set%flv)) return
write (u, "(A)") "\begin{align*}"
do f = 1, size (cascade_set%flv, 2)
do i = 1, cascade_set%n_in
if (i > 1) write (u, "(A)", advance="no") "\quad "
write (u, "(A)", advance="no") &
char (cascade_set%flv(i,f)%get_tex_name ())
end do
write (u, "(A)", advance="no") "\quad &\to\quad "
do i = cascade_set%n_in + 1, cascade_set%n_tot
if (i > cascade_set%n_in + 1) write (u, "(A)", advance="no") "\quad "
write (u, "(A)", advance="no") &
char (cascade_set%flv(i,f)%get_tex_name ())
end do
if (f < size (cascade_set%flv, 2)) then
write (u, "(A)") "\\"
else
write (u, "(A)") ""
end if
end do
write (u, "(A)") "\end{align*}"
end subroutine cascade_set_write_process_tex_format
@ %def cascade_set_write_process_tex_format
@ Three output routines: phase-space file, graph source code, and
screen output.
This version generates the phase space file. It deals only with
complete cascades.
<<Cascades: public>>=
public :: cascade_set_write_file_format
<<Cascades: sub interfaces>>=
module subroutine cascade_set_write_file_format (cascade_set, unit)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
end subroutine cascade_set_write_file_format
<<Cascades: procedures>>=
module subroutine cascade_set_write_file_format (cascade_set, unit)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
type(cascade_t), pointer :: cascade
integer :: u, grove, count
logical :: first_in_grove
u = given_output_unit (unit); if (u < 0) return
count = 0
do grove = 1, cascade_set%n_groves
first_in_grove = .true.
cascade => cascade_set%first_k
do while (associated (cascade))
if (cascade%active .and. cascade%complete) then
if (cascade%grove == grove) then
if (first_in_grove) then
first_in_grove = .false.
write (u, "(A)")
write (u, "(1x,'!',1x,A,1x,I0,A)", advance='no') &
'Multiplicity =', cascade%multiplicity, ","
select case (cascade%n_resonances)
case (0)
write (u, '(1x,A)', advance='no') 'no resonances, '
case (1)
write (u, '(1x,A)', advance='no') '1 resonance, '
case default
write (u, '(1x,I0,1x,A)', advance='no') &
cascade%n_resonances, 'resonances, '
end select
write (u, '(1x,I0,1x,A)', advance='no') &
cascade%n_log_enhanced, 'logs, '
write (u, '(1x,I0,1x,A)', advance='no') &
cascade%n_off_shell, 'off-shell, '
select case (cascade%n_t_channel)
case (0); write (u, '(1x,A)') 's-channel graph'
case (1); write (u, '(1x,A)') '1 t-channel line'
case default
write(u,'(1x,I0,1x,A)') &
cascade%n_t_channel, 't-channel lines'
end select
write (u, '(1x,A,I0)') 'grove #', grove
end if
count = count + 1
write (u, "(1x,'!',1x,A,I0)") "Channel #", count
call cascade_write_file_format (cascade, cascade_set%model, u)
end if
end if
cascade => cascade%next
end do
end do
end subroutine cascade_set_write_file_format
@ %def cascade_set_write_file_format
@ This is the graph output format, the driver-file
<<Cascades: public>>=
public :: cascade_set_write_graph_format
<<Cascades: sub interfaces>>=
module subroutine cascade_set_write_graph_format &
(cascade_set, filename, process_id, unit)
type(cascade_set_t), intent(in), target :: cascade_set
type(string_t), intent(in) :: filename, process_id
integer, intent(in), optional :: unit
end subroutine cascade_set_write_graph_format
<<Cascades: procedures>>=
module subroutine cascade_set_write_graph_format &
(cascade_set, filename, process_id, unit)
type(cascade_set_t), intent(in), target :: cascade_set
type(string_t), intent(in) :: filename, process_id
integer, intent(in), optional :: unit
type(cascade_t), pointer :: cascade
integer :: u, grove, count, pgcount
logical :: first_in_grove
u = given_output_unit (unit); if (u < 0) return
write (u, '(A)') "\documentclass[10pt]{article}"
write (u, '(A)') "\usepackage{amsmath}"
write (u, '(A)') "\usepackage{feynmp}"
write (u, '(A)') "\usepackage{url}"
write (u, '(A)') "\usepackage{color}"
write (u, *)
write (u, '(A)') "\textwidth 18.5cm"
write (u, '(A)') "\evensidemargin -1.5cm"
write (u, '(A)') "\oddsidemargin -1.5cm"
write (u, *)
write (u, '(A)') "\newcommand{\blue}{\color{blue}}"
write (u, '(A)') "\newcommand{\green}{\color{green}}"
write (u, '(A)') "\newcommand{\red}{\color{red}}"
write (u, '(A)') "\newcommand{\magenta}{\color{magenta}}"
write (u, '(A)') "\newcommand{\cyan}{\color{cyan}}"
write (u, '(A)') "\newcommand{\sm}{\footnotesize}"
write (u, '(A)') "\setlength{\parindent}{0pt}"
write (u, '(A)') "\setlength{\parsep}{20pt}"
write (u, *)
write (u, '(A)') "\begin{document}"
write (u, '(A)') "\begin{fmffile}{" // char (filename) // "}"
write (u, '(A)') "\fmfcmd{color magenta; magenta = red + blue;}"
write (u, '(A)') "\fmfcmd{color cyan; cyan = green + blue;}"
write (u, '(A)') "\begin{fmfshrink}{0.5}"
write (u, '(A)') "\begin{flushleft}"
write (u, *)
write (u, '(A)') "\noindent" // &
& "\textbf{\large\texttt{WHIZARD} phase space channels}" // &
& "\hfill\today"
write (u, *)
write (u, '(A)') "\vspace{10pt}"
write (u, '(A)') "\noindent" // &
& "\textbf{Process:} \url{" // char (process_id) // "}"
call cascade_set_write_process_tex_format (cascade_set, u)
write (u, *)
write (u, '(A)') "\noindent" // &
& "\textbf{Note:} These are pseudo Feynman graphs that "
write (u, '(A)') "visualize phase-space parameterizations " // &
& "(``integration channels''). "
write (u, '(A)') "They do \emph{not} indicate Feynman graphs used for the " // &
& "matrix element."
write (u, *)
write (u, '(A)') "\textbf{Color code:} " // &
& "{\blue resonance,} " // &
& "{\cyan t-channel,} " // &
& "{\green radiation,} "
write (u, '(A)') "{\red infrared,} " // &
& "{\magenta collinear,} " // &
& "external/off-shell"
write (u, *)
write (u, '(A)') "\noindent" // &
& "\textbf{Black square:} Keystone, indicates ordering of " // &
& "phase space parameters."
write (u, *)
write (u, '(A)') "\vspace{-20pt}"
count = 0
pgcount = 0
do grove = 1, cascade_set%n_groves
first_in_grove = .true.
cascade => cascade_set%first
do while (associated (cascade))
if (cascade%active .and. cascade%complete) then
if (cascade%grove == grove) then
if (first_in_grove) then
first_in_grove = .false.
write (u, *)
write (u, '(A)') "\vspace{20pt}"
write (u, '(A)') "\begin{tabular}{l}"
write (u, '(A,I5,A)') &
& "\fbox{\bf Grove \boldmath$", grove, "$} \\[10pt]"
write (u, '(A,I1,A)') "Multiplicity: ", &
cascade%multiplicity, "\\"
write (u, '(A,I1,A)') "Resonances: ", &
cascade%n_resonances, "\\"
write (u, '(A,I1,A)') "Log-enhanced: ", &
cascade%n_log_enhanced, "\\"
write (u, '(A,I1,A)') "Off-shell: ", &
cascade%n_off_shell, "\\"
write (u, '(A,I1,A)') "t-channel: ", &
cascade%n_t_channel, ""
write (u, '(A)') "\end{tabular}"
end if
count = count + 1
call cascade_write_graph_format (cascade, count, unit)
if (pgcount >= 250) then
write (u, '(A)') "\clearpage"
pgcount = 0
end if
end if
end if
cascade => cascade%next
end do
end do
write (u, '(A)') "\end{flushleft}"
write (u, '(A)') "\end{fmfshrink}"
write (u, '(A)') "\end{fmffile}"
write (u, '(A)') "\end{document}"
end subroutine cascade_set_write_graph_format
@ %def cascade_set_write_graph_format
@ This is for screen output and debugging:
<<Cascades: public>>=
public :: cascade_set_write
<<Cascades: sub interfaces>>=
module subroutine cascade_set_write &
(cascade_set, unit, active_only, complete_only)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
logical, intent(in), optional :: active_only, complete_only
end subroutine cascade_set_write
<<Cascades: procedures>>=
module subroutine cascade_set_write &
(cascade_set, unit, active_only, complete_only)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: unit
logical, intent(in), optional :: active_only, complete_only
logical :: active, complete
type(cascade_t), pointer :: cascade
integer :: u, i
u = given_output_unit (unit); if (u < 0) return
active = .true.; if (present (active_only)) active = active_only
complete = .false.; if (present (complete_only)) complete = complete_only
write (u, "(A)") "Cascade set:"
write (u, "(3x,A)", advance="no") "Model:"
if (associated (cascade_set%model)) then
write (u, "(1x,A)") char (cascade_set%model%get_name ())
else
write (u, "(1x,A)") "[none]"
end if
write (u, "(3x,A)", advance="no") "n_in/out/tot ="
write (u, "(3(1x,I7))") &
cascade_set%n_in, cascade_set%n_out, cascade_set%n_tot
write (u, "(3x,A)", advance="no") "depth_out/tot ="
write (u, "(2(1x,I7))") cascade_set%depth_out, cascade_set%depth_tot
write (u, "(3x,A)", advance="no") "mass thr(s/t) ="
write (u, "(2(1x," // FMT_19 // "))") &
cascade_set%m_threshold_s, cascade_set%m_threshold_t
write (u, "(3x,A)", advance="no") "off shell ="
write (u, "(1x,I7)") cascade_set%off_shell
write (u, "(3x,A)", advance="no") "keep_nonreson ="
write (u, "(1x,L1)") cascade_set%keep_nonresonant
write (u, "(3x,A)", advance="no") "n_groves ="
write (u, "(1x,I7)") cascade_set%n_groves
write (u, "(A)")
write (u, "(A)") "Cascade list:"
if (associated (cascade_set%first)) then
cascade => cascade_set%first
do while (associated (cascade))
if (active .and. .not. cascade%active) cycle
if (complete .and. .not. cascade%complete) cycle
call cascade_write (cascade, unit)
cascade => cascade%next
end do
else
write (u, "(A)") "[empty]"
end if
write (u, "(A)") "Hash array"
write (u, "(3x,A)", advance="no") "n_entries ="
write (u, "(1x,I7)") cascade_set%n_entries
write (u, "(3x,A)", advance="no") "fill_ratio ="
write (u, "(1x," // FMT_12 // ")") cascade_set%fill_ratio
write (u, "(3x,A)", advance="no") "n_entries_max ="
write (u, "(1x,I7)") cascade_set%n_entries_max
write (u, "(3x,A)", advance="no") "mask ="
write (u, "(1x,I0)") cascade_set%mask
do i = 0, ubound (cascade_set%entry, 1)
if (allocated (cascade_set%entry(i)%key)) then
write (u, "(1x,I7)") i
call hash_entry_write (cascade_set%entry(i), u)
end if
end do
end subroutine cascade_set_write
@ %def cascade_set_write
@
\subsection{Adding cascades}
Add a deep copy of a cascade to the set. The copy has all content of the
original, but the pointers are nullified. We do not care whether insertion
was successful or not. The pointer argument, if present, is assigned to the
input cascade, or to the hash entry if it is already present.
The procedure is recursive: any daughter or mother entries are also
deep-copied and added to the cascade set before the current copy is added.
<<Cascades: procedures>>=
recursive subroutine cascade_set_add_copy &
(cascade_set, cascade_in, cascade_ptr)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in) :: cascade_in
type(cascade_t), optional, pointer :: cascade_ptr
type(cascade_t), pointer :: cascade
logical :: ok
allocate (cascade)
cascade = cascade_in
if (associated (cascade_in%daughter1)) call cascade_set_add_copy &
(cascade_set, cascade_in%daughter1, cascade%daughter1)
if (associated (cascade_in%daughter2)) call cascade_set_add_copy &
(cascade_set, cascade_in%daughter2, cascade%daughter2)
if (associated (cascade_in%mother)) call cascade_set_add_copy &
(cascade_set, cascade_in%mother, cascade%mother)
cascade%next => null ()
call cascade_set_add (cascade_set, cascade, ok, cascade_ptr)
if (.not. ok) deallocate (cascade)
end subroutine cascade_set_add_copy
@ %def cascade_set_add_copy
@ Add a cascade to the set. This does not deep-copy. We first try to insert
it in the hash array. If successful, add it to the list. Failure indicates
that it is already present, and we drop it.
The hash key is built solely from the tree array, so neither particle
codes nor resonances count, just topology.
Technically, hash and list receive only pointers, so the cascade can
be considered as being in either of both. We treat it as part of the
list.
<<Cascades: procedures>>=
subroutine cascade_set_add (cascade_set, cascade, ok, cascade_ptr)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade
logical, intent(out) :: ok
type(cascade_t), optional, pointer :: cascade_ptr
integer(i8), dimension(1) :: mold
call cascade_set_hash_insert &
(cascade_set, transfer (cascade%tree, mold), cascade, ok, cascade_ptr)
if (ok) call cascade_set_list_add (cascade_set, cascade)
end subroutine cascade_set_add
@ %def cascade_set_add
@ Add a new cascade to the list:
<<Cascades: procedures>>=
subroutine cascade_set_list_add (cascade_set, cascade)
type(cascade_set_t), intent(inout) :: cascade_set
type(cascade_t), intent(in), target :: cascade
if (associated (cascade_set%last)) then
cascade_set%last%next => cascade
else
cascade_set%first => cascade
end if
cascade_set%last => cascade
end subroutine cascade_set_list_add
@ %def cascade_set_list_add
@ Add a cascade entry to the hash array:
<<Cascades: procedures>>=
subroutine cascade_set_hash_insert &
(cascade_set, key, cascade, ok, cascade_ptr)
type(cascade_set_t), intent(inout), target :: cascade_set
integer(i8), dimension(:), intent(in) :: key
type(cascade_t), intent(in), target :: cascade
logical, intent(out) :: ok
type(cascade_t), optional, pointer :: cascade_ptr
integer(i32) :: h
if (cascade_set%n_entries >= cascade_set%n_entries_max) &
call cascade_set_hash_expand (cascade_set)
h = hash (key)
call cascade_set_hash_insert_rec &
(cascade_set, h, h, key, cascade, ok, cascade_ptr)
end subroutine cascade_set_hash_insert
@ %def cascade_set_hash_insert
@ Double the hashtable size when necesssary:
<<Cascades: procedures>>=
subroutine cascade_set_hash_expand (cascade_set)
type(cascade_set_t), intent(inout), target :: cascade_set
type(hash_entry_t), dimension(:), allocatable, target :: table_tmp
type(cascade_p), pointer :: current
integer :: i, s
allocate (table_tmp (0:cascade_set%mask))
table_tmp = cascade_set%entry
deallocate (cascade_set%entry)
s = 2 * size (table_tmp)
cascade_set%n_entries = 0
cascade_set%n_entries_max = s * cascade_set%fill_ratio
cascade_set%mask = s - 1
allocate (cascade_set%entry (0:cascade_set%mask))
do i = 0, ubound (table_tmp, 1)
current => table_tmp(i)%first
do while (associated (current))
call cascade_set_hash_insert_rec &
(cascade_set, table_tmp(i)%hashval, table_tmp(i)%hashval, &
table_tmp(i)%key, current%cascade)
current => current%next
end do
end do
end subroutine cascade_set_hash_expand
@ %def cascade_set_hash_expand
@ Insert the cascade at the bucket determined by the hash value. If
the bucket is filled, check first for a collision (unequal keys). In
that case, choose the following bucket and repeat. Otherwise, add the
cascade to the bucket.
If the bucket is empty, record the hash value, allocate and store the
key, and then add the cascade to the bucket.
If [[ok]] is present, before insertion we check whether the cascade is
already stored, and return failure if yes.
<<Cascades: procedures>>=
recursive subroutine cascade_set_hash_insert_rec &
(cascade_set, h, hashval, key, cascade, ok, cascade_ptr)
type(cascade_set_t), intent(inout) :: cascade_set
integer(i32), intent(in) :: h, hashval
integer(i8), dimension(:), intent(in) :: key
type(cascade_t), intent(in), target :: cascade
logical, intent(out), optional :: ok
type(cascade_t), optional, pointer :: cascade_ptr
integer(i32) :: i
i = iand (h, cascade_set%mask)
if (allocated (cascade_set%entry(i)%key)) then
if (size (cascade_set%entry(i)%key) /= size (key)) then
call cascade_set_hash_insert_rec &
(cascade_set, h + 1, hashval, key, cascade, ok, cascade_ptr)
else if (any (cascade_set%entry(i)%key /= key)) then
call cascade_set_hash_insert_rec &
(cascade_set, h + 1, hashval, key, cascade, ok, cascade_ptr)
else
call hash_entry_add_cascade_ptr &
(cascade_set%entry(i), cascade, ok, cascade_ptr)
end if
else
cascade_set%entry(i)%hashval = hashval
allocate (cascade_set%entry(i)%key (size (key)))
cascade_set%entry(i)%key = key
call hash_entry_add_cascade_ptr &
(cascade_set%entry(i), cascade, ok, cascade_ptr)
cascade_set%n_entries = cascade_set%n_entries + 1
end if
end subroutine cascade_set_hash_insert_rec
@ %def cascade_set_hash_insert_rec
@
\subsection{External particles}
We want to initialize the cascade set with the outgoing particles. In
case of multiple processes, initial cascades are prepared for all of
them. The hash array check ensures that no particle appears more than
once at the same place.
<<Cascades: interfaces>>=
interface cascade_set_add_outgoing
module procedure cascade_set_add_outgoing1
module procedure cascade_set_add_outgoing2
end interface
<<Cascades: sub interfaces>>=
module subroutine cascade_set_add_outgoing1 (cascade_set, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
type(flavor_t), dimension(:), intent(in) :: flv
end subroutine cascade_set_add_outgoing1
module subroutine cascade_set_add_outgoing2 (cascade_set, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
type(flavor_t), dimension(:,:), intent(in) :: flv
end subroutine cascade_set_add_outgoing2
<<Cascades: procedures>>=
module subroutine cascade_set_add_outgoing2 (cascade_set, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
type(flavor_t), dimension(:,:), intent(in) :: flv
integer :: pos, prc, n_out, n_prc
type(cascade_t), pointer :: cascade
logical :: ok
n_out = size (flv, dim=1)
n_prc = size (flv, dim=2)
do prc = 1, n_prc
do pos = 1, n_out
allocate (cascade)
call cascade_init_outgoing &
(cascade, flv(pos,prc), pos, cascade_set%m_threshold_s)
call cascade_set_add (cascade_set, cascade, ok)
if (.not. ok) then
deallocate (cascade)
end if
end do
end do
end subroutine cascade_set_add_outgoing2
module subroutine cascade_set_add_outgoing1 (cascade_set, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
type(flavor_t), dimension(:), intent(in) :: flv
integer :: pos, n_out
type(cascade_t), pointer :: cascade
logical :: ok
n_out = size (flv, dim=1)
do pos = 1, n_out
allocate (cascade)
call cascade_init_outgoing &
(cascade, flv(pos), pos, cascade_set%m_threshold_s)
call cascade_set_add (cascade_set, cascade, ok)
if (.not. ok) then
deallocate (cascade)
end if
end do
end subroutine cascade_set_add_outgoing1
@ %def cascade_set_add_outgoing
@ The incoming particles are added one at a time. Nevertheless, we
may have several processes which are looped over. At the first
opportunity, we set the pointer [[first_t]] in the cascade set which
should point to the first t-channel cascade.
Return the indices of the first and last cascade generated.
<<Cascades: interfaces>>=
interface cascade_set_add_incoming
module procedure cascade_set_add_incoming0
module procedure cascade_set_add_incoming1
end interface
<<Cascades: sub interfaces>>=
module subroutine cascade_set_add_incoming1 (cascade_set, n1, n2, pos, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
integer, intent(out) :: n1, n2
integer, intent(in) :: pos
type(flavor_t), dimension(:), intent(in) :: flv
end subroutine cascade_set_add_incoming1
module subroutine cascade_set_add_incoming0 (cascade_set, n1, n2, pos, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
integer, intent(out) :: n1, n2
integer, intent(in) :: pos
type(flavor_t), intent(in) :: flv
end subroutine cascade_set_add_incoming0
<<Cascades: procedures>>=
module subroutine cascade_set_add_incoming1 (cascade_set, n1, n2, pos, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
integer, intent(out) :: n1, n2
integer, intent(in) :: pos
type(flavor_t), dimension(:), intent(in) :: flv
integer :: prc, n_prc
type(cascade_t), pointer :: cascade
logical :: ok
n1 = 0
n2 = 0
n_prc = size (flv)
do prc = 1, n_prc
allocate (cascade)
call cascade_init_incoming &
(cascade, flv(prc), pos, cascade_set%m_threshold_t)
call cascade_set_add (cascade_set, cascade, ok)
if (ok) then
if (n1 == 0) n1 = cascade%index
n2 = cascade%index
if (.not. associated (cascade_set%first_t)) then
cascade_set%first_t => cascade
end if
else
deallocate (cascade)
end if
end do
end subroutine cascade_set_add_incoming1
module subroutine cascade_set_add_incoming0 (cascade_set, n1, n2, pos, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
integer, intent(out) :: n1, n2
integer, intent(in) :: pos
type(flavor_t), intent(in) :: flv
type(cascade_t), pointer :: cascade
logical :: ok
n1 = 0
n2 = 0
allocate (cascade)
call cascade_init_incoming &
(cascade, flv, pos, cascade_set%m_threshold_t)
call cascade_set_add (cascade_set, cascade, ok)
if (ok) then
if (n1 == 0) n1 = cascade%index
n2 = cascade%index
if (.not. associated (cascade_set%first_t)) then
cascade_set%first_t => cascade
end if
else
deallocate (cascade)
end if
end subroutine cascade_set_add_incoming0
@ %def cascade_set_add_incoming
@
\subsection{Cascade combination I: flavor assignment}
We have two disjunct cascades, now use the vertex table to determine
the possible flavors of the combination cascade. For each
possibility, try to generate a new cascade. The total cascade depth
has to be one less than the limit, because this is reached by setting
the keystone.
<<Cascades: procedures>>=
subroutine cascade_match_pair (cascade_set, cascade1, cascade2, s_channel)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2
logical, intent(in) :: s_channel
integer, dimension(:), allocatable :: pdg3
integer :: i, depth_max
type(flavor_t) :: flv
if (s_channel) then
depth_max = cascade_set%depth_out
else
depth_max = cascade_set%depth_tot
end if
if (cascade1%depth + cascade2%depth < depth_max) then
call cascade_set%model%match_vertex ( &
cascade1%flv%get_pdg (), &
cascade2%flv%get_pdg (), &
pdg3)
do i = 1, size (pdg3)
call flv%init (pdg3(i), cascade_set%model)
if (s_channel) then
call cascade_combine_s (cascade_set, cascade1, cascade2, flv)
else
call cascade_combine_t (cascade_set, cascade1, cascade2, flv)
end if
end do
deallocate (pdg3)
end if
end subroutine cascade_match_pair
@ %def cascade_match_pair
@ The triplet version takes a third cascade, and we check whether this
triplet has a matching vertex in the database. If yes, we make a
keystone cascade.
<<Cascades: procedures>>=
subroutine cascade_match_triplet &
(cascade_set, cascade1, cascade2, cascade3, s_channel)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2, cascade3
logical, intent(in) :: s_channel
integer :: depth_max
depth_max = cascade_set%depth_tot
if (cascade1%depth + cascade2%depth + cascade3%depth == depth_max) then
if (cascade_set%model%check_vertex ( &
cascade1%flv%get_pdg (), &
cascade2%flv%get_pdg (), &
cascade3%flv%get_pdg ())) then
call cascade_combine_keystone &
(cascade_set, cascade1, cascade2, cascade3, s_channel)
end if
end if
end subroutine cascade_match_triplet
@ %def cascade_match_triplet
@
\subsection{Cascade combination II: kinematics setup and check}
Having three matching flavors, we start constructing the combination
cascade. We look at the mass hierarchies and determine whether the
cascade is to be kept. In passing we set mapping modes, resonance
properties and such.
If successful, the cascade is finalized. For a resonant cascade, we
prepare in addition a copy without the resonance.
<<Cascades: procedures>>=
subroutine cascade_combine_s (cascade_set, cascade1, cascade2, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2
type(flavor_t), intent(in) :: flv
type(cascade_t), pointer :: cascade3, cascade4
real(default) :: width
logical :: keep
keep = .false.
allocate (cascade3)
call cascade_init (cascade3, cascade1%depth + cascade2%depth + 1)
cascade3%bincode = ior (cascade1%bincode, cascade2%bincode)
cascade3%flv = flv%anti ()
cascade3%pdg = cascade3%flv%get_pdg ()
cascade3%is_vector = flv%get_spin_type () == VECTOR
cascade3%m_min = cascade1%m_min + cascade2%m_min
cascade3%m_rea = flv%get_mass ()
width = flv%get_width ()
if (cascade3%m_rea > cascade_set%m_threshold_s) then
cascade3%m_eff = cascade3%m_rea
end if
! Potentially resonant cases [sqrts = m_rea for on-shell decay]
if (cascade3%m_rea > cascade3%m_min .and. &
cascade3%m_rea <= cascade_set%sqrts) then
if (width /= 0) then
if (cascade1%on_shell .or. cascade2%on_shell) then
keep = .true.
cascade3%mapping = S_CHANNEL
cascade3%resonant = .true.
end if
else
call warn_decay (flv)
end if
! Collinear and IR singular cases
else if (cascade3%m_rea < cascade_set%sqrts) then
! Massless splitting
if (cascade1%m_eff == 0 .and. cascade2%m_eff == 0 &
.and. cascade3%depth <= 3) then
keep = .true.
cascade3%log_enhanced = .true.
if (cascade3%is_vector) then
if (cascade1%is_vector .and. cascade2%is_vector) then
cascade3%mapping = COLLINEAR ! three-vector-vertex
else
cascade3%mapping = INFRARED ! vector splitting into matter
end if
else
if (cascade1%is_vector .or. cascade2%is_vector) then
cascade3%mapping = COLLINEAR ! vector radiation off matter
else
cascade3%mapping = INFRARED ! scalar radiation/splitting
end if
end if
! IR radiation off massive particle
else if (cascade3%m_eff > 0 .and. cascade1%m_eff > 0 &
.and. cascade2%m_eff == 0 &
.and. (cascade1%on_shell .or. cascade1%mapping == RADIATION) &
.and. abs (cascade3%m_eff - cascade1%m_eff) &
< cascade_set%m_threshold_s) &
then
keep = .true.
cascade3%log_enhanced = .true.
cascade3%mapping = RADIATION
else if (cascade3%m_eff > 0 .and. cascade2%m_eff > 0 &
.and. cascade1%m_eff == 0 &
.and. (cascade2%on_shell .or. cascade2%mapping == RADIATION) &
.and. abs (cascade3%m_eff - cascade2%m_eff) &
< cascade_set%m_threshold_s) &
then
keep = .true.
cascade3%log_enhanced = .true.
cascade3%mapping = RADIATION
end if
end if
! Non-singular cases, including failed resonances
if (.not. keep) then
! Two on-shell particles from a virtual mother
if (cascade1%on_shell .or. cascade2%on_shell) then
keep = .true.
cascade3%m_eff = max (cascade3%m_min, &
cascade1%m_eff + cascade2%m_eff)
if (cascade3%m_eff < cascade_set%m_threshold_s) then
cascade3%m_eff = 0
end if
end if
end if
! Complete and register the cascade (two in case of resonance)
if (keep) then
cascade3%on_shell = cascade3%resonant .or. cascade3%log_enhanced
if (cascade3%resonant) then
cascade3%pdg = cascade3%flv%get_pdg ()
if (cascade_set%keep_nonresonant) then
allocate (cascade4)
cascade4 = cascade3
cascade4%index = cascade_index ()
cascade4%pdg = UNDEFINED
cascade4%mapping = NO_MAPPING
cascade4%resonant = .false.
cascade4%on_shell = .false.
end if
cascade3%m_min = cascade3%m_rea
call cascade_fusion (cascade_set, cascade1, cascade2, cascade3)
if (cascade_set%keep_nonresonant) then
call cascade_fusion (cascade_set, cascade1, cascade2, cascade4)
end if
else
call cascade_fusion (cascade_set, cascade1, cascade2, cascade3)
end if
else
deallocate (cascade3)
end if
contains
subroutine warn_decay (flv)
type(flavor_t), intent(in) :: flv
integer :: i
integer, dimension(MAX_WARN_RESONANCE), save :: warned_code = 0
LOOP_WARNED: do i = 1, MAX_WARN_RESONANCE
if (warned_code(i) == 0) then
warned_code(i) = flv%get_pdg ()
write (msg_buffer, "(A)") &
& " Intermediate decay of zero-width particle " &
& // char (flv%get_name ()) &
& // " may be possible."
call msg_warning
exit LOOP_WARNED
else if (warned_code(i) == flv%get_pdg ()) then
exit LOOP_WARNED
end if
end do LOOP_WARNED
end subroutine warn_decay
end subroutine cascade_combine_s
@ %def cascade_combine_s
<<Cascades: parameters>>=
integer, parameter, public :: MAX_WARN_RESONANCE = 50
@ %def MAX_WARN_RESONANCE
@ This is the t-channel version. [[cascade1]] is t-channel and
contains the seed, [[cascade2]] is s-channel. We check for
kinematically allowed beam decay (which is a fatal error), or massless
splitting / soft radiation. The cascade is kept in all remaining
cases and submitted for registration.
<<Cascades: procedures>>=
subroutine cascade_combine_t (cascade_set, cascade1, cascade2, flv)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2
type(flavor_t), intent(in) :: flv
type(cascade_t), pointer :: cascade3
allocate (cascade3)
call cascade_init (cascade3, cascade1%depth + cascade2%depth + 1)
cascade3%bincode = ior (cascade1%bincode, cascade2%bincode)
cascade3%flv = flv%anti ()
cascade3%pdg = abs (cascade3%flv%get_pdg ())
cascade3%is_vector = flv%get_spin_type () == VECTOR
if (cascade1%incoming) then
cascade3%m_min = cascade2%m_min
else
cascade3%m_min = cascade1%m_min + cascade2%m_min
end if
cascade3%m_rea = flv%get_mass ()
if (cascade3%m_rea > cascade_set%m_threshold_t) then
cascade3%m_eff = max (cascade3%m_rea, cascade2%m_eff)
else if (cascade2%m_eff > cascade_set%m_threshold_t) then
cascade3%m_eff = cascade2%m_eff
else
cascade3%m_eff = 0
end if
! Allowed decay of beam particle
if (cascade1%incoming &
.and. cascade1%m_rea > cascade2%m_rea + cascade3%m_rea) then
call beam_decay (cascade_set%fatal_beam_decay)
! Massless splitting
else if (cascade1%m_eff == 0 &
.and. cascade2%m_eff < cascade_set%m_threshold_t &
.and. cascade3%m_eff == 0) then
cascade3%mapping = U_CHANNEL
cascade3%log_enhanced = .true.
! IR radiation off massive particle
else if (cascade1%m_eff /= 0 .and. cascade2%m_eff == 0 &
.and. cascade3%m_eff /= 0 &
.and. (cascade1%on_shell .or. cascade1%mapping == RADIATION) &
.and. abs (cascade1%m_eff - cascade3%m_eff) &
< cascade_set%m_threshold_t) &
then
cascade3%pdg = flv%get_pdg ()
cascade3%log_enhanced = .true.
cascade3%mapping = RADIATION
end if
cascade3%t_channel = .true.
call cascade_fusion (cascade_set, cascade1, cascade2, cascade3)
contains
subroutine beam_decay (fatal_beam_decay)
logical, intent(in) :: fatal_beam_decay
write (msg_buffer, "(1x,A,1x,'->',1x,A,1x,A)") &
char (cascade1%flv%get_name ()), &
char (cascade3%flv%get_name ()), &
char (cascade2%flv%get_name ())
call msg_message
write (msg_buffer, "(1x,'mass(',A,') =',1x,E17.10)") &
char (cascade1%flv%get_name ()), cascade1%m_rea
call msg_message
write (msg_buffer, "(1x,'mass(',A,') =',1x,E17.10)") &
char (cascade3%flv%get_name ()), cascade3%m_rea
call msg_message
write (msg_buffer, "(1x,'mass(',A,') =',1x,E17.10)") &
char (cascade2%flv%get_name ()), cascade2%m_rea
call msg_message
if (fatal_beam_decay) then
call msg_fatal (" Phase space: Initial beam particle can decay")
else
call msg_warning (" Phase space: Initial beam particle can decay")
end if
end subroutine beam_decay
end subroutine cascade_combine_t
@ %def cascade_combine_t
@ Here we complete a decay cascade. The third input is the
single-particle cascade for the initial particle. There is no
resonance or mapping assignment. The only condition for keeping the
cascade is the mass sum of the final state, which must be less than
the available energy.
Two modifications are necessary for scattering cascades: a pure
s-channel diagram (cascade1 is the incoming particle) do not have a
logarithmic mapping at top-level. And in a t-channel diagram, the
last line exchanged is mapped t-channel, not u-channel. Finally, we
can encounter the case of a $2\to 1$ process, where cascade1 is
incoming, and cascade2 is the outgoing particle. In all three cases
we register a new cascade with the modified mapping.
<<Cascades: procedures>>=
subroutine cascade_combine_keystone &
(cascade_set, cascade1, cascade2, cascade3, s_channel)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2, cascade3
logical, intent(in) :: s_channel
type(cascade_t), pointer :: cascade4, cascade0
logical :: keep, ok
keep = .false.
allocate (cascade4)
call cascade_init &
(cascade4, cascade1%depth + cascade2%depth + cascade3%depth)
cascade4%complete = .true.
if (s_channel) then
cascade4%bincode = ior (cascade1%bincode, cascade2%bincode)
else
cascade4%bincode = cascade3%bincode
end if
cascade4%flv = cascade3%flv
cascade4%pdg = cascade3%pdg
cascade4%mapping = EXTERNAL_PRT
cascade4%is_vector = cascade3%is_vector
cascade4%m_min = cascade1%m_min + cascade2%m_min
cascade4%m_rea = cascade3%m_rea
cascade4%m_eff = cascade3%m_rea
if (cascade4%m_min < cascade_set%sqrts) then
keep = .true.
end if
if (keep) then
if (cascade1%incoming .and. cascade2%log_enhanced) then
allocate (cascade0)
cascade0 = cascade2
cascade0%next => null ()
cascade0%index = cascade_index ()
cascade0%mapping = NO_MAPPING
cascade0%log_enhanced = .false.
cascade0%n_log_enhanced = cascade0%n_log_enhanced - 1
cascade0%tree_mapping(cascade0%depth) = NO_MAPPING
call cascade_keystone &
(cascade_set, cascade1, cascade0, cascade3, cascade4, ok)
if (ok) then
call cascade_set_add (cascade_set, cascade0, ok)
else
deallocate (cascade0)
end if
else if (cascade1%t_channel .and. cascade1%mapping == U_CHANNEL) then
allocate (cascade0)
cascade0 = cascade1
cascade0%next => null ()
cascade0%index = cascade_index ()
cascade0%mapping = T_CHANNEL
cascade0%tree_mapping(cascade0%depth) = T_CHANNEL
call cascade_keystone &
(cascade_set, cascade0, cascade2, cascade3, cascade4, ok)
if (ok) then
call cascade_set_add (cascade_set, cascade0, ok)
else
deallocate (cascade0)
end if
else if (cascade1%incoming .and. cascade2%depth == 1) then
allocate (cascade0)
cascade0 = cascade2
cascade0%next => null ()
cascade0%index = cascade_index ()
cascade0%mapping = ON_SHELL
cascade0%tree_mapping(cascade0%depth) = ON_SHELL
call cascade_keystone &
(cascade_set, cascade1, cascade0, cascade3, cascade4, ok)
if (ok) then
call cascade_set_add (cascade_set, cascade0, ok)
else
deallocate (cascade0)
end if
else
call cascade_keystone &
(cascade_set, cascade1, cascade2, cascade3, cascade4, ok)
end if
else
deallocate (cascade4)
end if
end subroutine cascade_combine_keystone
@ %def cascade_combine_keystone
@
\subsection{Cascade combination III: node connections and tree fusion}
Here we assign global tree properties. If the allowed number of
off-shell lines is exceeded, discard the new cascade. Otherwise,
assign the trees, sort them, and assign connections. Finally, append
the cascade to the list. This may fail (because in the hash array
there is already an equivalent cascade). On failure, discard the
cascade.
<<Cascades: procedures>>=
subroutine cascade_fusion (cascade_set, cascade1, cascade2, cascade3)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2
type(cascade_t), pointer :: cascade3
integer :: i1, i2, i3, i4
logical :: ok
cascade3%internal = (cascade3%depth - 3) / 2
if (cascade3%resonant) then
cascade3%multiplicity = 1
cascade3%n_resonances = &
cascade1%n_resonances + cascade2%n_resonances + 1
else
cascade3%multiplicity = cascade1%multiplicity + cascade2%multiplicity
cascade3%n_resonances = cascade1%n_resonances + cascade2%n_resonances
end if
if (cascade3%log_enhanced) then
cascade3%n_log_enhanced = &
cascade1%n_log_enhanced + cascade2%n_log_enhanced + 1
else
cascade3%n_log_enhanced = &
cascade1%n_log_enhanced + cascade2%n_log_enhanced
end if
if (cascade3%resonant) then
cascade3%n_off_shell = 0
else if (cascade3%log_enhanced) then
cascade3%n_off_shell = cascade1%n_off_shell + cascade2%n_off_shell
else
cascade3%n_off_shell = cascade1%n_off_shell + cascade2%n_off_shell + 1
end if
if (cascade3%t_channel) then
cascade3%n_t_channel = cascade1%n_t_channel + 1
end if
if (cascade3%n_off_shell > cascade_set%off_shell) then
deallocate (cascade3)
else if (cascade3%n_t_channel > cascade_set%t_channel) then
deallocate (cascade3)
else
i1 = cascade1%depth
i2 = i1 + 1
i3 = i1 + cascade2%depth
i4 = cascade3%depth
cascade3%tree(:i1) = cascade1%tree
where (cascade1%tree_mapping > NO_MAPPING)
cascade3%tree_pdg(:i1) = cascade1%tree_pdg
elsewhere
cascade3%tree_pdg(:i1) = UNDEFINED
end where
cascade3%tree_mapping(:i1) = cascade1%tree_mapping
cascade3%tree_resonant(:i1) = cascade1%tree_resonant
cascade3%tree(i2:i3) = cascade2%tree
where (cascade2%tree_mapping > NO_MAPPING)
cascade3%tree_pdg(i2:i3) = cascade2%tree_pdg
elsewhere
cascade3%tree_pdg(i2:i3) = UNDEFINED
end where
cascade3%tree_mapping(i2:i3) = cascade2%tree_mapping
cascade3%tree_resonant(i2:i3) = cascade2%tree_resonant
cascade3%tree(i4) = cascade3%bincode
cascade3%tree_pdg(i4) = cascade3%pdg
cascade3%tree_mapping(i4) = cascade3%mapping
cascade3%tree_resonant(i4) = cascade3%resonant
call tree_sort (cascade3%tree, &
cascade3%tree_pdg, cascade3%tree_mapping, cascade3%tree_resonant)
cascade3%has_children = .true.
cascade3%daughter1 => cascade1
cascade3%daughter2 => cascade2
call cascade_set_add (cascade_set, cascade3, ok)
if (.not. ok) deallocate (cascade3)
end if
end subroutine cascade_fusion
@ %def cascade_fusion
@ Here we combine a cascade pair with an incoming particle, i.e., we
set a keystone. Otherwise, this is similar. On the first
opportunity, we set the [[first_k]] pointer in the cascade set.
<<Cascades: procedures>>=
subroutine cascade_keystone &
(cascade_set, cascade1, cascade2, cascade3, cascade4, ok)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), intent(in), target :: cascade1, cascade2, cascade3
type(cascade_t), pointer :: cascade4
logical, intent(out) :: ok
integer :: i1, i2, i3, i4
cascade4%internal = (cascade4%depth - 3) / 2
cascade4%multiplicity = cascade1%multiplicity + cascade2%multiplicity
cascade4%n_resonances = cascade1%n_resonances + cascade2%n_resonances
cascade4%n_off_shell = cascade1%n_off_shell + cascade2%n_off_shell
cascade4%n_log_enhanced = &
cascade1%n_log_enhanced + cascade2%n_log_enhanced
cascade4%n_t_channel = cascade1%n_t_channel + cascade2%n_t_channel
if (cascade4%n_off_shell > cascade_set%off_shell) then
deallocate (cascade4)
ok = .false.
else if (cascade4%n_t_channel > cascade_set%t_channel) then
deallocate (cascade4)
ok = .false.
else
i1 = cascade1%depth
i2 = i1 + 1
i3 = i1 + cascade2%depth
i4 = cascade4%depth
cascade4%tree(:i1) = cascade1%tree
where (cascade1%tree_mapping > NO_MAPPING)
cascade4%tree_pdg(:i1) = cascade1%tree_pdg
elsewhere
cascade4%tree_pdg(:i1) = UNDEFINED
end where
cascade4%tree_mapping(:i1) = cascade1%tree_mapping
cascade4%tree_resonant(:i1) = cascade1%tree_resonant
cascade4%tree(i2:i3) = cascade2%tree
where (cascade2%tree_mapping > NO_MAPPING)
cascade4%tree_pdg(i2:i3) = cascade2%tree_pdg
elsewhere
cascade4%tree_pdg(i2:i3) = UNDEFINED
end where
cascade4%tree_mapping(i2:i3) = cascade2%tree_mapping
cascade4%tree_resonant(i2:i3) = cascade2%tree_resonant
cascade4%tree(i4) = cascade4%bincode
cascade4%tree_pdg(i4) = UNDEFINED
cascade4%tree_mapping(i4) = cascade4%mapping
cascade4%tree_resonant(i4) = .false.
call tree_sort (cascade4%tree, &
cascade4%tree_pdg, cascade4%tree_mapping, cascade4%tree_resonant)
cascade4%has_children = .true.
cascade4%daughter1 => cascade1
cascade4%daughter2 => cascade2
cascade4%mother => cascade3
call cascade_set_add (cascade_set, cascade4, ok)
if (ok) then
if (.not. associated (cascade_set%first_k)) then
cascade_set%first_k => cascade4
end if
else
deallocate (cascade4)
end if
end if
end subroutine cascade_keystone
@ %def cascade_keystone
@
Sort a tree (array of binary codes) and particle code array
simultaneously, by ascending binary codes. A convenient method is to
use the [[maxloc]] function iteratively, to find and remove the
largest entry in the tree array one by one.
<<Cascades: procedures>>=
subroutine tree_sort (tree, pdg, mapping, resonant)
integer(TC), dimension(:), intent(inout) :: tree
integer, dimension(:), intent(inout) :: pdg, mapping
logical, dimension(:), intent(inout) :: resonant
integer(TC), dimension(size(tree)) :: tree_tmp
integer, dimension(size(pdg)) :: pdg_tmp, mapping_tmp
logical, dimension(size(resonant)) :: resonant_tmp
integer, dimension(1) :: pos
integer :: i
tree_tmp = tree
pdg_tmp = pdg
mapping_tmp = mapping
resonant_tmp = resonant
do i = size(tree),1,-1
pos = maxloc (tree_tmp)
tree(i) = tree_tmp (pos(1))
pdg(i) = pdg_tmp (pos(1))
mapping(i) = mapping_tmp (pos(1))
resonant(i) = resonant_tmp (pos(1))
tree_tmp(pos(1)) = 0
end do
end subroutine tree_sort
@ %def tree_sort
@
\subsection{Cascade set generation}
These procedures loop over cascades and build up the cascade set. After each
iteration of the innermost loop, we set a breakpoint.
s-channel: We use a nested scan to combine all cascades with all other
cascades.
<<Cascades: procedures>>=
subroutine cascade_set_generate_s (cascade_set)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), pointer :: cascade1, cascade2
cascade1 => cascade_set%first
LOOP1: do while (associated (cascade1))
cascade2 => cascade_set%first
LOOP2: do while (associated (cascade2))
if (cascade2%index >= cascade1%index) exit LOOP2
if (cascade1 .disjunct. cascade2) then
call cascade_match_pair (cascade_set, cascade1, cascade2, .true.)
end if
call terminate_now_if_signal ()
cascade2 => cascade2%next
end do LOOP2
cascade1 => cascade1%next
end do LOOP1
end subroutine cascade_set_generate_s
@ %def cascade_set_generate_s
@ The t-channel cascades are directed and have a seed (one of the
incoming particles) and a target (the other one). We loop over all
possible seeds and targets. Inside this, we loop over all t-channel
cascades ([[cascade1]]) and s-channel cascades ([[cascade2]]) and try
to combine them.
<<Cascades: procedures>>=
subroutine cascade_set_generate_t (cascade_set, pos_seed, pos_target)
type(cascade_set_t), intent(inout), target :: cascade_set
integer, intent(in) :: pos_seed, pos_target
type(cascade_t), pointer :: cascade_seed, cascade_target
type(cascade_t), pointer :: cascade1, cascade2
integer(TC) :: bc_seed, bc_target
bc_seed = ibset (0_TC, pos_seed-1)
bc_target = ibset (0_TC, pos_target-1)
cascade_seed => cascade_set%first_t
LOOP_SEED: do while (associated (cascade_seed))
if (cascade_seed%bincode == bc_seed) then
cascade_target => cascade_set%first_t
LOOP_TARGET: do while (associated (cascade_target))
if (cascade_target%bincode == bc_target) then
cascade1 => cascade_set%first_t
LOOP_T: do while (associated (cascade1))
if ((cascade1 .disjunct. cascade_target) &
.and. .not. (cascade1 .disjunct. cascade_seed)) then
cascade2 => cascade_set%first
LOOP_S: do while (associated (cascade2))
if ((cascade2 .disjunct. cascade_target) &
.and. (cascade2 .disjunct. cascade1)) then
call cascade_match_pair &
(cascade_set, cascade1, cascade2, .false.)
end if
call terminate_now_if_signal ()
cascade2 => cascade2%next
end do LOOP_S
end if
call terminate_now_if_signal ()
cascade1 => cascade1%next
end do LOOP_T
end if
call terminate_now_if_signal ()
cascade_target => cascade_target%next
end do LOOP_TARGET
end if
call terminate_now_if_signal ()
cascade_seed => cascade_seed%next
end do LOOP_SEED
end subroutine cascade_set_generate_t
@ %def cascade_set_generate_t
@ This part completes the phase space for decay processes. It is
similar to s-channel cascade generation, but combines two cascade with
the particular cascade of the incoming particle. This particular
cascade is expected to be pointed at by [[first_t]].
<<Cascades: procedures>>=
subroutine cascade_set_generate_decay (cascade_set)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), pointer :: cascade1, cascade2
type(cascade_t), pointer :: cascade_in
cascade_in => cascade_set%first_t
cascade1 => cascade_set%first
do while (associated (cascade1))
if (cascade1 .disjunct. cascade_in) then
cascade2 => cascade1%next
do while (associated (cascade2))
if ((cascade2 .disjunct. cascade1) &
.and. (cascade2 .disjunct. cascade_in)) then
call cascade_match_triplet (cascade_set, &
cascade1, cascade2, cascade_in, .true.)
end if
call terminate_now_if_signal ()
cascade2 => cascade2%next
end do
end if
call terminate_now_if_signal ()
cascade1 => cascade1%next
end do
end subroutine cascade_set_generate_decay
@ %def cascade_set_generate_decay
@ This part completes the phase space for scattering processes. We
combine a t-channel cascade (containing the seed) with a s-channel
cascade and the target.
<<Cascades: procedures>>=
subroutine cascade_set_generate_scattering &
(cascade_set, ns1, ns2, nt1, nt2, pos_seed, pos_target)
type(cascade_set_t), intent(inout), target :: cascade_set
integer, intent(in) :: pos_seed, pos_target
integer, intent(in) :: ns1, ns2, nt1, nt2
type(cascade_t), pointer :: cascade_seed, cascade_target
type(cascade_t), pointer :: cascade1, cascade2
integer(TC) :: bc_seed, bc_target
bc_seed = ibset (0_TC, pos_seed-1)
bc_target = ibset (0_TC, pos_target-1)
cascade_seed => cascade_set%first_t
LOOP_SEED: do while (associated (cascade_seed))
if (cascade_seed%index < ns1) then
cascade_seed => cascade_seed%next
cycle LOOP_SEED
else if (cascade_seed%index > ns2) then
exit LOOP_SEED
else if (cascade_seed%bincode == bc_seed) then
cascade_target => cascade_set%first_t
LOOP_TARGET: do while (associated (cascade_target))
if (cascade_target%index < nt1) then
cascade_target => cascade_target%next
cycle LOOP_TARGET
else if (cascade_target%index > nt2) then
exit LOOP_TARGET
else if (cascade_target%bincode == bc_target) then
cascade1 => cascade_set%first_t
LOOP_T: do while (associated (cascade1))
if ((cascade1 .disjunct. cascade_target) &
.and. .not. (cascade1 .disjunct. cascade_seed)) then
cascade2 => cascade_set%first
LOOP_S: do while (associated (cascade2))
if ((cascade2 .disjunct. cascade_target) &
.and. (cascade2 .disjunct. cascade1)) then
call cascade_match_triplet (cascade_set, &
cascade1, cascade2, cascade_target, .false.)
end if
call terminate_now_if_signal ()
cascade2 => cascade2%next
end do LOOP_S
end if
call terminate_now_if_signal ()
cascade1 => cascade1%next
end do LOOP_T
end if
call terminate_now_if_signal ()
cascade_target => cascade_target%next
end do LOOP_TARGET
end if
call terminate_now_if_signal ()
cascade_seed => cascade_seed%next
end do LOOP_SEED
end subroutine cascade_set_generate_scattering
@ %def cascade_set_generate_scattering
@
\subsection{Groves}
Before assigning groves, assign hashcodes to the resonance patterns, so they
can easily be compared.
<<Cascades: procedures>>=
subroutine cascade_set_assign_resonance_hash (cascade_set)
type(cascade_set_t), intent(inout) :: cascade_set
type(cascade_t), pointer :: cascade
cascade => cascade_set%first_k
do while (associated (cascade))
call cascade_assign_resonance_hash (cascade)
cascade => cascade%next
end do
end subroutine cascade_set_assign_resonance_hash
@ %def cascade_assign_resonance_hash
@ After all cascades are recorded, we group the complete cascades in
groves. A grove consists of cascades with identical multiplicity,
number of resonances, log-enhanced, t-channel lines, and resonance flavors.
<<Cascades: procedures>>=
subroutine cascade_set_assign_groves (cascade_set)
type(cascade_set_t), intent(inout), target :: cascade_set
type(cascade_t), pointer :: cascade1, cascade2
integer :: multiplicity
integer :: n_resonances, n_log_enhanced, n_t_channel, n_off_shell
integer :: res_hash
integer :: grove
grove = 0
cascade1 => cascade_set%first_k
do while (associated (cascade1))
if (cascade1%active .and. cascade1%complete &
.and. cascade1%grove == 0) then
grove = grove + 1
cascade1%grove = grove
multiplicity = cascade1%multiplicity
n_resonances = cascade1%n_resonances
n_log_enhanced = cascade1%n_log_enhanced
n_off_shell = cascade1%n_off_shell
n_t_channel = cascade1%n_t_channel
res_hash = cascade1%res_hash
cascade2 => cascade1%next
do while (associated (cascade2))
if (cascade2%grove == 0) then
if (cascade2%multiplicity == multiplicity &
.and. cascade2%n_resonances == n_resonances &
.and. cascade2%n_log_enhanced == n_log_enhanced &
.and. cascade2%n_off_shell == n_off_shell &
.and. cascade2%n_t_channel == n_t_channel &
.and. cascade2%res_hash == res_hash) then
cascade2%grove = grove
end if
end if
call terminate_now_if_signal ()
cascade2 => cascade2%next
end do
end if
call terminate_now_if_signal ()
cascade1 => cascade1%next
end do
cascade_set%n_groves = grove
end subroutine cascade_set_assign_groves
@ %def cascade_set_assign_groves
@
\subsection{Generate the phase space file}
Generate a complete phase space configuration.
For each flavor assignment: First, all s-channel
graphs that can be built up from the outgoing particles. Then we
distinguish (1) decay, where we complete the s-channel graphs by
connecting to the input line, and (2) scattering, where we now
generate t-channel graphs by introducing an incoming particle, and
complete this by connecting to the other incoming particle.
After all cascade sets have been generated, merge them into a common set.
This eliminates redunancies between flavor assignments.
<<Cascades: public>>=
public :: cascade_set_generate
<<Cascades: sub interfaces>>=
module subroutine cascade_set_generate &
(cascade_set, model, n_in, n_out, flv, phs_par, fatal_beam_decay)
type(cascade_set_t), intent(out) :: cascade_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(flavor_t), dimension(:,:), intent(in) :: flv
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
end subroutine cascade_set_generate
<<Cascades: procedures>>=
module subroutine cascade_set_generate &
(cascade_set, model, n_in, n_out, flv, phs_par, fatal_beam_decay)
type(cascade_set_t), intent(out) :: cascade_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(flavor_t), dimension(:,:), intent(in) :: flv
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
type(cascade_set_t), dimension(:), allocatable :: cset
type(cascade_t), pointer :: cascade
integer :: i
if (phase_space_vanishes (phs_par%sqrts, n_in, flv)) return
call cascade_set_init (cascade_set, model, n_in, n_out, phs_par, &
fatal_beam_decay, flv)
allocate (cset (size (flv, 2)))
do i = 1, size (cset)
call cascade_set_generate_single (cset(i), &
model, n_in, n_out, flv(:,i), phs_par, fatal_beam_decay)
cascade => cset(i)%first_k
do while (associated (cascade))
if (cascade%active .and. cascade%complete) then
call cascade_set_add_copy (cascade_set, cascade)
end if
cascade => cascade%next
end do
call cascade_set_final (cset(i))
end do
cascade_set%first_k => cascade_set%first
call cascade_set_assign_resonance_hash (cascade_set)
call cascade_set_assign_groves (cascade_set)
end subroutine cascade_set_generate
@ %def cascade_set_generate
@ This generates phase space for a single channel, without assigning groves.
<<Cascades: procedures>>=
subroutine cascade_set_generate_single (cascade_set, &
model, n_in, n_out, flv, phs_par, fatal_beam_decay)
type(cascade_set_t), intent(out) :: cascade_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(flavor_t), dimension(:), intent(in) :: flv
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
integer :: n11, n12, n21, n22
call cascade_set_init (cascade_set, model, n_in, n_out, phs_par, &
fatal_beam_decay)
call cascade_set_add_outgoing (cascade_set, flv(n_in+1:))
call cascade_set_generate_s (cascade_set)
select case (n_in)
case(1)
call cascade_set_add_incoming &
(cascade_set, n11, n12, n_out + 1, flv(1))
call cascade_set_generate_decay (cascade_set)
case(2)
call cascade_set_add_incoming &
(cascade_set, n11, n12, n_out + 1, flv(2))
call cascade_set_add_incoming &
(cascade_set, n21, n22, n_out + 2, flv(1))
call cascade_set_generate_t (cascade_set, n_out + 1, n_out + 2)
call cascade_set_generate_t (cascade_set, n_out + 2, n_out + 1)
call cascade_set_generate_scattering &
(cascade_set, n11, n12, n21, n22, n_out + 1, n_out + 2)
call cascade_set_generate_scattering &
(cascade_set, n21, n22, n11, n12, n_out + 2, n_out + 1)
end select
end subroutine cascade_set_generate_single
@ %def cascade_set_generate_single
@ Sanity check: Before anything else is done, check if there could
possibly be any phase space.
<<Cascades: public>>=
public :: phase_space_vanishes
<<Cascades: sub interfaces>>=
module function phase_space_vanishes (sqrts, n_in, flv) result (flag)
logical :: flag
real(default), intent(in) :: sqrts
integer, intent(in) :: n_in
type(flavor_t), dimension(:,:), intent(in) :: flv
end function phase_space_vanishes
<<Cascades: procedures>>=
module function phase_space_vanishes (sqrts, n_in, flv) result (flag)
logical :: flag
real(default), intent(in) :: sqrts
integer, intent(in) :: n_in
type(flavor_t), dimension(:,:), intent(in) :: flv
real(default), dimension(:,:), allocatable :: mass
real(default), dimension(:), allocatable :: mass_in, mass_out
integer :: n_prt, n_flv, i, j
flag = .false.
if (sqrts <= 0) then
call msg_error ("Phase space vanishes (sqrts must be positive)")
flag = .true.; return
end if
n_prt = size (flv, 1)
n_flv = size (flv, 2)
allocate (mass (n_prt, n_flv), mass_in (n_flv), mass_out (n_flv))
mass = flv%get_mass ()
mass_in = sum (mass(:n_in,:), 1)
mass_out = sum (mass(n_in+1:,:), 1)
if (any (mass_in > sqrts)) then
call msg_error ("Mass sum of incoming particles " &
// "is more than available energy")
flag = .true.; return
end if
if (any (mass_out > sqrts)) then
call msg_error ("Mass sum of outgoing particles " &
// "is more than available energy")
flag = .true.; return
end if
end function phase_space_vanishes
@ %def phase_space_vanishes
@
\subsection{Return the resonance histories for subtraction}
This appears to be essential (re-export of some imported assignment?)!
<<Cascades: public>>=
public :: assignment(=)
@
Extract the resonance set from a complete cascade.
<<Cascades: cascade: TBP>>=
procedure :: extract_resonance_history => cascade_extract_resonance_history
<<Cascades: sub interfaces>>=
module subroutine cascade_extract_resonance_history &
(cascade, res_hist, model, n_out)
class(cascade_t), intent(in), target :: cascade
type(resonance_history_t), intent(out) :: res_hist
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_out
end subroutine cascade_extract_resonance_history
<<Cascades: procedures>>=
module subroutine cascade_extract_resonance_history &
(cascade, res_hist, model, n_out)
class(cascade_t), intent(in), target :: cascade
type(resonance_history_t), intent(out) :: res_hist
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_out
type(resonance_info_t) :: resonance
integer :: i, mom_id, pdg
if (debug_on) call msg_debug2 (D_PHASESPACE, "cascade_extract_resonance_history")
if (cascade%n_resonances > 0) then
if (cascade%has_children) then
if (debug_on) call msg_debug2 (D_PHASESPACE, "cascade has resonances and children")
do i = 1, size(cascade%tree_resonant)
if (cascade%tree_resonant (i)) then
mom_id = cascade%tree (i)
pdg = cascade%tree_pdg (i)
call resonance%init (mom_id, pdg, model, n_out)
if (debug2_active (D_PHASESPACE)) then
print *, 'D: Adding resonance'
call resonance%write ()
end if
call res_hist%add_resonance (resonance)
end if
end do
end if
end if
end subroutine cascade_extract_resonance_history
@ %def cascade_extract_resonance_history
@
<<Cascades: public>>=
public :: cascade_set_get_n_trees
<<Cascades: sub interfaces>>=
module function cascade_set_get_n_trees (cascade_set) result (n)
type(cascade_set_t), intent(in), target :: cascade_set
integer :: n
end function cascade_set_get_n_trees
<<Cascades: procedures>>=
module function cascade_set_get_n_trees (cascade_set) result (n)
type(cascade_set_t), intent(in), target :: cascade_set
integer :: n
type(cascade_t), pointer :: cascade
integer :: grove
if (debug_on) call msg_debug (D_PHASESPACE, "cascade_set_get_n_trees")
n = 0
do grove = 1, cascade_set%n_groves
cascade => cascade_set%first_k
do while (associated (cascade))
if (cascade%active .and. cascade%complete) then
if (cascade%grove == grove) then
n = n + 1
end if
end if
cascade => cascade%next
end do
end do
if (debug_on) call msg_debug (D_PHASESPACE, "n", n)
end function cascade_set_get_n_trees
@ %def cascade_set_get_n_trees
@ Distill the set of resonance histories from the cascade set. The
result is an array which contains each valid history exactly once.
<<Cascades: public>>=
public :: cascade_set_get_resonance_histories
<<Cascades: sub interfaces>>=
module subroutine cascade_set_get_resonance_histories &
(cascade_set, n_filter, res_hists)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: n_filter
type(resonance_history_t), dimension(:), allocatable, intent(out) :: &
res_hists
end subroutine cascade_set_get_resonance_histories
<<Cascades: procedures>>=
module subroutine cascade_set_get_resonance_histories &
(cascade_set, n_filter, res_hists)
type(cascade_set_t), intent(in), target :: cascade_set
integer, intent(in), optional :: n_filter
type(resonance_history_t), dimension(:), allocatable, intent(out) :: &
res_hists
type(resonance_history_t), dimension(:), allocatable :: tmp
type(cascade_t), pointer :: cascade
type(resonance_history_t) :: res_hist
type(resonance_history_set_t) :: res_hist_set
integer :: grove, i, n_hists
logical :: included, add_to_list
if (debug_on) call msg_debug &
(D_PHASESPACE, "cascade_set_get_resonance_histories")
call res_hist_set%init (n_filter = n_filter)
do grove = 1, cascade_set%n_groves
cascade => cascade_set%first_k
do while (associated (cascade))
if (cascade%active .and. cascade%complete) then
if (cascade%grove == grove) then
if (debug_on) call msg_debug2 (D_PHASESPACE, "grove", grove)
call cascade%extract_resonance_history &
(res_hist, cascade_set%model, cascade_set%n_out)
call res_hist_set%enter (res_hist)
end if
end if
cascade => cascade%next
end do
end do
call res_hist_set%freeze ()
call res_hist_set%to_array (res_hists)
end subroutine cascade_set_get_resonance_histories
@ %def cascade_set_get_resonance_histories
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[cascades_ut.f90]]>>=
<<File header>>
module cascades_ut
use unit_tests
use cascades_uti
<<Standard module head>>
<<Cascades: public test>>
contains
<<Cascades: test driver>>
end module cascades_ut
@ %def cascades_ut
@
<<[[cascades_uti.f90]]>>=
<<File header>>
module cascades_uti
<<Use kinds>>
<<Use strings>>
use numeric_utils
use flavors
use model_data
use phs_forests, only: phs_parameters_t
use resonances, only: resonance_history_t
use cascades
<<Standard module head>>
<<Cascades: test declarations>>
contains
<<Cascades: tests>>
end module cascades_uti
@ %def cascades_ut
@ API: driver for the unit tests below.
<<Cascades: public test>>=
public :: cascades_test
<<Cascades: test driver>>=
subroutine cascades_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<Cascades: execute tests>>
end subroutine cascades_test
@ %def cascades_test
\subsubsection{Check cascade setup}
@ Checking the basic setup up of the phase space cascade parameterizations.
<<Cascades: execute tests>>=
call test (cascades_1, "cascades_1", &
"check cascade setup", &
u, results)
<<Cascades: test declarations>>=
public :: cascades_1
<<Cascades: tests>>=
subroutine cascades_1 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t), dimension(5,2) :: flv
type(cascade_set_t) :: cascade_set
type(phs_parameters_t) :: phs_par
write (u, "(A)") "* Test output: cascades_1"
write (u, "(A)") "* Purpose: test cascade phase space functions"
write (u, "(A)")
write (u, "(A)") "* Initializing"
write (u, "(A)")
call model%init_sm_test ()
call flv(1,1)%init ( 2, model)
call flv(2,1)%init (-2, model)
call flv(3,1)%init ( 1, model)
call flv(4,1)%init (-1, model)
call flv(5,1)%init (21, model)
call flv(1,2)%init ( 2, model)
call flv(2,2)%init (-2, model)
call flv(3,2)%init ( 2, model)
call flv(4,2)%init (-2, model)
call flv(5,2)%init (21, model)
phs_par%sqrts = 1000._default
phs_par%off_shell = 2
write (u, "(A)")
write (u, "(A)") "* Generating the cascades"
write (u, "(A)")
call cascade_set_generate (cascade_set, model, 2, 3, flv, phs_par,.true.)
call cascade_set_write (cascade_set, u)
call cascade_set_write_file_format (cascade_set, u)
write (u, "(A)") "* Cleanup"
write (u, "(A)")
call cascade_set_final (cascade_set)
call model%final ()
write (u, *)
write (u, "(A)") "* Test output end: cascades_1"
end subroutine cascades_1
@ %def cascades_1
@
\subsubsection{Check resonance history}
<<Cascades: execute tests>>=
call test(cascades_2, "cascades_2", &
"Check resonance history", u, results)
<<Cascades: test declarations>>=
public :: cascades_2
<<Cascades: tests>>=
subroutine cascades_2 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t), dimension(5,1) :: flv
type(cascade_set_t) :: cascade_set
type(phs_parameters_t) :: phs_par
type(resonance_history_t), dimension(:), allocatable :: res_hists
integer :: n, i
write (u, "(A)") "* Test output: cascades_2"
write (u, "(A)") "* Purpose: Check resonance history"
write (u, "(A)")
write (u, "(A)") "* Initializing"
write (u, "(A)")
call model%init_sm_test ()
call flv(1,1)%init ( 2, model)
call flv(2,1)%init (-2, model)
call flv(3,1)%init ( 1, model)
call flv(4,1)%init (-1, model)
call flv(5,1)%init (22, model)
phs_par%sqrts = 1000._default
phs_par%off_shell = 2
write (u, "(A)")
write (u, "(A)") "* Generating the cascades"
write (u, "(A)")
call cascade_set_generate (cascade_set, model, 2, 3, flv, phs_par,.true.)
call cascade_set_get_resonance_histories (cascade_set, res_hists = res_hists)
n = cascade_set_get_n_trees (cascade_set)
call assert_equal (u, n, 24, "Number of trees")
do i = 1, size(res_hists)
call res_hists(i)%write (u)
write (u, "(A)")
end do
write (u, "(A)") "* Cleanup"
write (u, "(A)")
call cascade_set_final (cascade_set)
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: cascades_2"
end subroutine cascades_2
@ %def cascades_2
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{A lexer for O'Mega's phase-space output}
This module provides three data types. One of them is the type
[[dag_string_t]] which should contain the information of all Feynman
diagrams in the factorized form which is provided by O'Mega in its
phase-space outout. This output is translated into a string of tokens (in
the form of an a array of the type [[dag_token_t]]) which have a certain
meaning. The purpose of this module is only to identify these tokens
correctly and to provide some procedures and interfaces which allow us to
use these strings in a similar way as variables of the basic character
type or the type [[iso_varying_string]]. Both [[character]] and
[[iso_varying_string]] have some disadvantages at least if one wants to
keep support for some older compiler versions. These can be circumvented
by the [[dag_string_t]] type. Finally the [[dag_chain_t]] type is used
to create a larger string in several steps without always recreating the
string, which is done in the form of a simple linked list. In the end
one can create a single [[dag_string]] out of this list, which is more
useful.
<<[[cascades2_lexer.f90]]>>=
<<File header>>
module cascades2_lexer
<<Use kinds>>
use kinds, only: TC, i8
<<Standard module head>>
<<Cascades2 lexer: public>>
<<Cascades2 lexer: parameters>>
<<Cascades2 lexer: types>>
<<Cascades2 lexer: interfaces>>
interface
<<Cascades2 lexer: sub interfaces>>
end interface
end module cascades2_lexer
@ %def cascades2_lexer
@
<<[[cascades2_lexer_sub.f90]]>>=
<<File header>>
submodule (cascades2_lexer) cascades2_lexer_s
implicit none
contains
<<Cascades2 lexer: procedures>>
end submodule cascades2_lexer_s
@ %def cascades2_lexer_s
@
This is the token type. By default the variable [[type]] is [[EMPTY_TK]]
but can obtain other values corresponding to the parameters defined below.
The type of the token corresponds to a particular sequence of characters.
When the token corresponds to a node of a tree, i.e. some particle in the
Feynman diagram, the type is [[NODE_TK]] and the [[particle_name]] variable
is holding the name of the particle. O'Megas output contains in
addition to the particle name some numbers which indicate the external
momenta that are flowing through this line. These numbers are translated
into a binary code and saved in the variable [[bincode]]. In this case
the number 1 corresponds to a bit set at position 0, 2 corresponds to a
bit set at position 1, etc. Instead of numbers which are composed out of
several digits, letters are used, i.e. A instead of 10 (bit at position 9),
B instead of 11 (bit at position 10), etc.\\
When the DAG is reconstructed from a [[dag_string]] which was built from
O'Mega's output, this string is modified such that a substring (a set of
tokens) is replaced by a single token where the type variable is one of
the three parameters [[DAG_NODE_TK]], [[DAG_OPTIONS_TK]] and
[[DAG_COMBINATION_TK]]. These parameters correspond to the three types
[[dag_node_t]], [[dag_options_t]] and [[dag_combination_t]] (see [[cascades2]]
for more information. In this case, since these objects are organized
in arrays, the [[index]] variable holds the corresponding position in
the array.\\
In any case, we want to be able to reproduce the character string from
which a token (or a string) has been created. The variable [[char_len]]
is the length of this string. For tokens with the type [[DAG_NODE_TK]],
[[DAG_OPTIONS_TK]] and [[DAG_COMBINATION_TK]] we use output of the form
[[<N23>]], [[<O23>]] or [[<C23>]] which is useful for debugging the parser.
Here 23 is the [[index]] and [[N]], [[O]] or [[C]] obviously corresponds
to the [[type]].
<<Cascades2 lexer: parameters>>=
integer, parameter :: PRT_NAME_LEN = 20
@ %def PRT_NAME_LEN
<<Cascades2 lexer: public>>=
public :: dag_token_t
<<Cascades2 lexer: types>>=
type :: dag_token_t
integer :: type = EMPTY_TK
integer :: char_len = 0
integer(TC) :: bincode = 0
character(len=PRT_NAME_LEN) :: particle_name=""
integer :: index = 0
contains
<<Cascades2 lexer: dag token: TBP>>
end type dag_token_t
@ %def dag_token_t
@ This is the string type. It also holds the number of characters in the
corresponding character string. It contains an array of tokens. If the
[[dag_string]] is constructed using the type [[dag_chain_t]], which creates
a linked list, we also need the pointer [[next]].
<<Cascades2 lexer: public>>=
public :: dag_string_t
<<Cascades2 lexer: types>>=
type :: dag_string_t
integer :: char_len = 0
type(dag_token_t), dimension(:), allocatable :: t
type(dag_string_t), pointer :: next => null ()
contains
<<Cascades2 lexer: dag string: TBP>>
end type dag_string_t
@ %def dag_string_t
@ This is the chain of [[dag_strings]]. It allows us to construct a large
string by appending new strings to the linked list, which can later be
merged to a single string. This is very useful because the file written
by O'Mega contains large strings where each string contains all Feynman
diagrams in a factorized form, but these large strings are cut into
several pieces and distributed over many lines. As the file can become
large, rewriting a new [[dag_string]] (or [[iso_varying_string]]) would
consume more and more time with each additional line. For recreating a
single [[dag_string]] out of this chain, we need the total character
length and the sum of all sizes of the [[dag_token]] arrays [[t]].
<<Cascades2 lexer: public>>=
public :: dag_chain_t
<<Cascades2 lexer: types>>=
type :: dag_chain_t
integer :: char_len = 0
integer :: t_size = 0
type(dag_string_t), pointer :: first => null ()
type(dag_string_t), pointer :: last => null ()
contains
<<Cascades2 lexer: dag chain: TBP>>
end type dag_chain_t
@ %def dag_chain_t
@ We define two parameters holding the characters corresponding to a
backslash and a blanc space.
<<Cascades2 lexer: parameters>>=
character(len=1), parameter, public :: BACKSLASH_CHAR = "\\"
character(len=1), parameter :: BLANC_CHAR = " "
@ %def BACKSLASH_CHAR BLANC_CHAR
@ These are the parameters which correspond to meaningful types
of [[token]].
<<Cascades2 lexer: parameters>>=
integer, parameter, public :: NEW_LINE_TK = -2
integer, parameter :: BLANC_SPACE_TK = -1
integer, parameter :: EMPTY_TK = 0
integer, parameter, public :: NODE_TK = 1
integer, parameter, public :: DAG_NODE_TK = 2
integer, parameter, public :: DAG_OPTIONS_TK = 3
integer, parameter, public :: DAG_COMBINATION_TK = 4
integer, parameter, public :: COLON_TK = 11
integer, parameter, public :: COMMA_TK = 12
integer, parameter, public :: VERTICAL_BAR_TK = 13
integer, parameter, public :: OPEN_PAR_TK = 21
integer, parameter, public :: CLOSED_PAR_TK = 22
integer, parameter, public :: OPEN_CURLY_TK = 31
integer, parameter, public :: CLOSED_CURLY_TK = 32
@ %def NEW_LINE_TK BLANC_SPACE_TK EMPTY_TK NODE_TK
@ %def COLON_TK COMMA_TK VERTICAL_LINE_TK OPEN_PAR_TK
@ %def CLOSED_PAR_TK OPEN_CURLY_TK CLOSED_CURLY_TK
@ Different sorts of assignment. This contains the conversion
of a [[character]] variable into a [[dag_token]] or [[dag_string]].
<<Cascades2 lexer: public>>=
public :: assignment (=)
<<Cascades2 lexer: interfaces>>=
interface assignment (=)
module procedure dag_token_assign_from_char_string
module procedure dag_token_assign_from_dag_token
module procedure dag_string_assign_from_dag_token
module procedure dag_string_assign_from_char_string
module procedure dag_string_assign_from_dag_string
module procedure dag_string_assign_from_dag_token_array
end interface assignment (=)
@ %def interfaces
<<Cascades2 lexer: dag token: TBP>>=
procedure :: init_dag_object_token => dag_token_init_dag_object_token
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_token_init_dag_object_token (dag_token, type, index)
class(dag_token_t), intent(out) :: dag_token
integer, intent(in) :: index
integer :: type
end subroutine dag_token_init_dag_object_token
<<Cascades2 lexer: procedures>>=
module subroutine dag_token_init_dag_object_token (dag_token, type, index)
class(dag_token_t), intent(out) :: dag_token
integer, intent(in) :: index
integer :: type
dag_token%type = type
dag_token%char_len = integer_n_dec_digits (index) + 3
dag_token%index = index
contains
function integer_n_dec_digits (number) result (n_digits)
integer, intent(in) :: number
integer :: n_digits
integer :: div_number
n_digits = 0
div_number = number
do
div_number = div_number / 10
n_digits = n_digits + 1
if (div_number == 0) exit
end do
end function integer_n_dec_digits
end subroutine dag_token_init_dag_object_token
@ %def dag_token_init_dag_object_token
<<Cascades2 lexer: sub interfaces>>=
elemental module subroutine dag_token_assign_from_char_string &
(dag_token, char_string)
type(dag_token_t), intent(out) :: dag_token
character(len=*), intent(in) :: char_string
end subroutine dag_token_assign_from_char_string
<<Cascades2 lexer: procedures>>=
elemental module subroutine dag_token_assign_from_char_string &
(dag_token, char_string)
type(dag_token_t), intent(out) :: dag_token
character(len=*), intent(in) :: char_string
integer :: i, j
logical :: set_bincode
integer :: bit_pos
character(len=10) :: index_char
dag_token%char_len = len (char_string)
if (dag_token%char_len == 1) then
select case (char_string(1:1))
case (BACKSLASH_CHAR)
dag_token%type = NEW_LINE_TK
case (" ")
dag_token%type = BLANC_SPACE_TK
case (":")
dag_token%type = COLON_TK
case (",")
dag_token%type = COMMA_TK
case ("|")
dag_token%type = VERTICAL_BAR_TK
case ("(")
dag_token%type = OPEN_PAR_TK
case (")")
dag_token%type = CLOSED_PAR_TK
case ("{")
dag_token%type = OPEN_CURLY_TK
case ("}")
dag_token%type = CLOSED_CURLY_TK
end select
else if (char_string(1:1) == "<") then
select case (char_string(2:2))
case ("N")
dag_token%type = DAG_NODE_TK
case ("O")
dag_token%type = DAG_OPTIONS_TK
case ("C")
dag_token%type = DAG_COMBINATION_TK
end select
read(char_string(3:dag_token%char_len-1), fmt="(I10)") dag_token%index
else
dag_token%bincode = 0
set_bincode = .false.
do i=1, dag_token%char_len
select case (char_string(i:i))
case ("[")
dag_token%type = NODE_TK
if (i > 1) then
do j = 1, i - 1
dag_token%particle_name(j:j) = char_string(j:j)
end do
end if
set_bincode = .true.
case ("]")
set_bincode = .false.
case default
dag_token%type = NODE_TK
if (set_bincode) then
select case (char_string(i:i))
case ("1", "2", "3", "4", "5", "6", "7", "8", "9")
read (char_string(i:i), fmt="(I1)") bit_pos
case ("A")
bit_pos = 10
case ("B")
bit_pos = 11
case ("C")
bit_pos = 12
end select
dag_token%bincode = ibset(dag_token%bincode, bit_pos - 1)
end if
end select
if (dag_token%type /= NODE_TK) exit
end do
end if
end subroutine dag_token_assign_from_char_string
@ %def dag_token_assign_from_char_string
<<Cascades2 lexer: sub interfaces>>=
elemental module subroutine dag_token_assign_from_dag_token &
(token_out, token_in)
type(dag_token_t), intent(out) :: token_out
type(dag_token_t), intent(in) :: token_in
end subroutine dag_token_assign_from_dag_token
<<Cascades2 lexer: procedures>>=
elemental module subroutine dag_token_assign_from_dag_token &
(token_out, token_in)
type(dag_token_t), intent(out) :: token_out
type(dag_token_t), intent(in) :: token_in
token_out%type = token_in%type
token_out%char_len = token_in%char_len
token_out%bincode = token_in%bincode
token_out%particle_name = token_in%particle_name
token_out%index = token_in%index
end subroutine dag_token_assign_from_dag_token
@ %def dag_token_assign_from_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module subroutine dag_string_assign_from_dag_token &
(dag_string, dag_token)
type(dag_string_t), intent(out) :: dag_string
type(dag_token_t), intent(in) :: dag_token
end subroutine dag_string_assign_from_dag_token
<<Cascades2 lexer: procedures>>=
elemental module subroutine dag_string_assign_from_dag_token &
(dag_string, dag_token)
type(dag_string_t), intent(out) :: dag_string
type(dag_token_t), intent(in) :: dag_token
allocate (dag_string%t(1))
dag_string%t(1) = dag_token
dag_string%char_len = dag_token%char_len
end subroutine dag_string_assign_from_dag_token
@ %def dag_string_assign_from_dag_token
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_string_assign_from_dag_token_array &
(dag_string, dag_token)
type(dag_string_t), intent(out) :: dag_string
type(dag_token_t), dimension(:), intent(in) :: dag_token
end subroutine dag_string_assign_from_dag_token_array
<<Cascades2 lexer: procedures>>=
module subroutine dag_string_assign_from_dag_token_array &
(dag_string, dag_token)
type(dag_string_t), intent(out) :: dag_string
type(dag_token_t), dimension(:), intent(in) :: dag_token
allocate (dag_string%t(size(dag_token)))
dag_string%t = dag_token
dag_string%char_len = sum(dag_token%char_len)
end subroutine dag_string_assign_from_dag_token_array
@ %def dag_string_assign_from_dag_token_array
<<Cascades2 lexer: sub interfaces>>=
elemental module subroutine dag_string_assign_from_char_string &
(dag_string, char_string)
type(dag_string_t), intent(out) :: dag_string
character(len=*), intent(in) :: char_string
end subroutine dag_string_assign_from_char_string
<<Cascades2 lexer: procedures>>=
elemental module subroutine dag_string_assign_from_char_string &
(dag_string, char_string)
type(dag_string_t), intent(out) :: dag_string
character(len=*), intent(in) :: char_string
type(dag_token_t), dimension(:), allocatable :: token
integer :: token_pos
integer :: i
character(len=len(char_string)) :: node_char
integer :: node_char_len
node_char = ""
dag_string%char_len = len (char_string)
if (dag_string%char_len > 0) then
allocate (token(dag_string%char_len))
token_pos = 0
node_char_len = 0
do i=1, dag_string%char_len
select case (char_string(i:i))
case (BACKSLASH_CHAR, " ", ":", ",", "|", "(", ")", "{", "}")
if (node_char_len > 0) then
token_pos = token_pos + 1
token(token_pos) = node_char(:node_char_len)
node_char_len = 0
end if
token_pos = token_pos + 1
token(token_pos) = char_string(i:i)
case default
node_char_len = node_char_len + 1
node_char(node_char_len:node_char_len) = char_string(i:i)
end select
end do
if (node_char_len > 0) then
token_pos = token_pos + 1
token(token_pos) = node_char(:node_char_len)
end if
if (token_pos > 0) then
allocate (dag_string%t(token_pos))
dag_string%t = token(:token_pos)
deallocate (token)
end if
end if
end subroutine dag_string_assign_from_char_string
@ %def dag_string_assign_from_char_string
<<Cascades2 lexer: sub interfaces>>=
elemental module subroutine dag_string_assign_from_dag_string &
(string_out, string_in)
type(dag_string_t), intent(out) :: string_out
type(dag_string_t), intent(in) :: string_in
end subroutine dag_string_assign_from_dag_string
<<Cascades2 lexer: procedures>>=
elemental module subroutine dag_string_assign_from_dag_string &
(string_out, string_in)
type(dag_string_t), intent(out) :: string_out
type(dag_string_t), intent(in) :: string_in
if (allocated (string_in%t)) then
allocate (string_out%t (size(string_in%t)))
string_out%t = string_in%t
end if
string_out%char_len = string_in%char_len
end subroutine dag_string_assign_from_dag_string
@ %def dag_string_assign_from_dag_string
@ Concatenate strings/tokens. The result is always a [[dag_string]].
<<Cascades2 lexer: public>>=
public :: operator (//)
<<Cascades2 lexer: interfaces>>=
interface operator (//)
module procedure concat_dag_token_dag_token
module procedure concat_dag_string_dag_token
module procedure concat_dag_token_dag_string
module procedure concat_dag_string_dag_string
end interface operator (//)
@ %def interfaces
<<Cascades2 lexer: sub interfaces>>=
module function concat_dag_token_dag_token &
(token1, token2) result (res_string)
type(dag_token_t), intent(in) :: token1, token2
type(dag_string_t) :: res_string
end function concat_dag_token_dag_token
<<Cascades2 lexer: procedures>>=
module function concat_dag_token_dag_token &
(token1, token2) result (res_string)
type(dag_token_t), intent(in) :: token1, token2
type(dag_string_t) :: res_string
if (token1%type == EMPTY_TK) then
call dag_string_assign_from_dag_token (res_string, token2)
else if (token2%type == EMPTY_TK) then
call dag_string_assign_from_dag_token (res_string, token1)
else
allocate (res_string%t(2))
res_string%t(1) = token1
res_string%t(2) = token2
res_string%char_len = token1%char_len + token2%char_len
end if
end function concat_dag_token_dag_token
@ %def concat_dag_token_dag_token
<<Cascades2 lexer: sub interfaces>>=
module function concat_dag_string_dag_token &
(dag_string, dag_token) result (res_string)
type(dag_string_t), intent(in) :: dag_string
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t) :: res_string
end function concat_dag_string_dag_token
<<Cascades2 lexer: procedures>>=
module function concat_dag_string_dag_token &
(dag_string, dag_token) result (res_string)
type(dag_string_t), intent(in) :: dag_string
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t) :: res_string
integer :: t_size
if (dag_string%char_len == 0) then
call dag_string_assign_from_dag_token (res_string, dag_token)
else if (dag_token%type == EMPTY_TK) then
res_string = dag_string
else
t_size = size (dag_string%t)
allocate (res_string%t(t_size+1))
res_string%t(:t_size) = dag_string%t
res_string%t(t_size+1) = dag_token
res_string%char_len = dag_string%char_len + dag_token%char_len
end if
end function concat_dag_string_dag_token
@ %def concat_dag_string_dag_token
<<Cascades2 lexer: sub interfaces>>=
module function concat_dag_token_dag_string &
(dag_token, dag_string) result (res_string)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
type(dag_string_t) :: res_string
integer :: t_size
end function concat_dag_token_dag_string
<<Cascades2 lexer: procedures>>=
module function concat_dag_token_dag_string &
(dag_token, dag_string) result (res_string)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
type(dag_string_t) :: res_string
integer :: t_size
if (dag_token%type == EMPTY_TK) then
res_string = dag_string
else if (dag_string%char_len == 0) then
call dag_string_assign_from_dag_token (res_string, dag_token)
else
t_size = size (dag_string%t)
allocate (res_string%t(t_size+1))
res_string%t(2:t_size+1) = dag_string%t
res_string%t(1) = dag_token
res_string%char_len = dag_token%char_len + dag_string%char_len
end if
end function concat_dag_token_dag_string
@ %def concat_dag_token_dag_string
<<Cascades2 lexer: sub interfaces>>=
module function concat_dag_string_dag_string &
(string1, string2) result (res_string)
type(dag_string_t), intent(in) :: string1, string2
type(dag_string_t) :: res_string
end function concat_dag_string_dag_string
<<Cascades2 lexer: procedures>>=
module function concat_dag_string_dag_string &
(string1, string2) result (res_string)
type(dag_string_t), intent(in) :: string1, string2
type(dag_string_t) :: res_string
integer :: t1_size, t2_size, t_size
if (string1%char_len == 0) then
res_string = string2
else if (string2%char_len == 0) then
res_string = string1
else
t1_size = size (string1%t)
t2_size = size (string2%t)
t_size = t1_size + t2_size
if (t_size > 0) then
allocate (res_string%t(t_size))
res_string%t(:t1_size) = string1%t
res_string%t(t1_size+1:) = string2%t
res_string%char_len = string1%char_len + string2%char_len
end if
end if
end function concat_dag_string_dag_string
@ %def concat_dag_string_dag_string
@ Compare strings/tokens/characters. Each character is relevant, including
all blanc spaces. An exception is the [[newline]] character which is not
treated by the types used in this module (not to confused with the type
parameter [[NEW_LINE_TK]] which corresponds to the backslash character
and simply tells us that the string continues on the next line in the file).
<<Cascades2 lexer: public>>=
public :: operator (==)
<<Cascades2 lexer: interfaces>>=
interface operator (==)
module procedure dag_token_eq_dag_token
module procedure dag_string_eq_dag_string
module procedure dag_token_eq_dag_string
module procedure dag_string_eq_dag_token
module procedure dag_token_eq_char_string
module procedure char_string_eq_dag_token
module procedure dag_string_eq_char_string
module procedure char_string_eq_dag_string
end interface operator (==)
@ %def interfaces
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_token_eq_dag_token &
(token1, token2) result (flag)
type(dag_token_t), intent(in) :: token1, token2
logical :: flag
end function dag_token_eq_dag_token
<<Cascades2 lexer: procedures>>=
elemental module function dag_token_eq_dag_token &
(token1, token2) result (flag)
type(dag_token_t), intent(in) :: token1, token2
logical :: flag
flag = (token1%type == token2%type) .and. &
(token1%char_len == token2%char_len) .and. &
(token1%bincode == token2%bincode) .and. &
(token1%index == token2%index) .and. &
(token1%particle_name == token2%particle_name)
end function dag_token_eq_dag_token
@ %def dag_token_eq_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_string_eq_dag_string &
(string1, string2) result (flag)
type(dag_string_t), intent(in) :: string1, string2
logical :: flag
end function dag_string_eq_dag_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_string_eq_dag_string &
(string1, string2) result (flag)
type(dag_string_t), intent(in) :: string1, string2
logical :: flag
flag = (string1%char_len == string2%char_len) .and. &
(allocated (string1%t) .eqv. allocated (string2%t))
if (flag) then
if (allocated (string1%t)) flag = all (string1%t == string2%t)
end if
end function dag_string_eq_dag_string
@ %def dag_string_eq_dag_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_token_eq_dag_string &
(dag_token, dag_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
end function dag_token_eq_dag_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_token_eq_dag_string &
(dag_token, dag_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
flag = size (dag_string%t) == 1 .and. &
dag_string%char_len == dag_token%char_len
if (flag) flag = (dag_string%t(1) == dag_token)
end function dag_token_eq_dag_string
@ %def dag_token_eq_dag_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_string_eq_dag_token &
(dag_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
end function dag_string_eq_dag_token
<<Cascades2 lexer: procedures>>=
elemental module function dag_string_eq_dag_token &
(dag_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
flag = (dag_token == dag_string)
end function dag_string_eq_dag_token
@ %def dag_string_eq_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_token_eq_char_string &
(dag_token, char_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
end function dag_token_eq_char_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_token_eq_char_string &
(dag_token, char_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
flag = (char (dag_token) == char_string)
end function dag_token_eq_char_string
@ %def dag_token_eq_char_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function char_string_eq_dag_token &
(char_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
end function char_string_eq_dag_token
<<Cascades2 lexer: procedures>>=
elemental module function char_string_eq_dag_token &
(char_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
flag = (char (dag_token) == char_string)
end function char_string_eq_dag_token
@ %def char_string_eq_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_string_eq_char_string &
(dag_string, char_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
end function dag_string_eq_char_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_string_eq_char_string &
(dag_string, char_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
flag = (char (dag_string) == char_string)
end function dag_string_eq_char_string
@ %def dag_string_eq_char_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function char_string_eq_dag_string &
(char_string, dag_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
end function char_string_eq_dag_string
<<Cascades2 lexer: procedures>>=
elemental module function char_string_eq_dag_string &
(char_string, dag_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
flag = (char (dag_string) == char_string)
end function char_string_eq_dag_string
@ %def char_string_eq_dag_string
<<Cascades2 lexer: public>>=
public :: operator (/=)
<<Cascades2 lexer: interfaces>>=
interface operator (/=)
module procedure dag_token_ne_dag_token
module procedure dag_string_ne_dag_string
module procedure dag_token_ne_dag_string
module procedure dag_string_ne_dag_token
module procedure dag_token_ne_char_string
module procedure char_string_ne_dag_token
module procedure dag_string_ne_char_string
module procedure char_string_ne_dag_string
end interface operator (/=)
@ %def interfaces
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_token_ne_dag_token &
(token1, token2) result (flag)
type(dag_token_t), intent(in) :: token1, token2
logical :: flag
end function dag_token_ne_dag_token
<<Cascades2 lexer: procedures>>=
elemental module function dag_token_ne_dag_token &
(token1, token2) result (flag)
type(dag_token_t), intent(in) :: token1, token2
logical :: flag
flag = .not. (token1 == token2)
end function dag_token_ne_dag_token
@ %def dag_token_ne_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_string_ne_dag_string &
(string1, string2) result (flag)
type(dag_string_t), intent(in) :: string1, string2
logical :: flag
end function dag_string_ne_dag_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_string_ne_dag_string &
(string1, string2) result (flag)
type(dag_string_t), intent(in) :: string1, string2
logical :: flag
flag = .not. (string1 == string2)
end function dag_string_ne_dag_string
@ %def dag_string_ne_dag_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_token_ne_dag_string &
(dag_token, dag_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
end function dag_token_ne_dag_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_token_ne_dag_string &
(dag_token, dag_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
flag = .not. (dag_token == dag_string)
end function dag_token_ne_dag_string
@ %def dag_token_ne_dag_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_string_ne_dag_token &
(dag_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
end function dag_string_ne_dag_token
<<Cascades2 lexer: procedures>>=
elemental module function dag_string_ne_dag_token &
(dag_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
type(dag_string_t), intent(in) :: dag_string
logical :: flag
flag = .not. (dag_string == dag_token)
end function dag_string_ne_dag_token
@ %def dag_string_ne_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_token_ne_char_string &
(dag_token, char_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
end function dag_token_ne_char_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_token_ne_char_string &
(dag_token, char_string) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
flag = .not. (dag_token == char_string)
end function dag_token_ne_char_string
@ %def dag_token_ne_char_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function char_string_ne_dag_token &
(char_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
end function char_string_ne_dag_token
<<Cascades2 lexer: procedures>>=
elemental module function char_string_ne_dag_token &
(char_string, dag_token) result (flag)
type(dag_token_t), intent(in) :: dag_token
character(len=*), intent(in) :: char_string
logical :: flag
flag = .not. (char_string == dag_token)
end function char_string_ne_dag_token
@ %def char_string_ne_dag_token
<<Cascades2 lexer: sub interfaces>>=
elemental module function dag_string_ne_char_string &
(dag_string, char_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
end function dag_string_ne_char_string
<<Cascades2 lexer: procedures>>=
elemental module function dag_string_ne_char_string &
(dag_string, char_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
flag = .not. (dag_string == char_string)
end function dag_string_ne_char_string
@ %def dag_string_ne_char_string
<<Cascades2 lexer: sub interfaces>>=
elemental module function char_string_ne_dag_string &
(char_string, dag_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
end function char_string_ne_dag_string
<<Cascades2 lexer: procedures>>=
elemental module function char_string_ne_dag_string &
(char_string, dag_string) result (flag)
type(dag_string_t), intent(in) :: dag_string
character(len=*), intent(in) :: char_string
logical :: flag
flag = .not. (char_string == dag_string)
end function char_string_ne_dag_string
@ %def char_string_ne_dag_string
@ Convert a [[dag_token]] or [[dag_string]] to character.
<<Cascades2 lexer: public>>=
public :: char
<<Cascades2 lexer: interfaces>>=
interface char
module procedure char_dag_token
module procedure char_dag_string
end interface char
@ %def interfaces
<<Cascades2 lexer: sub interfaces>>=
pure module function char_dag_token (dag_token) result (char_string)
type(dag_token_t), intent(in) :: dag_token
character (dag_token%char_len) :: char_string
end function char_dag_token
<<Cascades2 lexer: procedures>>=
pure module function char_dag_token (dag_token) result (char_string)
type(dag_token_t), intent(in) :: dag_token
character (dag_token%char_len) :: char_string
integer :: i
integer :: name_len
integer :: bc_pos
integer :: n_digits
character(len=9) :: fmt_spec
select case (dag_token%type)
case (EMPTY_TK)
char_string = ""
case (NEW_LINE_TK)
char_string = BACKSLASH_CHAR
case (BLANC_SPACE_TK)
char_string = " "
case (COLON_TK)
char_string = ":"
case (COMMA_TK)
char_string = ","
case (VERTICAL_BAR_TK)
char_string = "|"
case (OPEN_PAR_TK)
char_string = "("
case (CLOSED_PAR_TK)
char_string = ")"
case (OPEN_CURLY_TK)
char_string = "{"
case (CLOSED_CURLY_TK)
char_string = "}"
case (DAG_NODE_TK, DAG_OPTIONS_TK, DAG_COMBINATION_TK)
n_digits = dag_token%char_len - 3
fmt_spec = ""
if (n_digits > 9) then
write (fmt_spec, fmt="(A,I2,A)") "(A,I", n_digits, ",A)"
else
write (fmt_spec, fmt="(A,I1,A)") "(A,I", n_digits, ",A)"
end if
select case (dag_token%type)
case (DAG_NODE_TK)
write (char_string, fmt=fmt_spec) "<N", dag_token%index, ">"
case (DAG_OPTIONS_TK)
write (char_string, fmt=fmt_spec) "<O", dag_token%index, ">"
case (DAG_COMBINATION_TK)
write (char_string, fmt=fmt_spec) "<C", dag_token%index, ">"
end select
case (NODE_TK)
name_len = len_trim (dag_token%particle_name)
char_string = dag_token%particle_name
bc_pos = name_len + 1
char_string(bc_pos:bc_pos) = "["
do i=0, bit_size (dag_token%bincode) - 1
if (btest (dag_token%bincode, i)) then
bc_pos = bc_pos + 1
select case (i)
case (0, 1, 2, 3, 4, 5, 6, 7, 8)
write (char_string(bc_pos:bc_pos), fmt="(I1)") i + 1
case (9)
write (char_string(bc_pos:bc_pos), fmt="(A1)") "A"
case (10)
write (char_string(bc_pos:bc_pos), fmt="(A1)") "B"
case (11)
write (char_string(bc_pos:bc_pos), fmt="(A1)") "C"
end select
bc_pos = bc_pos + 1
if (bc_pos == dag_token%char_len) then
write (char_string(bc_pos:bc_pos), fmt="(A1)") "]"
return
else
write (char_string(bc_pos:bc_pos), fmt="(A1)") "/"
end if
end if
end do
end select
end function char_dag_token
@ %def char_dag_token
<<Cascades2 lexer: sub interfaces>>=
pure module function char_dag_string (dag_string) result (char_string)
type(dag_string_t), intent(in) :: dag_string
character (dag_string%char_len) :: char_string
end function char_dag_string
<<Cascades2 lexer: procedures>>=
pure module function char_dag_string (dag_string) result (char_string)
type(dag_string_t), intent(in) :: dag_string
character (dag_string%char_len) :: char_string
integer :: pos
integer :: i
char_string = ""
pos = 0
do i=1, size(dag_string%t)
char_string(pos+1:pos+dag_string%t(i)%char_len) = char (dag_string%t(i))
pos = pos + dag_string%t(i)%char_len
end do
end function char_dag_string
@ %def char_dag_string
@ Remove all tokens which are irrelevant for parsing. These are of type
[[NEW_LINE_TK]], [[BLANC_SPACE_TK]] and [[EMTPY_TK]].
<<Cascades2 lexer: dag string: TBP>>=
procedure :: clean => dag_string_clean
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_string_clean (dag_string)
class(dag_string_t), intent(inout) :: dag_string
end subroutine dag_string_clean
<<Cascades2 lexer: procedures>>=
module subroutine dag_string_clean (dag_string)
class(dag_string_t), intent(inout) :: dag_string
type(dag_token_t), dimension(:), allocatable :: tmp_token
integer :: n_keep
integer :: i
n_keep = 0
dag_string%char_len = 0
allocate (tmp_token (size(dag_string%t)))
do i=1, size (dag_string%t)
select case (dag_string%t(i)%type)
case(NEW_LINE_TK, BLANC_SPACE_TK, EMPTY_TK)
case default
n_keep = n_keep + 1
tmp_token(n_keep) = dag_string%t(i)
dag_string%char_len = dag_string%char_len + dag_string%t(i)%char_len
end select
end do
deallocate (dag_string%t)
allocate (dag_string%t(n_keep))
dag_string%t = tmp_token(:n_keep)
end subroutine dag_string_clean
@ %def dag_string_clean
@ If we operate explicitly on the [[token]] array [[t]] of a [[dag_string]],
the variable [[char_len]] is not automatically modified. It can however be
determined afterwards using the following subroutine.
<<Cascades2 lexer: dag string: TBP>>=
procedure :: update_char_len => dag_string_update_char_len
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_string_update_char_len (dag_string)
class(dag_string_t), intent(inout) :: dag_string
end subroutine dag_string_update_char_len
<<Cascades2 lexer: procedures>>=
module subroutine dag_string_update_char_len (dag_string)
class(dag_string_t), intent(inout) :: dag_string
integer :: char_len
integer :: i
char_len = 0
if (allocated (dag_string%t)) then
do i=1, size (dag_string%t)
char_len = char_len + dag_string%t(i)%char_len
end do
end if
dag_string%char_len = char_len
end subroutine dag_string_update_char_len
@ %def dag_string_update_char_len
@ Append a [[dag_string]] to a [[dag_chain]]. The argument [[char_string]]
is of type [[character]] because the subroutine is used for reading from
the file produced by O'Mega which is first read line by line to a character
variable.
<<Cascades2 lexer: dag chain: TBP>>=
procedure :: append => dag_chain_append_string
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_chain_append_string (dag_chain, char_string)
class(dag_chain_t), intent(inout) :: dag_chain
character(len=*), intent(in) :: char_string
end subroutine dag_chain_append_string
<<Cascades2 lexer: procedures>>=
module subroutine dag_chain_append_string (dag_chain, char_string)
class(dag_chain_t), intent(inout) :: dag_chain
character(len=*), intent(in) :: char_string
if (.not. associated (dag_chain%first)) then
allocate (dag_chain%first)
dag_chain%last => dag_chain%first
else
allocate (dag_chain%last%next)
dag_chain%last => dag_chain%last%next
end if
dag_chain%last = char_string
dag_chain%char_len = dag_chain%char_len + dag_chain%last%char_len
dag_chain%t_size = dag_chain%t_size + size (dag_chain%last%t)
end subroutine dag_chain_append_string
@ %def dag_chain_append_string
@ Reduce the linked list of [[dag_string]] objects which are attached
to a given [[dag_chain]] object to a single [[dag_string]].
<<Cascades2 lexer: dag chain: TBP>>=
procedure :: compress => dag_chain_compress
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_chain_compress (dag_chain)
class(dag_chain_t), intent(inout) :: dag_chain
end subroutine dag_chain_compress
<<Cascades2 lexer: procedures>>=
module subroutine dag_chain_compress (dag_chain)
class(dag_chain_t), intent(inout) :: dag_chain
type(dag_string_t), pointer :: current
type(dag_string_t), pointer :: remove
integer :: filled_t
current => dag_chain%first
dag_chain%first => null ()
allocate (dag_chain%first)
dag_chain%last => dag_chain%first
dag_chain%first%char_len = dag_chain%char_len
allocate (dag_chain%first%t (dag_chain%t_size))
filled_t = 0
do while (associated (current))
dag_chain%first%t(filled_t+1:filled_t+size(current%t)) = current%t
filled_t = filled_t + size (current%t)
remove => current
current => current%next
deallocate (remove)
end do
end subroutine dag_chain_compress
@ %def dag_chain_compress
@ Finalizer for [[dag_string_t]].
<<Cascades2 lexer: dag string: TBP>>=
procedure :: final => dag_string_final
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_string_final (dag_string)
class(dag_string_t), intent(inout) :: dag_string
end subroutine dag_string_final
<<Cascades2 lexer: procedures>>=
module subroutine dag_string_final (dag_string)
class(dag_string_t), intent(inout) :: dag_string
if (allocated (dag_string%t)) deallocate (dag_string%t)
dag_string%next => null ()
end subroutine dag_string_final
@ %def dag_string_final
@ Finalizer for [[dag_chain_t]].
<<Cascades2 lexer: dag chain: TBP>>=
procedure :: final => dag_chain_final
<<Cascades2 lexer: sub interfaces>>=
module subroutine dag_chain_final (dag_chain)
class(dag_chain_t), intent(inout) :: dag_chain
end subroutine dag_chain_final
<<Cascades2 lexer: procedures>>=
module subroutine dag_chain_final (dag_chain)
class(dag_chain_t), intent(inout) :: dag_chain
type(dag_string_t), pointer :: current
current => dag_chain%first
do while (associated (current))
dag_chain%first => dag_chain%first%next
call current%final ()
deallocate (current)
current => dag_chain%first
end do
dag_chain%last => null ()
end subroutine dag_chain_final
@ %def dag_chain_final
<<[[cascades2_lexer_ut.f90]]>>=
<<File header>>
module cascades2_lexer_ut
use unit_tests
use cascades2_lexer_uti
<<Standard module head>>
<<Cascades2 lexer: public test>>
contains
<<Cascades2 lexer: test driver>>
end module cascades2_lexer_ut
@ %def cascades2_lexer_ut
@
<<[[cascades2_lexer_uti.f90]]>>=
<<File header>>
module cascades2_lexer_uti
<<Use kinds>>
<<Use strings>>
use numeric_utils
use cascades2_lexer
<<Standard module head>>
<<Cascades2 lexer: test declarations>>
contains
<<Cascades2 lexer: tests>>
end module cascades2_lexer_uti
@ %def cascades2_lexer_uti
@ API: driver for the unit tests below.
<<Cascades2 lexer: public test>>=
public :: cascades2_lexer_test
<<Cascades2 lexer: test driver>>=
subroutine cascades2_lexer_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<Cascades2 lexer: execute tests>>
end subroutine cascades2_lexer_test
@ %def cascades2_lexer_test
@
<<Cascades2 lexer: execute tests>>=
call test (cascades2_lexer_1, "cascades2_lexer_1", &
"make phase-space", u, results)
<<Cascades2 lexer: test declarations>>=
public :: cascades2_lexer_1
<<Cascades2 lexer: tests>>=
subroutine cascades2_lexer_1 (u)
integer, intent(in) :: u
integer :: u_in = 8
character(len=300) :: line
integer :: stat
logical :: fail
type(dag_string_t) :: dag_string
write (u, "(A)") "* Test output: cascades2_lexer_1"
write (u, "(A)") "* Purpose: read lines of O'Mega's phase space output, translate"
write (u, "(A)") "* to dag_string, retranslate to character string and"
write (u, "(A)") "* compare"
write (u, "(A)")
open (unit=u_in, file="cascades2_lexer_1.fds", status='old', action='read')
stat = 0
fail = .false.
read (unit=u_in, fmt="(A)", iostat=stat) line
do while (stat == 0 .and. .not. fail)
read (unit=u_in, fmt="(A)", iostat=stat) line
if (stat /= 0) exit
dag_string = line
fail = (char(dag_string) /= line)
end do
if (fail) then
write (u, "(A)") "* Test result: Test failed!"
else
write (u, "(A)") "* Test result: Test passed"
end if
close (u_in)
write (u, *)
write (u, "(A)") "* Test output end: cascades2_lexer_1"
end subroutine cascades2_lexer_1
@ %def cascades2_lexer_1
@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{An alternative cascades module}
This module might replace the module [[cascades]], which generates
suitable phase space parametrizations and generates the phase space file.
The mappings, as well as the criteria to determine these, do not change.
The advantage of this module is that it makes use of the [[O'Mega]] matrix
element generator which provides the relevant Feynman diagrams (the ones
which can be constructed only from 3-vertices). In principle, the
construction of these diagrams is also one of the tasks of the existing
[[cascades]] module, in which the diagrams would correspond to a set of
cascades. It starts by creating cascades which correspond to the
outgoing particles. These are combined to a new cascade using the
vertices of the model. In this way, since each cascade knows the
daughter cascades from which it is built, complete Feynman diagrams are
represented by sets of cascades, as soon as the existing cascades can be
recombined with the incoming particle(s).
In this module, the Feynman diagrams are represented by the type
[[feyngraph_t]], which represents the Feynman diagrams as a tree of
nodes. The object which contains the necessary kinematical information
to determine mappings, and hence sensible phase space parametrizations
is of another type, called [[kingraph_t]], which is built from a
corresponding [[feyngraph]] object.
There are two types of output which can be produced by [[O'Mega]] and
are potentially relevant here. The first type contains all tree
diagrams for the process under consideration, where each line of the
output corresponds to one Feynman diagram. This output is easy to read,
but can be very large, depending on the number of particles involved in
the process. Moreover, it repeats substructures of the diagrams which
are part of more than one diagram. One could in principle work with
this output and construct a [[feyngraph]] from each line, if allowed,
i.e. if there are only 3-vertices.
The other output contains also all of these Feynman diagrams, but in
a factorized form. This means that the substructures which appear in
several Feynman diagrams, are written only once, if possible. This
leads to a much shorter input file, which speeds up the parsing
process. Furthermore it makes it possible to reconstruct the
[[feyngraphs]] in such a way that the calculations concerning
subdiagrams which reappear in other [[feyngraphs]] have to be
performed only once. This is already the case in the existing
[[cascades]] module but can be exploited more efficiently here
because the possible graphs are well known from the input file, whereas
the [[cascades]] module would create a large number of [[cascades]]
which do not lead to a complete Feynman diagram of the given process.
<<[[cascades2.f90]]>>=
<<File header>>
module cascades2
use, intrinsic :: iso_fortran_env, only : input_unit, output_unit, error_unit
<<Use kinds>>
use kinds, only: TC, i8
<<Use strings>>
<<Use debug>>
use diagnostics
use flavors
use model_data
use phs_forests, only: phs_parameters_t
use resonances, only: resonance_info_t
use resonances, only: resonance_history_t
use resonances, only: resonance_history_set_t
use cascades2_lexer
<<Standard module head>>
<<Cascades2: public>>
<<Cascades2: parameters>>
<<Cascades2: types>>
<<Cascades2: interfaces>>
interface
<<Cascades2: sub interfaces>>
end interface
contains
<<Cascades2: main procedures>>
end module cascades2
@ %def cascades2
@
<<[[cascades2_sub.f90]]>>=
<<File header>>
submodule (cascades2) cascades2_s
use sorting
use io_units
use physics_defs, only: SCALAR, SPINOR, VECTOR, VECTORSPINOR, TENSOR
use hashes
use cascades, only: phase_space_vanishes, MAX_WARN_RESONANCE
implicit none
contains
<<Cascades2: procedures>>
end submodule cascades2_s
@ %def cascades2_s
@
\subsection{Particle properties}
We define a type holding the properties of the particles which are needed
for parsing and finding the phase space parametrizations and mappings.
The properties of all particles which appear in the parsed
Feynman diagrams for the given process will be stored in a central place,
and only pointers to these objects are used.
<<Cascades2: types>>=
type :: part_prop_t
character(len=LABEL_LEN) :: particle_label
integer :: pdg = 0
real(default) :: mass = 0.
real :: width = 0.
integer :: spin_type = 0
logical :: is_vector = .false.
logical :: empty = .true.
type(part_prop_t), pointer :: anti => null ()
type(string_t) :: tex_name
contains
<<Cascades2: part prop: TBP>>
end type part_prop_t
@ %def part_prop_t
@ The [[particle_label]] in [[part_prop_t]] is simply the particle name
(e.g. 'W+'). The corresponding variable in the type [[f_node_t]] contains
some additional information related to the external momenta, see below.
The length of the [[character]] variable is fixed as:
<<Cascades2: parameters>>=
integer, parameter :: LABEL_LEN=30
@ %def LABEL_LEN
<<Cascades2: part prop: TBP>>=
procedure :: final => part_prop_final
<<Cascades2: sub interfaces>>=
module subroutine part_prop_final (part)
class(part_prop_t), intent(inout) :: part
end subroutine part_prop_final
<<Cascades2: procedures>>=
module subroutine part_prop_final (part)
class(part_prop_t), intent(inout) :: part
part%anti => null ()
end subroutine part_prop_final
@ %def part_prop_final
@
\subsection{The mapping modes}
The possible mappings are essentially the same as in [[cascades]], but we
introduce in addition the mapping constant [[NON_RESONANT]], which does
not refer to a new mapping; it corresponds to the nonresonant version of
a potentially resonant particle (or [[k_node]]). This becomes relevant
when we compare [[k_nodes]] to eliminate equivalences.
<<Cascades2: parameters>>=
integer, parameter :: &
& NONRESONANT = -2, EXTERNAL_PRT = -1, &
& NO_MAPPING = 0, S_CHANNEL = 1, T_CHANNEL = 2, U_CHANNEL = 3, &
& RADIATION = 4, COLLINEAR = 5, INFRARED = 6, &
& STEP_MAPPING_E = 11, STEP_MAPPING_H = 12, &
& ON_SHELL = 99
@ %def NONRESONANT EXTERNAL_PRT
@ %def NO_MAPPING S_CHANNEL T_CHANNEL U_CHANNEL
@ %def RADIATION COLLINEAR INFRARED
@ %def STEP_MAPPING_E STEP_MAPPING_H
@ %def ON_SHELL
@
\subsection{Grove properties}
The channels or [[kingraphs]] will be grouped in groves, i.e. sets of
channels, which share some characteristic numbers. These numbers are
stored in the following type:
<<Cascades2: types>>=
type :: grove_prop_t
integer :: multiplicity = 0
integer :: n_resonances = 0
integer :: n_log_enhanced = 0
integer :: n_off_shell = 0
integer :: n_t_channel = 0
integer :: res_hash = 0
end type grove_prop_t
@ %def grove_prop_t
@
\subsection{The tree type}
This type contains all the information which is needed to
reconstruct a [[feyngraph]] or [[kingraph]]. We store bincodes, pdg codes
and mappings for all nodes of a valid [[kingraph]]. If we label the
external particles as given in the process definition with integer
numbers representing their position in the process definition, the bincode
would be the number that one obtains by setting the bit at the position
that is given by this number. If we combine two particles/nodes to a third
one (using a three-vertex of the given model), the bincode is the number which
one obtains by setting all the bits which are set for the two particles.
The [[pdg]] and [[mapping]] are simply the pdg-code and mapping at the
position (i.e. propagator or external particle) which is specified by the
corresponding bincode. We use [[tree_t]] not only for completed [[kingraphs]],
but also for all [[k_nodes]], which are a subtree of a [[kingraph]].
<<Cascades2: types>>=
type :: tree_t
integer(TC), dimension(:), allocatable :: bc
integer, dimension(:), allocatable :: pdg
integer, dimension(:), allocatable :: mapping
integer :: n_entries = 0
logical :: keep = .true.
logical :: empty = .true.
contains
<<Cascades2: tree: TBP>>
end type tree_t
@ %def tree_t
<<Cascades2: tree: TBP>>=
procedure :: final => tree_final
<<Cascades2: sub interfaces>>=
module subroutine tree_final (tree)
class(tree_t), intent(inout) :: tree
end subroutine tree_final
<<Cascades2: procedures>>=
module subroutine tree_final (tree)
class(tree_t), intent(inout) :: tree
if (allocated (tree%bc)) deallocate (tree%bc)
if (allocated (tree%pdg)) deallocate (tree%pdg)
if (allocated (tree%mapping)) deallocate (tree%mapping)
end subroutine tree_final
@ %def tree_final
<<Cascades2: interfaces>>=
interface assignment (=)
module procedure tree_assign
end interface assignment (=)
<<Cascades2: sub interfaces>>=
module subroutine tree_assign (tree1, tree2)
type(tree_t), intent(inout) :: tree1
type(tree_t), intent(in) :: tree2
end subroutine tree_assign
<<Cascades2: procedures>>=
module subroutine tree_assign (tree1, tree2)
type(tree_t), intent(inout) :: tree1
type(tree_t), intent(in) :: tree2
if (allocated (tree2%bc)) then
allocate (tree1%bc(size(tree2%bc)))
tree1%bc = tree2%bc
end if
if (allocated (tree2%pdg)) then
allocate (tree1%pdg(size(tree2%pdg)))
tree1%pdg = tree2%pdg
end if
if (allocated (tree2%mapping)) then
allocate (tree1%mapping(size(tree2%mapping)))
tree1%mapping = tree2%mapping
end if
tree1%n_entries = tree2%n_entries
tree1%keep = tree2%keep
tree1%empty = tree2%empty
end subroutine tree_assign
@ %def tree_assign
@
\subsection{Add entries to the tree}
The following procedures fill the arrays in [[tree_t]] with entries
resulting from the bincode and mapping assignment.
<<Cascades2: tree: TBP>>=
procedure :: add_entry_from_numbers => tree_add_entry_from_numbers
procedure :: add_entry_from_node => tree_add_entry_from_node
generic :: add_entry => add_entry_from_numbers, add_entry_from_node
@ Here we add a single entry to each of the arrays. This will exclusively
be used for external particles.
<<Cascades2: sub interfaces>>=
module subroutine tree_add_entry_from_numbers (tree, bincode, pdg, mapping)
class(tree_t), intent(inout) :: tree
integer(TC), intent(in) :: bincode
integer, intent(in) :: pdg
integer, intent(in) :: mapping
end subroutine tree_add_entry_from_numbers
<<Cascades2: procedures>>=
module subroutine tree_add_entry_from_numbers (tree, bincode, pdg, mapping)
class(tree_t), intent(inout) :: tree
integer(TC), intent(in) :: bincode
integer, intent(in) :: pdg
integer, intent(in) :: mapping
integer :: pos
if (tree%empty) then
allocate (tree%bc(1))
allocate (tree%pdg(1))
allocate (tree%mapping(1))
pos = tree%n_entries + 1
tree%bc(pos) = bincode
tree%pdg(pos) = pdg
tree%mapping(pos) = mapping
tree%n_entries = pos
tree%empty = .false.
end if
end subroutine tree_add_entry_from_numbers
@ %def tree_add_entry_from_numbers
@ Here we merge two existing subtrees and a single entry (bc, pdg and
mapping).
<<Cascades2: procedures>>=
subroutine tree_merge (tree, tree1, tree2, bc, pdg, mapping)
class(tree_t), intent(inout) :: tree
type(tree_t), intent(in) :: tree1, tree2
integer(TC), intent(in) :: bc
integer, intent(in) :: pdg, mapping
integer :: tree_size
integer :: i1, i2
if (tree%empty) then
i1 = tree1%n_entries
i2 = tree1%n_entries + tree2%n_entries
!! Proof: tree_size > 0 (always)
tree_size = tree1%n_entries + tree2%n_entries + 1
allocate (tree%bc (tree_size))
allocate (tree%pdg (tree_size))
allocate (tree%mapping (tree_size))
if (.not. tree1%empty) then
tree%bc(:i1) = tree1%bc
tree%pdg(:i1) = tree1%pdg
tree%mapping(:i1) = tree1%mapping
end if
if (.not. tree2%empty) then
tree%bc(i1+1:i2) = tree2%bc
tree%pdg(i1+1:i2) = tree2%pdg
tree%mapping(i1+1:i2) = tree2%mapping
end if
tree%bc(tree_size) = bc
tree%pdg(tree_size) = pdg
tree%mapping(tree_size) = mapping
tree%n_entries = tree_size
tree%empty = .false.
end if
end subroutine tree_merge
@ %def tree_merge
@ Here we add entries to a tree for a given [[k_node]], which means that
we first have to determine whether the node is external or internal.
The arrays are sorted after the entries have been added (see below for
details).
<<Cascades2: sub interfaces>>=
module subroutine tree_add_entry_from_node (tree, node)
class(tree_t), intent(inout) :: tree
type(k_node_t), intent(in) :: node
end subroutine tree_add_entry_from_node
<<Cascades2: procedures>>=
module subroutine tree_add_entry_from_node (tree, node)
class(tree_t), intent(inout) :: tree
type(k_node_t), intent(in) :: node
integer :: pdg
if (node%t_line) then
pdg = abs (node%particle%pdg)
else
pdg = node%particle%pdg
end if
if (associated (node%daughter1) .and. &
associated (node%daughter2)) then
call tree_merge (tree, node%daughter1%subtree, &
node%daughter2%subtree, node%bincode, &
node%particle%pdg, node%mapping)
else
call tree_add_entry_from_numbers (tree, node%bincode, &
node%particle%pdg, node%mapping)
end if
call tree%sort ()
end subroutine tree_add_entry_from_node
@ %def tree_add_entry_from_node
@ For a well-defined order of the elements of the arrays in [[tree_t]],
the elements can be sorted. The bincodes (entries of [[bc]]) are
simply ordered by size, the [[pdg]] and [[mapping]] entries go to the
positions of the corresponding [[bc]] values.
<<Cascades2: tree: TBP>>=
procedure :: sort => tree_sort
<<Cascades2: sub interfaces>>=
module subroutine tree_sort (tree)
class(tree_t), intent(inout) :: tree
end subroutine tree_sort
<<Cascades2: procedures>>=
module subroutine tree_sort (tree)
class(tree_t), intent(inout) :: tree
integer(TC), dimension(size(tree%bc)) :: bc_tmp
integer, dimension(size(tree%pdg)) :: pdg_tmp, mapping_tmp
integer, dimension(1) :: pos
integer :: i
bc_tmp = tree%bc
pdg_tmp = tree%pdg
mapping_tmp = tree%mapping
do i = size(tree%bc),1,-1
pos = maxloc (bc_tmp)
tree%bc(i) = bc_tmp (pos(1))
tree%pdg(i) = pdg_tmp (pos(1))
tree%mapping(i) = mapping_tmp (pos(1))
bc_tmp(pos(1)) = 0
end do
end subroutine tree_sort
@ %def tree_sort
@
\subsection{Graph types}
We define an abstract type which will give rise to two different types:
The type [[feyngraph_t]] contains the pure information of the
corresponding Feynman diagram, but also a list of objects of the
[[kingraph]] type which contain the kinematically relevant data for the
mapping calculation as well as the mappings themselves. Every graph
should have an index which is unique. Graphs which are not needed any
more can be disabled by setting the [[keep]] variable to [[false]].
<<Cascades2: types>>=
type, abstract :: graph_t
integer :: index = 0
integer :: n_nodes = 0
logical :: keep = .true.
end type graph_t
@ %def graph_t
@ This is the type representing the Feynman diagrams which are read from
an input file created by O'Mega. It is a tree of nodes, which we call
[[f_nodes]], so that [[feyngraph_t]] contains a pointer to the root of
this tree, and each node can have two daughter nodes. The case of only
one associated daughter should never appear, because in the method of
phase space parametrization which is used here, we combine always two
particle momenta to a third one. The [[feyngraphs]] will be arranged in
a linked list. This is why we have a pointer to the next graph. The
[[kingraphs]] on the other hand are arranged in linked lists which are
attached to the corresponding [[feyngraph]]. In general, a [[feyngraph]]
can give rise to more than one [[kingraph]] because we make a copy
every time a particle can be resonant, so that in the copy we keep
the particle nonresonant.
<<Cascades2: types>>=
type, extends (graph_t) :: feyngraph_t
type(string_t) :: omega_feyngraph_output
type(f_node_t), pointer :: root => null ()
type(feyngraph_t), pointer :: next => null()
type(kingraph_t), pointer :: kin_first => null ()
type(kingraph_t), pointer :: kin_last => null ()
contains
<<Cascades2: feyngraph: TBP>>
end type feyngraph_t
@ %def feyngraph_t
@ A container for a pointer of type [[feyngraph_t]]. This is used to
realize arrays of these pointers.
<<Cascades2: types>>=
type :: feyngraph_ptr_t
type(feyngraph_t), pointer :: graph => null ()
end type feyngraph_ptr_t
@ %def feyngraph_ptr_t
@
The length of a string describing a Feynman diagram which is produced by
O'Mega is fixed by the parameter
<<Cascades2: parameters>>=
integer, parameter :: FEYNGRAPH_LEN=300
@ %def feyngraph_len
<<Cascades2: feyngraph: TBP>>=
procedure :: final => feyngraph_final
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_final (graph)
class(feyngraph_t), intent(inout) :: graph
end subroutine feyngraph_final
<<Cascades2: procedures>>=
module subroutine feyngraph_final (graph)
class(feyngraph_t), intent(inout) :: graph
type(kingraph_t), pointer :: current
graph%root => null ()
graph%kin_last => null ()
do while (associated (graph%kin_first))
current => graph%kin_first
graph%kin_first => graph%kin_first%next
call current%final ()
deallocate (current)
end do
end subroutine feyngraph_final
@ %def feyngraph_final
This is the type of graph which is used to find the phase space channels,
or in other words, each kingraph could correspond to a channel, if it is
not eliminated for kinematical reasons or due to an equivalence. For the
linked list which is attached to the corresponding [[feyngraph]], we
need the [[next]] pointer, whereas [[grove_next]] points to the next
[[kingraph]] within a grove. The information which is relevant for the
specification of a channel is stored in [[tree]]. We use [[grove_prop]]
to sort the [[kingraph]] in a grove in which all [[kingraphs]] are
characterized by the numbers contained in [[grove_prop]]. Later these
groves are further subdevided using the resonance hash. A [[kingraph]]
which is constructed directly from the output of O'Mega, is not
[[inverse]]. In this case the first incoming particle is the root ofthe
tree. In a scattering process, we can also construct a [[kingraph]]
where the root of the tree is the second incoming particle. In this
case the value of [[inverse]] is [[.true.]].
<<Cascades2: types>>=
type, extends (graph_t) :: kingraph_t
type(k_node_t), pointer :: root => null ()
type(kingraph_t), pointer :: next => null()
type(kingraph_t), pointer :: grove_next => null ()
type(tree_t) :: tree
type(grove_prop_t) :: grove_prop
logical :: inverse = .false.
integer :: prc_component = 0
contains
<<Cascades2: kingraph: TBP>>
end type kingraph_t
@ %def kingraph_t
@ Another container for a pointer to emulate arrays of pointers:
<<Cascades2: types>>=
type :: kingraph_ptr_t
type(kingraph_t), pointer :: graph => null ()
end type kingraph_ptr_t
@ %def kingraph_ptr_t
@
<<Cascades2: kingraph: TBP>>=
procedure :: final => kingraph_final
<<Cascades2: sub interfaces>>=
module subroutine kingraph_final (graph)
class(kingraph_t), intent(inout) :: graph
end subroutine kingraph_final
<<Cascades2: procedures>>=
module subroutine kingraph_final (graph)
class(kingraph_t), intent(inout) :: graph
graph%root => null ()
graph%next => null ()
graph%grove_next => null ()
call graph%tree%final ()
end subroutine kingraph_final
@ %def kingraph_final
@
\subsection{The node types}
We define an abstract type containing variables which are needed for
[[f_node_t]] as well as [[k_node_t]]. We say that a node is on the
t-line if it lies between the two nodes which correspond to the two
incoming particles. [[incoming]] and [[tline]] are used only for
scattering processes and remain [[.false.]] in decay processes. The
variable [[n_subtree_nodes]] holds the number of nodes (including the
node itself) of the subtree of which the node is the root.
<<Cascades2: types>>=
type, abstract :: node_t
type(part_prop_t), pointer :: particle => null ()
logical :: incoming = .false.
logical :: t_line = .false.
integer :: index = 0
logical :: keep = .true.
integer :: n_subtree_nodes = 1
end type node_t
@ %def node_t
@ We use two different list types for the different kinds of nodes. We
therefore start with an abstract type:
<<Cascades2: types>>=
type, abstract :: list_t
integer :: n_entries = 0
end type list_t
@ %def list_t
@ Since the contents of the lists are different, we introduce two
different entry types. Since the trees of nodes use pointers, the nodes
should only be allocated by a type-bound procedure of the corresponding
list type, such that we can keep track of all nodes, eventually reuse
and in the end deallocate nodes correctly, without forgetting any nodes.
Here is the type for the [[k_nodes]]. The list is a linked list. We want
to reuse (recycle) the [[k_nodes]] which are neither [[incoming]] nore
[[t_line]].
<<Cascades2: types>>=
type :: k_node_entry_t
type(k_node_t), pointer :: node => null ()
type(k_node_entry_t), pointer :: next => null ()
logical :: recycle = .false.
contains
<<Cascades2: k node entry: TBP>>
end type k_node_entry_t
@ %def k_node_entry_t
<<Cascades2: k node entry: TBP>>=
procedure :: final => k_node_entry_final
<<Cascades2: sub interfaces>>=
module subroutine k_node_entry_final (entry)
class(k_node_entry_t), intent(inout) :: entry
end subroutine k_node_entry_final
<<Cascades2: procedures>>=
module subroutine k_node_entry_final (entry)
class(k_node_entry_t), intent(inout) :: entry
if (associated (entry%node)) then
call entry%node%final
deallocate (entry%node)
end if
entry%next => null ()
end subroutine k_node_entry_final
@ %def k_node_entry_final
<<Cascades2: k node entry: TBP>>=
procedure :: write => k_node_entry_write
<<Cascades2: sub interfaces>>=
module subroutine k_node_entry_write (k_node_entry, u)
class(k_node_entry_t), intent(in) :: k_node_entry
integer, intent(in) :: u
end subroutine k_node_entry_write
<<Cascades2: procedures>>=
module subroutine k_node_entry_write (k_node_entry, u)
class(k_node_entry_t), intent(in) :: k_node_entry
integer, intent(in) :: u
end subroutine k_node_entry_write
@ %def k_node_entry_write
@ Here is the list type for [[k_nodes]]. A [[k_node_list]] can be
declared to be an observer. In this case it does not create any nodes by
itself, but the entries set their pointers to existing nodes. In this
way we can use the list structure and the type bound procedures for
existing nodes.
<<Cascades2: types>>=
type, extends (list_t) :: k_node_list_t
type(k_node_entry_t), pointer :: first => null ()
type(k_node_entry_t), pointer :: last => null ()
integer :: n_recycle
logical :: observer = .false.
contains
<<Cascades2: k node list: TBP>>
end type k_node_list_t
@ %def k_node_list_t
<<Cascades2: k node list: TBP>>=
procedure :: final => k_node_list_final
<<Cascades2: sub interfaces>>=
module subroutine k_node_list_final (list)
class(k_node_list_t), intent(inout) :: list
end subroutine k_node_list_final
<<Cascades2: procedures>>=
module subroutine k_node_list_final (list)
class(k_node_list_t), intent(inout) :: list
type(k_node_entry_t), pointer :: current
do while (associated (list%first))
current => list%first
list%first => list%first%next
if (list%observer) current%node => null ()
call current%final ()
deallocate (current)
end do
end subroutine k_node_list_final
@ %def k_node_list_final
@ The [[f_node_t]] type contains the [[particle_label]] variable which is
extracted from the input file. It consists not only of the particle
name, but also of some numbers in brackets. These numbers indicate which
external particles are part of the subtree of this node. The [[f_node]]
contains also a list of [[k_nodes]]. Therefore, if the nodes are not
[[incoming]] or [[t_line]], the mapping calculations for these
[[k_nodes]] which can appear in several [[kingraphs]] have to be
performed only once.
<<Cascades2: types>>=
type, extends (node_t) :: f_node_t
type(f_node_t), pointer :: daughter1 => null ()
type(f_node_t), pointer :: daughter2 => null ()
character(len=LABEL_LEN) :: particle_label
type(k_node_list_t) :: k_node_list
contains
<<Cascades2: f node: TBP>>
end type f_node_t
@ %def f_node_t
@ The finalizer nullifies the daughter pointers, since they are
deallocated, like the [[f_node]] itself, with the finalizer of the
[[f_node_list]].
<<Cascades2: f node: TBP>>=
procedure :: final => f_node_final
<<Cascades2: sub interfaces>>=
recursive module subroutine f_node_final (node)
class(f_node_t), intent(inout) :: node
end subroutine f_node_final
<<Cascades2: procedures>>=
recursive module subroutine f_node_final (node)
class(f_node_t), intent(inout) :: node
call node%k_node_list%final ()
node%daughter1 => null ()
node%daughter2 => null ()
end subroutine f_node_final
@ %def f_node_final
@ Finaliser for [[f_node_entry]].
<<Cascades2: f node entry: TBP>>=
procedure :: final => f_node_entry_final
<<Cascades2: sub interfaces>>=
module subroutine f_node_entry_final (entry)
class(f_node_entry_t), intent(inout) :: entry
end subroutine f_node_entry_final
<<Cascades2: procedures>>=
module subroutine f_node_entry_final (entry)
class(f_node_entry_t), intent(inout) :: entry
if (associated (entry%node)) then
call entry%node%final ()
deallocate (entry%node)
end if
entry%next => null ()
end subroutine f_node_entry_final
@ %def f_node_entry_final
@ Set index if not yet done, i.e. if it is zero.
<<Cascades2: f node: TBP>>=
procedure :: set_index => f_node_set_index
<<Cascades2: sub interfaces>>=
module subroutine f_node_set_index (f_node)
class(f_node_t), intent(inout) :: f_node
end subroutine f_node_set_index
<<Cascades2: procedures>>=
module subroutine f_node_set_index (f_node)
class(f_node_t), intent(inout) :: f_node
integer, save :: counter = 0
if (f_node%index == 0) then
counter = counter + 1
f_node%index = counter
end if
end subroutine f_node_set_index
@ %def f_node_set_index
@
Type for the nodes of the tree (lines of the Feynman diagrams). We
also need a type containing a pointer to a node, which is needed for
creating arrays of pointers. This will be used for scattering
processes where we can take either the first or the second particle to
be the root of the tree. Since we need both cases for the calculations
and O'Mega only gives us one of these, we have to perform a
transformation of the graph in which some nodes (on the line which we
hereafter call t-line) need to know their mother and sister nodes,
which become their daughters within this transformation.
<<Cascades2: types>>=
type :: f_node_ptr_t
type(f_node_t), pointer :: node => null ()
contains
<<Cascades2: f node ptr: TBP>>
end type f_node_ptr_t
@ %def f_node_ptr_t
<<Cascades2: f node ptr: TBP>>=
procedure :: final => f_node_ptr_final
<<Cascades2: sub interfaces>>=
module subroutine f_node_ptr_final (f_node_ptr)
class(f_node_ptr_t), intent(inout) :: f_node_ptr
end subroutine f_node_ptr_final
<<Cascades2: procedures>>=
module subroutine f_node_ptr_final (f_node_ptr)
class(f_node_ptr_t), intent(inout) :: f_node_ptr
f_node_ptr%node => null ()
end subroutine f_node_ptr_final
@ %def f_node_ptr_final
<<Cascades2: interfaces>>=
interface assignment (=)
module procedure f_node_ptr_assign
end interface assignment (=)
<<Cascades2: sub interfaces>>=
module subroutine f_node_ptr_assign (ptr1, ptr2)
type(f_node_ptr_t), intent(out) :: ptr1
type(f_node_ptr_t), intent(in) :: ptr2
end subroutine f_node_ptr_assign
<<Cascades2: procedures>>=
module subroutine f_node_ptr_assign (ptr1, ptr2)
type(f_node_ptr_t), intent(out) :: ptr1
type(f_node_ptr_t), intent(in) :: ptr2
ptr1%node => ptr2%node
end subroutine f_node_ptr_assign
@ %def f_node_ptr_assign
@
<<Cascades2: types>>=
type :: k_node_ptr_t
type(k_node_t), pointer :: node => null ()
end type k_node_ptr_t
@ %def k_node_ptr_t
@
<<Cascades2: types>>=
type, extends (node_t) :: k_node_t
type(k_node_t), pointer :: daughter1 => null ()
type(k_node_t), pointer :: daughter2 => null ()
type(k_node_t), pointer :: inverse_daughter1 => null ()
type(k_node_t), pointer :: inverse_daughter2 => null ()
type(f_node_t), pointer :: f_node => null ()
type(tree_t) :: subtree
real (default) :: ext_mass_sum = 0.
real (default) :: effective_mass = 0.
logical :: resonant = .false.
logical :: on_shell = .false.
logical :: log_enhanced = .false.
integer :: mapping = NO_MAPPING
integer(TC) :: bincode = 0
logical :: mapping_assigned = .false.
logical :: is_nonresonant_copy = .false.
logical :: subtree_checked = .false.
integer :: n_off_shell = 0
integer :: n_log_enhanced = 0
integer :: n_resonances = 0
integer :: multiplicity = 0
integer :: n_t_channel = 0
integer :: f_node_index = 0
contains
<<Cascades2: k node: TBP>>
end type k_node_t
@ %def k_node_t
@
Subroutine for [[k_node]] assignment.
<<Cascades2: interfaces>>=
interface assignment (=)
module procedure k_node_assign
end interface assignment (=)
<<Cascades2: sub interfaces>>=
module subroutine k_node_assign (k_node1, k_node2)
type(k_node_t), intent(inout) :: k_node1
type(k_node_t), intent(in) :: k_node2
end subroutine k_node_assign
<<Cascades2: procedures>>=
module subroutine k_node_assign (k_node1, k_node2)
type(k_node_t), intent(inout) :: k_node1
type(k_node_t), intent(in) :: k_node2
k_node1%f_node => k_node2%f_node
k_node1%particle => k_node2%particle
k_node1%incoming = k_node2%incoming
k_node1%t_line = k_node2%t_line
k_node1%keep = k_node2%keep
k_node1%n_subtree_nodes = k_node2%n_subtree_nodes
k_node1%ext_mass_sum = k_node2%ext_mass_sum
k_node1%effective_mass = k_node2%effective_mass
k_node1%resonant = k_node2%resonant
k_node1%on_shell = k_node2%on_shell
k_node1%log_enhanced = k_node2%log_enhanced
k_node1%mapping = k_node2%mapping
k_node1%bincode = k_node2%bincode
k_node1%mapping_assigned = k_node2%mapping_assigned
k_node1%is_nonresonant_copy = k_node2%is_nonresonant_copy
k_node1%n_off_shell = k_node2%n_off_shell
k_node1%n_log_enhanced = k_node2%n_log_enhanced
k_node1%n_resonances = k_node2%n_resonances
k_node1%multiplicity = k_node2%multiplicity
k_node1%n_t_channel = k_node2%n_t_channel
k_node1%f_node_index = k_node2%f_node_index
end subroutine k_node_assign
@ %def k_node_assign
@ The finalizer of [[k_node_t]] nullifies all pointers to nodes, since the
deallocation of these nodes takes place in the finalizer of the list by which
they were created.
<<Cascades2: k node: TBP>>=
procedure :: final => k_node_final
<<Cascades2: sub interfaces>>=
recursive module subroutine k_node_final (k_node)
class(k_node_t), intent(inout) :: k_node
end subroutine k_node_final
<<Cascades2: procedures>>=
recursive module subroutine k_node_final (k_node)
class(k_node_t), intent(inout) :: k_node
k_node%daughter1 => null ()
k_node%daughter2 => null ()
k_node%inverse_daughter1 => null ()
k_node%inverse_daughter2 => null ()
k_node%f_node => null ()
end subroutine k_node_final
@ %def k_node_final
@ Set an index to a [[k_node]], if not yet done, i.e. if it is zero. The
indices are simply positive integer numbers starting from 1.
<<Cascades2: k node: TBP>>=
procedure :: set_index => k_node_set_index
<<Cascades2: sub interfaces>>=
module subroutine k_node_set_index (k_node)
class(k_node_t), intent(inout) :: k_node
end subroutine k_node_set_index
<<Cascades2: procedures>>=
module subroutine k_node_set_index (k_node)
class(k_node_t), intent(inout) :: k_node
integer, save :: counter = 0
if (k_node%index == 0) then
counter = counter + 1
k_node%index = counter
end if
end subroutine k_node_set_index
@ %def k_node_set_index
@ The process type (decay or scattering) is given by an integer which is
equal to the number of incoming particles.
<<Cascades2: public>>=
public :: DECAY, SCATTERING
<<Cascades2: parameters>>=
integer, parameter :: DECAY=1, SCATTERING=2
@ %def decay scattering
@ The entries of the [[f_node_list]] contain the substring of the input
file from which the node's subtree will be constructed (or a modified
string containing placeholders for substrings). We use the
length of this string for fast comparison to find the nodes in the
[[f_node_list]] which we want to reuse.
<<Cascades2: types>>=
type :: f_node_entry_t
character(len=FEYNGRAPH_LEN) :: subtree_string
integer :: string_len = 0
type(f_node_t), pointer :: node => null ()
type(f_node_entry_t), pointer :: next => null ()
integer :: subtree_size = 0
contains
<<Cascades2: f node entry: TBP>>
end type f_node_entry_t
@ %def f_node_entry_t
@ A write method for [[f_node_entry]].
<<Cascades2: f node entry: TBP>>=
procedure :: write => f_node_entry_write
<<Cascades2: sub interfaces>>=
module subroutine f_node_entry_write (f_node_entry, u)
class(f_node_entry_t), intent(in) :: f_node_entry
integer, intent(in) :: u
end subroutine f_node_entry_write
<<Cascades2: procedures>>=
module subroutine f_node_entry_write (f_node_entry, u)
class(f_node_entry_t), intent(in) :: f_node_entry
integer, intent(in) :: u
write (unit=u, fmt='(A)') trim(f_node_entry%subtree_string)
end subroutine f_node_entry_write
@ %def f_node_entry_write
<<Cascades2: interfaces>>=
interface assignment (=)
module procedure f_node_entry_assign
end interface assignment (=)
<<Cascades2: sub interfaces>>=
module subroutine f_node_entry_assign (entry1, entry2)
type(f_node_entry_t), intent(out) :: entry1
type(f_node_entry_t), intent(in) :: entry2
end subroutine f_node_entry_assign
<<Cascades2: procedures>>=
module subroutine f_node_entry_assign (entry1, entry2)
type(f_node_entry_t), intent(out) :: entry1
type(f_node_entry_t), intent(in) :: entry2
entry1%node => entry2%node
entry1%subtree_string = entry2%subtree_string
entry1%string_len = entry2%string_len
entry1%subtree_size = entry2%subtree_size
end subroutine f_node_entry_assign
@ %def f_node_entry_assign
@ This is the list type for [[f_nodes]]. The variable [[max_tree_size]]
is the number of nodes which appear in a complete graph.
<<Cascades2: types>>=
type, extends (list_t) :: f_node_list_t
type(f_node_entry_t), pointer :: first => null ()
type(f_node_entry_t), pointer :: last => null ()
type(k_node_list_t), pointer :: k_node_list => null ()
integer :: max_tree_size = 0
contains
<<Cascades2: f node list: TBP>>
end type f_node_list_t
@ %def f_node_list_t
@ Add an entry to the [[f_node_list]]. If the node might be reused, we check first
using the [[subtree_string]] if there is already a node in the list which
is the root of exactly the same subtree. Otherwise we add an entry to the
list and allocate the node. In both cases we return a pointer to the node
which allows to access the node.
<<Cascades2: f node list: TBP>>=
procedure :: add_entry => f_node_list_add_entry
<<Cascades2: sub interfaces>>=
module subroutine f_node_list_add_entry (list, subtree_string, &
ptr_to_node, recycle, subtree_size)
class(f_node_list_t), intent(inout) :: list
character(len=*), intent(in) :: subtree_string
type(f_node_t), pointer, intent(out) :: ptr_to_node
logical, intent(in) :: recycle
integer, intent(in), optional :: subtree_size
end subroutine f_node_list_add_entry
<<Cascades2: procedures>>=
module subroutine f_node_list_add_entry (list, subtree_string, &
ptr_to_node, recycle, subtree_size)
class(f_node_list_t), intent(inout) :: list
character(len=*), intent(in) :: subtree_string
type(f_node_t), pointer, intent(out) :: ptr_to_node
logical, intent(in) :: recycle
integer, intent(in), optional :: subtree_size
type(f_node_entry_t), pointer :: current
type(f_node_entry_t), pointer :: second
integer :: subtree_len
ptr_to_node => null ()
if (recycle) then
subtree_len = len_trim (subtree_string)
current => list%first
do while (associated (current))
if (present (subtree_size)) then
if (current%subtree_size /= subtree_size) exit
end if
if (current%string_len == subtree_len) then
if (trim (current%subtree_string) == trim (subtree_string)) then
ptr_to_node => current%node
exit
end if
end if
current => current%next
end do
end if
if (.not. associated (ptr_to_node)) then
if (list%n_entries == 0) then
allocate (list%first)
list%last => list%first
else
second => list%first
list%first => null ()
allocate (list%first)
list%first%next => second
end if
list%n_entries = list%n_entries + 1
list%first%subtree_string = trim(subtree_string)
list%first%string_len = subtree_len
if (present (subtree_size)) list%first%subtree_size = subtree_size
allocate (list%first%node)
call list%first%node%set_index ()
ptr_to_node => list%first%node
end if
end subroutine f_node_list_add_entry
@ %def f_node_list_add_entry
@ A write method for debugging.
<<Cascades2: f node list: TBP>>=
procedure :: write => f_node_list_write
<<Cascades2: sub interfaces>>=
module subroutine f_node_list_write (f_node_list, u)
class(f_node_list_t), intent(in) :: f_node_list
integer, intent(in) :: u
end subroutine f_node_list_write
<<Cascades2: procedures>>=
module subroutine f_node_list_write (f_node_list, u)
class(f_node_list_t), intent(in) :: f_node_list
integer, intent(in) :: u
type(f_node_entry_t), pointer :: current
integer :: pos = 0
current => f_node_list%first
do while (associated (current))
pos = pos + 1
write (unit=u, fmt='(A,I10)') 'entry #: ', pos
call current%write (u)
write (unit=u, fmt=*)
current => current%next
end do
end subroutine f_node_list_write
@ %def f_node_list_write
<<Cascades2: interfaces>>=
interface assignment (=)
module procedure k_node_entry_assign
end interface assignment (=)
<<Cascades2: sub interfaces>>=
module subroutine k_node_entry_assign (entry1, entry2)
type(k_node_entry_t), intent(out) :: entry1
type(k_node_entry_t), intent(in) :: entry2
end subroutine k_node_entry_assign
<<Cascades2: procedures>>=
module subroutine k_node_entry_assign (entry1, entry2)
type(k_node_entry_t), intent(out) :: entry1
type(k_node_entry_t), intent(in) :: entry2
entry1%node => entry2%node
entry1%recycle = entry2%recycle
end subroutine k_node_entry_assign
@ %def k_node_entry_assign
@ Add an entry to the [[k_node_list]]. We have to specify if the
node can be reused. The check for existing reusable nodes happens with
[[k_node_list_get_nodes]] (see below).
<<Cascades2: k node list: TBP>>=
procedure :: add_entry => k_node_list_add_entry
<<Cascades2: sub interfaces>>=
recursive module subroutine k_node_list_add_entry &
(list, ptr_to_node, recycle)
class(k_node_list_t), intent(inout) :: list
type(k_node_t), pointer, intent(out) :: ptr_to_node
logical, intent(in) :: recycle
end subroutine k_node_list_add_entry
<<Cascades2: procedures>>=
recursive module subroutine k_node_list_add_entry &
(list, ptr_to_node, recycle)
class(k_node_list_t), intent(inout) :: list
type(k_node_t), pointer, intent(out) :: ptr_to_node
logical, intent(in) :: recycle
if (list%n_entries == 0) then
allocate (list%first)
list%last => list%first
else
allocate (list%last%next)
list%last => list%last%next
end if
list%n_entries = list%n_entries + 1
list%last%recycle = recycle
allocate (list%last%node)
call list%last%node%set_index ()
ptr_to_node => list%last%node
end subroutine k_node_list_add_entry
@ %def k_node_list_add_entry
@ We need a similar subroutine for adding only a pointer to a list. This
is needed for a [[k_node_list]] which is only an observer, i.e. it does
not create any nodes by itself.
<<Cascades2: k node list: TBP>>=
procedure :: add_pointer => k_node_list_add_pointer
<<Cascades2: sub interfaces>>=
module subroutine k_node_list_add_pointer (list, ptr_to_node, recycle)
class(k_node_list_t), intent(inout) :: list
type(k_node_t), pointer, intent(in) :: ptr_to_node
logical, optional, intent(in) :: recycle
end subroutine k_node_list_add_pointer
<<Cascades2: procedures>>=
module subroutine k_node_list_add_pointer (list, ptr_to_node, recycle)
class(k_node_list_t), intent(inout) :: list
type(k_node_t), pointer, intent(in) :: ptr_to_node
logical, optional, intent(in) :: recycle
logical :: rec
if (present (recycle)) then
rec = recycle
else
rec = .false.
end if
if (list%n_entries == 0) then
allocate (list%first)
list%last => list%first
else
allocate (list%last%next)
list%last => list%last%next
end if
list%n_entries = list%n_entries + 1
list%last%recycle = rec
list%last%node => ptr_to_node
end subroutine k_node_list_add_pointer
@ %def k_node_list_add_pointer
@ The [[k_node_list]] can also be used to collect [[k_nodes]] which belong to
different [[f_nodes]] in order to compare these. This is done only for nodes
which have the same number of subtree nodes. We compare all nodes of the
list with each other (as long as the node is not deactivated, i.e. if
the [[keep]] variable is set to [[.true.]]) using the subroutine
[[subtree_select]]. If it turns out that two nodes are equivalent, we
keep only one of them. The term equivalent in this module refers to trees
or subtrees which differ in the pdg codes at positions where
the trivial mapping is used ([[NO_MAPPING]] or [[NON_RESONANT]]) so that
the mass of the particle does not matter. Depending on the available
couplings, two equivalent subtrees could eventually lead to the same phase
space channels, which is why only one of them is kept.
<<Cascades2: k node list: TBP>>=
procedure :: check_subtree_equivalences => &
k_node_list_check_subtree_equivalences
<<Cascades2: sub interfaces>>=
module subroutine k_node_list_check_subtree_equivalences (list, model)
class(k_node_list_t), intent(inout) :: list
type(model_data_t), intent(in) :: model
end subroutine k_node_list_check_subtree_equivalences
<<Cascades2: procedures>>=
module subroutine k_node_list_check_subtree_equivalences (list, model)
class(k_node_list_t), intent(inout) :: list
type(model_data_t), intent(in) :: model
type(k_node_ptr_t), dimension (:), allocatable :: set
type(k_node_entry_t), pointer :: current
integer :: pos
integer :: i,j
if (list%n_entries == 0) return
allocate (set (list%n_entries))
current => list%first
pos = 0
do while (associated (current))
pos = pos + 1
set(pos)%node => current%node
current => current%next
end do
do i=1, list%n_entries
if (set(i)%node%keep) then
do j=i+1, list%n_entries
if (set(j)%node%keep) then
if (set(i)%node%bincode == set(j)%node%bincode) then
call subtree_select (set(i)%node%subtree,set(j)%node%subtree, model)
if (.not. set(i)%node%subtree%keep) then
set(i)%node%keep = .false.
exit
else if (.not. set(j)%node%subtree%keep) then
set(j)%node%keep = .false.
end if
end if
end if
end do
end if
end do
deallocate (set)
end subroutine k_node_list_check_subtree_equivalences
@ %def k_node_list_check_subtree_equivalences
@ This subroutine is used to obtain all [[k_nodes]] of a [[k_node_list]]
which can be recycled and are not disabled for some reason. We pass an
allocatable array of the type [[k_node_ptr_t]] which will be allocated
if there are any such nodes in the list and the pointers will be
associated with these nodes.
<<Cascades2: k node list: TBP>>=
procedure :: get_nodes => k_node_list_get_nodes
<<Cascades2: sub interfaces>>=
module subroutine k_node_list_get_nodes (list, nodes)
class(k_node_list_t), intent(inout) :: list
type(k_node_ptr_t), dimension(:), allocatable, intent(out) :: nodes
end subroutine k_node_list_get_nodes
<<Cascades2: procedures>>=
module subroutine k_node_list_get_nodes (list, nodes)
class(k_node_list_t), intent(inout) :: list
type(k_node_ptr_t), dimension(:), allocatable, intent(out) :: nodes
integer :: n_nodes
integer :: pos
type(k_node_entry_t), pointer :: current, garbage
n_nodes = 0
current => list%first
do while (associated (current))
if (current%recycle .and. current%node%keep) n_nodes = n_nodes + 1
current => current%next
end do
if (n_nodes /= 0) then
pos = 1
allocate (nodes (n_nodes))
do while (associated (list%first) .and. .not. list%first%node%keep)
garbage => list%first
list%first => list%first%next
call garbage%final ()
deallocate (garbage)
end do
current => list%first
do while (associated (current))
do while (associated (current%next))
if (.not. current%next%node%keep) then
garbage => current%next
current%next => current%next%next
call garbage%final
deallocate (garbage)
else
exit
end if
end do
if (current%recycle .and. current%node%keep) then
nodes(pos)%node => current%node
pos = pos + 1
end if
current => current%next
end do
end if
end subroutine k_node_list_get_nodes
@ %def k_node_list_get_nodes
Gfortran 7/8/9 bug, has to remain in the main module:
<<Cascades2: f node list: TBP>>=
procedure :: final => f_node_list_final
<<Cascades2: main procedures>>=
subroutine f_node_list_final (list)
class(f_node_list_t) :: list
type(f_node_entry_t), pointer :: current
list%k_node_list => null ()
do while (associated (list%first))
current => list%first
list%first => list%first%next
call current%final ()
deallocate (current)
end do
end subroutine f_node_list_final
@ %def f_node_list_final
@
\subsection{The grove list}
First a type is introduced in order to speed up the comparison of kingraphs
with the purpose to quickly find the graphs that might be equivalent.
This is done solely on the basis of a number (which is given
by the value of [[depth]] in [[compare_tree_t]]) of bincodes, which are
the highest ones that do not belong to external particles.
The highest such value determines the index of the element in the [[entry]]
array of the [[compare_tree]]. The next lower such value determines
the index of the element in the [[entry]] array of this [[entry]], and so
on and so forth. This results in a tree structure where the number of
levels is given by [[depth]] and should not be too large for reasons of
memory.
This is the entry type.
<<Cascades2: types>>=
type :: compare_tree_entry_t
type(compare_tree_entry_t), dimension(:), pointer :: entry => null ()
type(kingraph_ptr_t), dimension(:), allocatable :: graph_entry
contains
<<Cascades2: compare tree entry: TBP>>
end type compare_tree_entry_t
@ %def compare_tree_entry_t
@ This is the tree type.
<<Cascades2: types>>=
type :: compare_tree_t
integer :: depth = 3
type(compare_tree_entry_t), dimension(:), pointer :: entry => null ()
contains
<<Cascades2: compare tree: TBP>>
end type compare_tree_t
@ %def compare_tree_t
@ Finalizers for both types. The one for the entry type has to be recursive.
<<Cascades2: compare tree: TBP>>=
procedure :: final => compare_tree_final
<<Cascades2: sub interfaces>>=
module subroutine compare_tree_final (ctree)
class(compare_tree_t), intent(inout) :: ctree
end subroutine compare_tree_final
<<Cascades2: procedures>>=
module subroutine compare_tree_final (ctree)
class(compare_tree_t), intent(inout) :: ctree
integer :: i
if (associated (ctree%entry)) then
do i=1, size (ctree%entry)
call ctree%entry(i)%final ()
deallocate (ctree%entry)
end do
end if
end subroutine compare_tree_final
@ %def compare_tree_final
<<Cascades2: compare tree entry: TBP>>=
procedure :: final => compare_tree_entry_final
<<Cascades2: sub interfaces>>=
recursive module subroutine compare_tree_entry_final (ct_entry)
class(compare_tree_entry_t), intent(inout) :: ct_entry
end subroutine compare_tree_entry_final
<<Cascades2: procedures>>=
recursive module subroutine compare_tree_entry_final (ct_entry)
class(compare_tree_entry_t), intent(inout) :: ct_entry
integer :: i
if (associated (ct_entry%entry)) then
do i=1, size (ct_entry%entry)
call ct_entry%entry(i)%final ()
end do
deallocate (ct_entry%entry)
else
deallocate (ct_entry%graph_entry)
end if
end subroutine compare_tree_entry_final
@ %def compare_tree_entry_final
@ Check the presence of a graph which is considered as equivalent and
select between the two. If there is no such graph, the current one
is added to the list. First the entry has to be found:
<<Cascades2: compare tree: TBP>>=
procedure :: check_kingraph => compare_tree_check_kingraph
<<Cascades2: sub interfaces>>=
module subroutine compare_tree_check_kingraph &
(ctree, kingraph, model, preliminary)
class(compare_tree_t), intent(inout) :: ctree
type(kingraph_t), intent(inout), pointer :: kingraph
type(model_data_t), intent(in) :: model
logical, intent(in) :: preliminary
end subroutine compare_tree_check_kingraph
<<Cascades2: procedures>>=
module subroutine compare_tree_check_kingraph &
(ctree, kingraph, model, preliminary)
class(compare_tree_t), intent(inout) :: ctree
type(kingraph_t), intent(inout), pointer :: kingraph
type(model_data_t), intent(in) :: model
logical, intent(in) :: preliminary
integer :: i
integer :: pos
integer(TC) :: sz
integer(TC), dimension(:), allocatable :: identifier
if (.not. associated (ctree%entry)) then
sz = 0_TC
do i = size(kingraph%tree%bc), 1, -1
sz = ior (sz, kingraph%tree%bc(i))
end do
if (sz > 0) then
allocate (ctree%entry (sz))
else
call msg_bug ("Compare tree could not be created")
end if
end if
allocate (identifier (ctree%depth))
pos = 0
do i = size(kingraph%tree%bc), 1, -1
if (popcnt (kingraph%tree%bc(i)) /= 1) then
pos = pos + 1
identifier(pos) = kingraph%tree%bc(i)
if (pos == ctree%depth) exit
end if
end do
if (size (identifier) > 1) then
call ctree%entry(identifier(1))%check_kingraph (kingraph, model, &
preliminary, identifier(1), identifier(2:))
else if (size (identifier) == 1) then
call ctree%entry(identifier(1))%check_kingraph &
(kingraph, model, preliminary)
end if
deallocate (identifier)
end subroutine compare_tree_check_kingraph
@ %def compare_tree_check_kingraph
@ Then the graphs of the entry are checked.
<<Cascades2: compare tree entry: TBP>>=
procedure :: check_kingraph => compare_tree_entry_check_kingraph
<<Cascades2: sub interfaces>>=
recursive module subroutine compare_tree_entry_check_kingraph (ct_entry, &
kingraph, model, preliminary, subtree_size, identifier)
class(compare_tree_entry_t), intent(inout) :: ct_entry
type(kingraph_t), pointer, intent(inout) :: kingraph
type(model_data_t), intent(in) :: model
logical, intent(in) :: preliminary
integer, intent(in), optional :: subtree_size
integer, dimension (:), intent(in), optional :: identifier
end subroutine compare_tree_entry_check_kingraph
<<Cascades2: procedures>>=
recursive module subroutine compare_tree_entry_check_kingraph (ct_entry, &
kingraph, model, preliminary, subtree_size, identifier)
class(compare_tree_entry_t), intent(inout) :: ct_entry
type(kingraph_t), pointer, intent(inout) :: kingraph
type(model_data_t), intent(in) :: model
logical, intent(in) :: preliminary
integer, intent(in), optional :: subtree_size
integer, dimension (:), intent(in), optional :: identifier
if (present (identifier)) then
if (.not. associated (ct_entry%entry)) &
allocate (ct_entry%entry(subtree_size))
if (size (identifier) > 1) then
call ct_entry%entry(identifier(1))%check_kingraph (kingraph, &
model, preliminary, identifier(1), identifier(2:))
else if (size (identifier) == 1) then
call ct_entry%entry(identifier(1))%check_kingraph (kingraph, &
model, preliminary)
end if
else
if (allocated (ct_entry%graph_entry)) then
call perform_check
else
allocate (ct_entry%graph_entry(1))
ct_entry%graph_entry(1)%graph => kingraph
end if
end if
contains
subroutine perform_check
integer :: i
logical :: rebuild
rebuild = .true.
do i=1, size(ct_entry%graph_entry)
if (ct_entry%graph_entry(i)%graph%keep) then
if (preliminary .or. &
ct_entry%graph_entry(i)%graph%prc_component /= &
kingraph%prc_component) then
call kingraph_select (ct_entry%graph_entry(i)%graph, &
kingraph, model, preliminary)
if (.not. kingraph%keep) then
return
else if (rebuild .and. .not. &
ct_entry%graph_entry(i)%graph%keep) then
ct_entry%graph_entry(i)%graph => kingraph
rebuild = .false.
end if
end if
end if
end do
if (rebuild) call rebuild_graph_entry
end subroutine perform_check
subroutine rebuild_graph_entry
type(kingraph_ptr_t), dimension(:), allocatable :: tmp_ptr
integer :: i
integer :: pos
allocate (tmp_ptr(size(ct_entry%graph_entry)+1))
pos = 0
do i=1, size(ct_entry%graph_entry)
pos = pos + 1
tmp_ptr(pos)%graph => ct_entry%graph_entry(i)%graph
end do
pos = pos + 1
tmp_ptr(pos)%graph => kingraph
deallocate (ct_entry%graph_entry)
allocate (ct_entry%graph_entry (pos))
do i=1, pos
ct_entry%graph_entry(i)%graph => tmp_ptr(i)%graph
end do
deallocate (tmp_ptr)
end subroutine rebuild_graph_entry
end subroutine compare_tree_entry_check_kingraph
@ %def compare_tree_entry_check_kingraph
@ The grove to which a completed [[kingraph]] will be added is determined by the
entries of [[grove_prop]]. We use another list type (linked list) to
arrange the groves. Each [[grove]] contains again a linked list of
[[kingraphs]].
<<Cascades2: types>>=
type :: grove_t
type(grove_prop_t) :: grove_prop
type(grove_t), pointer :: next => null ()
type(kingraph_t), pointer :: first => null ()
type(kingraph_t), pointer :: last => null ()
type(compare_tree_t) :: compare_tree
contains
<<Cascades2: grove: TBP>>
end type grove_t
@ %def grove_t
@ Container for a pointer of type [[grove_t]]:
<<Cascades2: types>>=
type :: grove_ptr_t
type(grove_t), pointer :: grove => null ()
end type grove_ptr_t
@ %def grove_ptr_t
<<Cascades2: grove: TBP>>=
procedure :: final => grove_final
<<Cascades2: sub interfaces>>=
module subroutine grove_final (grove)
class(grove_t), intent(inout) :: grove
end subroutine grove_final
<<Cascades2: procedures>>=
module subroutine grove_final (grove)
class(grove_t), intent(inout) :: grove
grove%first => null ()
grove%last => null ()
grove%next => null ()
end subroutine grove_final
@ %def grove_final
@ This is the list type:
<<Cascades2: types>>=
type :: grove_list_t
type(grove_t), pointer :: first => null ()
contains
<<Cascades2: grove list: TBP>>
end type grove_list_t
@ %def grove_list_t
Gfortran 7/8/9 bug, has to remain in the main module:
<<Cascades2: grove list: TBP>>=
procedure :: final => grove_list_final
<<Cascades2: main procedures>>=
subroutine grove_list_final (list)
class(grove_list_t), intent(inout) :: list
class(grove_t), pointer :: current
do while (associated (list%first))
current => list%first
list%first => list%first%next
call current%final ()
deallocate (current)
end do
end subroutine grove_list_final
@ %def grove_list_final
@
\subsection{The feyngraph set}
The fundament of the module is the public type [[feyngraph_set_t]]. It
is not only a linked list of all [[feyngraphs]] but contains an array
of all particle properties ([[particle]]), an [[f_node_list]] and a
pointer of the type [[grove_list_t]], since several [[feyngraph_sets]]
can share a common [[grove_list]]. In addition it keeps the data which
unambiguously specifies the process, as well as the model which
provides information which allows us to choose between equivalent
subtrees or complete [[kingraphs]].
<<Cascades2: public>>=
public :: feyngraph_set_t
<<Cascades2: types>>=
type :: feyngraph_set_t
type(model_data_t), pointer :: model => null ()
type(flavor_t), dimension(:,:), allocatable :: flv
integer :: n_in = 0
integer :: n_out = 0
integer :: process_type = DECAY
type(phs_parameters_t) :: phs_par
logical :: fatal_beam_decay = .true.
type(part_prop_t), dimension (:), pointer :: particle => null ()
type(f_node_list_t) :: f_node_list
type(feyngraph_t), pointer :: first => null ()
type(feyngraph_t), pointer :: last => null ()
integer :: n_graphs = 0
type(grove_list_t), pointer :: grove_list => null ()
logical :: use_dag = .true.
type(dag_t), pointer :: dag => null ()
type(feyngraph_set_t), dimension (:), pointer :: fset => null ()
contains
<<Cascades2: feyngraph set: TBP>>
end type feyngraph_set_t
@ %def feyngraph_set_t
@ This final procedure contains calls to all other necessary final
procedures. Gfortran 7/8/9 bug, has to remain in the main module:
<<Cascades2: feyngraph set: TBP>>=
procedure :: final => feyngraph_set_final
<<Cascades2: main procedures>>=
recursive subroutine feyngraph_set_final (set)
class(feyngraph_set_t), intent(inout) :: set
class(feyngraph_t), pointer :: current
integer :: i
if (associated (set%fset)) then
do i=1, size (set%fset)
call set%fset(i)%final ()
end do
deallocate (set%fset)
else
set%particle => null ()
set%grove_list => null ()
end if
set%model => null ()
if (allocated (set%flv)) deallocate (set%flv)
set%last => null ()
do while (associated (set%first))
current => set%first
set%first => set%first%next
call current%final ()
deallocate (current)
end do
if (associated (set%particle)) then
do i = 1, size (set%particle)
call set%particle(i)%final ()
end do
deallocate (set%particle)
end if
if (associated (set%grove_list)) then
if (debug_on) call msg_debug (D_PHASESPACE, "grove_list: final")
call set%grove_list%final ()
deallocate (set%grove_list)
end if
if (debug_on) call msg_debug (D_PHASESPACE, "f_node_list: final")
call set%f_node_list%final ()
if (associated (set%dag)) then
if (debug_on) call msg_debug (D_PHASESPACE, "dag: final")
if (associated (set%dag)) then
call set%dag%final ()
deallocate (set%dag)
end if
end if
end subroutine feyngraph_set_final
@ %def feyngraph_set_final
@
\subsection{Construct the feyngraph set}
We construct the [[feyngraph_set]] from an input file. Therefore we pass
a unit to [[feyngraph_set_build]]. The parsing subroutines are chosen
depending on the value of [[use_dag]]. In the DAG output, which is the one
that is produced by default, we have to work on a string of one line,
where the lenght of this string becomes larger the more particles are
involved in the process. The other output (which is now only used in a
unit test) contains one Feynman diagram per line and each line starts with an open
parenthesis so that we read the file line per line and create a
[[feyngraph]] for every line. Only after this, nodes are created. In both
decay and scattering processes the diagrams are represented like in a decay
process, i.e. in a scattering process one of the incoming particles appears
as an outgoing particle.
<<Cascades2: feyngraph set: TBP>>=
procedure :: build => feyngraph_set_build
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_build (feyngraph_set, u_in)
class(feyngraph_set_t), intent(inout) :: feyngraph_set
integer, intent(in) :: u_in
end subroutine feyngraph_set_build
<<Cascades2: procedures>>=
module subroutine feyngraph_set_build (feyngraph_set, u_in)
class(feyngraph_set_t), intent(inout) :: feyngraph_set
integer, intent(in) :: u_in
integer :: stat = 0
character(len=FEYNGRAPH_LEN) :: omega_feyngraph_output
type(feyngraph_t), pointer :: current_graph
type(feyngraph_t), pointer :: compare_graph
logical :: present
if (feyngraph_set%use_dag) then
allocate (feyngraph_set%dag)
if (.not. associated (feyngraph_set%first)) then
call feyngraph_set%dag%read_string (u_in, feyngraph_set%flv(:,1))
call feyngraph_set%dag%construct (feyngraph_set)
call feyngraph_set%dag%make_feyngraphs (feyngraph_set)
end if
else
if (.not. associated (feyngraph_set%first)) then
read (unit=u_in, fmt='(A)', iostat=stat, advance='yes') &
omega_feyngraph_output
if (omega_feyngraph_output(1:1) == '(') then
allocate (feyngraph_set%first)
feyngraph_set%first%omega_feyngraph_output = &
trim(omega_feyngraph_output)
feyngraph_set%last => feyngraph_set%first
feyngraph_set%n_graphs = feyngraph_set%n_graphs + 1
else
call msg_fatal ("Invalid input file")
end if
read (unit=u_in, fmt='(A)', iostat=stat, advance='yes') &
omega_feyngraph_output
do while (stat == 0)
if (omega_feyngraph_output(1:1) == '(') then
compare_graph => feyngraph_set%first
present = .false.
do while (associated (compare_graph))
if (len_trim(compare_graph%omega_feyngraph_output) &
== len_trim(omega_feyngraph_output)) then
if (compare_graph%omega_feyngraph_output == &
omega_feyngraph_output) then
present = .true.
exit
end if
end if
compare_graph => compare_graph%next
end do
if (.not. present) then
allocate (feyngraph_set%last%next)
feyngraph_set%last => feyngraph_set%last%next
feyngraph_set%last%omega_feyngraph_output = &
trim(omega_feyngraph_output)
feyngraph_set%n_graphs = feyngraph_set%n_graphs + 1
end if
read (unit=u_in, fmt='(A)', iostat=stat, advance='yes') &
omega_feyngraph_output
else
exit
end if
end do
current_graph => feyngraph_set%first
do while (associated (current_graph))
call feyngraph_construct (feyngraph_set, current_graph)
current_graph => current_graph%next
end do
feyngraph_set%f_node_list%max_tree_size = feyngraph_set%first%n_nodes
end if
end if
end subroutine feyngraph_set_build
@ %def feyngraph_set_build
@ Read the string from the file. The output which is produced by O'Mega
contains the DAG in a factorised form as a long string, distributed over
several lines (in addition, in the case of a scattering process, it
contains a similar string for the same process, but with the other
incoming particle as the root of the tree structure). In general, such a
file can contain many of these strings, belonging to different process
components. Therefore we first have to find the correct position of the
string for the process in question. Therefore we look for a line
containing a pair of colons, in which case the line contains a process
string. Then we check if the process string describes the correct
process, which is done by checking for all the incoming and outgoing
particle names. If the process is correct, the dag output should start
in the following line. As long as we do not find the correct process
string, we continue searching. If we reach the end of the file, we
rewind the unit once, and repeat searching. If the process is still not
found, there must be some sort of error.
<<Cascades2: dag: TBP>>=
procedure :: read_string => dag_read_string
<<Cascades2: sub interfaces>>=
module subroutine dag_read_string (dag, u_in, flv)
class(dag_t), intent(inout) :: dag
integer, intent(in) :: u_in
type(flavor_t), dimension(:), intent(in) :: flv
end subroutine dag_read_string
<<Cascades2: procedures>>=
module subroutine dag_read_string (dag, u_in, flv)
class(dag_t), intent(inout) :: dag
integer, intent(in) :: u_in
type(flavor_t), dimension(:), intent(in) :: flv
character(len=BUFFER_LEN) :: process_string
logical :: process_found
logical :: rewound
!!! Find process string in file
process_found = .false.
rewound = .false.
do while (.not. process_found)
process_string = ""
read (unit=u_in, fmt='(A)') process_string
if (len_trim(process_string) /= 0) then
if (index (process_string, "::") > 0) then
process_found = process_string_match (trim (process_string), flv)
end if
else if (.not. rewound) then
rewind (u_in)
rewound = .true.
else
call msg_bug ("Process string not found in O'Mega input file.")
end if
end do
call fds_file_get_line (u_in, dag%string)
call dag%string%clean ()
if (.not. allocated (dag%string%t) .or. dag%string%char_len == 0) &
call msg_bug ("Process string not found in O'Mega input file.")
end subroutine dag_read_string
@ %def dag_read_string
@ The output of factorized Feynman diagrams which is created by O'Mega
for a given process could in principle be written to a single line in
the file. This can however lead to different problems with different
compilers as soon as such lines become too long. This is the reason why
the line is cut into smaller pieces. This means that a new line starts
after each vertical bar. For this long string the type [[dag_string_t]]
has been introduced. In order to read the file quickly into such a
[[dag_string]] we use another type, [[dag_chain_t]] which is a linked
list of such [[dag_strings]]. This has the advantage that we do not
have to recreate a new [[dag_string]] for every line which has been
read from file. Only in the end of this operation we compress the
list of strings to a single string, removing useless [[dag_tokens]],
such as blanc space tokens. This subroutine reads all lines starting
from the position in the file the unit is connected to, until no
backslash character is found at the end of a line (the backslash
means that the next line also belongs to the current string).
<<Cascades2: parameters>>=
integer, parameter :: BUFFER_LEN = 1000
integer, parameter :: STACK_SIZE = 100
@ %def BUFFER_LEN STACK_SIZE
<<Cascades2: procedures>>=
subroutine fds_file_get_line (u, string)
integer, intent(in) :: u
type(dag_string_t), intent(out) :: string
type(dag_chain_t) :: chain
integer :: string_size, current_len
character(len=BUFFER_LEN) :: buffer
integer :: fragment_len
integer :: stat
current_len = 0
stat = 0
string_size = 0
do while (stat == 0)
read (unit=u, fmt='(A)', iostat=stat) buffer
if (stat /= 0) exit
fragment_len = len_trim (buffer)
if (fragment_len == 0) then
exit
else if (buffer (fragment_len:fragment_len) == BACKSLASH_CHAR) then
fragment_len = fragment_len - 1
end if
call chain%append (buffer(:fragment_len))
if (buffer(fragment_len+1:fragment_len+1) /= BACKSLASH_CHAR) exit
end do
if (associated (chain%first)) then
call chain%compress ()
string = chain%first
call chain%final ()
end if
end subroutine fds_file_get_line
@ %def fds_file_get_line
@ We check, if the process string which has been read from file
corresponds to the process for which we want to extract the Feynman
diagrams.
<<Cascades2: procedures>>=
function process_string_match (string, flv) result (match)
character(len=*), intent(in) :: string
type(flavor_t), dimension(:), intent(in) :: flv
logical :: match
integer :: pos
integer :: occurence
integer :: i
pos = 1
match = .false.
do i=1, size (flv)
occurence = index (string(pos:), char(flv(i)%get_name()))
if (occurence > 0) then
pos = pos + occurence
match = .true.
else
match = .false.
exit
end if
end do
end function process_string_match
@ %def process_string_match
@
\subsection{Particle properties}
This subroutine initializes a model instance with the Standard Model
data. It is only relevant for a unit test.
We do not have to care about the model initialization in this module
because the [[model]] is passed to [[feyngraph_set_generate]] when
it is called.
<<Cascades2: public>>=
public :: init_sm_full_test
<<Cascades2: sub interfaces>>=
module subroutine init_sm_full_test (model)
class(model_data_t), intent(out) :: model
end subroutine init_sm_full_test
<<Cascades2: procedures>>=
module subroutine init_sm_full_test (model)
class(model_data_t), intent(out) :: model
type(field_data_t), pointer :: field
integer, parameter :: n_real = 17
integer, parameter :: n_field = 21
integer, parameter :: n_vtx = 56
integer :: i
call model%init (var_str ("SM_vertex_test"), &
n_real, 0, n_field, n_vtx)
call model%init_par (1, var_str ("mZ"), 91.1882_default)
call model%init_par (2, var_str ("mW"), 80.419_default)
call model%init_par (3, var_str ("mH"), 125._default)
call model%init_par (4, var_str ("me"), 0.000510997_default)
call model%init_par (5, var_str ("mmu"), 0.105658389_default)
call model%init_par (6, var_str ("mtau"), 1.77705_default)
call model%init_par (7, var_str ("ms"), 0.095_default)
call model%init_par (8, var_str ("mc"), 1.2_default)
call model%init_par (9, var_str ("mb"), 4.2_default)
call model%init_par (10, var_str ("mtop"), 173.1_default)
call model%init_par (11, var_str ("wtop"), 1.523_default)
call model%init_par (12, var_str ("wZ"), 2.443_default)
call model%init_par (13, var_str ("wW"), 2.049_default)
call model%init_par (14, var_str ("wH"), 0.004143_default)
call model%init_par (15, var_str ("ee"), 0.3079561542961_default)
call model%init_par (16, var_str ("cw"), 8.819013863636E-01_default)
call model%init_par (17, var_str ("sw"), 4.714339240339E-01_default)
i = 0
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("D_QUARK"), 1)
call field%set (spin_type=2, color_type=3, charge_type=-2, isospin_type=-2)
call field%set (name = [var_str ("d")], anti = [var_str ("dbar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("U_QUARK"), 2)
call field%set (spin_type=2, color_type=3, charge_type=3, isospin_type=2)
call field%set (name = [var_str ("u")], anti = [var_str ("ubar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("S_QUARK"), 3)
call field%set (spin_type=2, color_type=3, charge_type=-2, isospin_type=-2)
call field%set (mass_data=model%get_par_real_ptr (7))
call field%set (name = [var_str ("s")], anti = [var_str ("sbar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("C_QUARK"), 4)
call field%set (spin_type=2, color_type=3, charge_type=3, isospin_type=2)
call field%set (mass_data=model%get_par_real_ptr (8))
call field%set (name = [var_str ("c")], anti = [var_str ("cbar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("B_QUARK"), 5)
call field%set (spin_type=2, color_type=3, charge_type=-2, isospin_type=-2)
call field%set (mass_data=model%get_par_real_ptr (9))
call field%set (name = [var_str ("b")], anti = [var_str ("bbar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("T_QUARK"), 6)
call field%set (spin_type=2, color_type=3, charge_type=3, isospin_type=2)
call field%set (mass_data=model%get_par_real_ptr (10))
call field%set (width_data=model%get_par_real_ptr (11))
call field%set (name = [var_str ("t")], anti = [var_str ("tbar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("E_LEPTON"), 11)
call field%set (spin_type=2)
call field%set (mass_data=model%get_par_real_ptr (4))
call field%set (name = [var_str ("e-")], anti = [var_str ("e+")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("E_NEUTRINO"), 12)
call field%set (spin_type=2, is_left_handed=.true.)
call field%set (name = [var_str ("nue")], anti = [var_str ("nuebar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("MU_LEPTON"), 13)
call field%set (spin_type=2)
call field%set (mass_data=model%get_par_real_ptr (5))
call field%set (name = [var_str ("mu-")], anti = [var_str ("mu+")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("MU_NEUTRINO"), 14)
call field%set (spin_type=2, is_left_handed=.true.)
call field%set (name = [var_str ("numu")], anti = [var_str ("numubar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("TAU_LEPTON"), 15)
call field%set (spin_type=2)
call field%set (mass_data=model%get_par_real_ptr (6))
call field%set (name = [var_str ("tau-")], anti = [var_str ("tau+")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("TAU_NEUTRINO"), 16)
call field%set (spin_type=2, is_left_handed=.true.)
call field%set (name = [var_str ("nutau")], anti = [var_str ("nutaubar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("GLUON"), 21)
call field%set (spin_type=3, color_type=8)
call field%set (name = [var_str ("gl")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("PHOTON"), 22)
call field%set (spin_type=3)
call field%set (name = [var_str ("A")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("Z_BOSON"), 23)
call field%set (spin_type=3)
call field%set (mass_data=model%get_par_real_ptr (1))
call field%set (width_data=model%get_par_real_ptr (12))
call field%set (name = [var_str ("Z")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("W_BOSON"), 24)
call field%set (spin_type=3)
call field%set (mass_data=model%get_par_real_ptr (2))
call field%set (width_data=model%get_par_real_ptr (13))
call field%set (name = [var_str ("W+")], anti = [var_str ("W-")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("HIGGS"), 25)
call field%set (spin_type=1)
call field%set (mass_data=model%get_par_real_ptr (3))
call field%set (width_data=model%get_par_real_ptr (14))
call field%set (name = [var_str ("H")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("PROTON"), 2212)
call field%set (spin_type=2)
call field%set (name = [var_str ("p")], anti = [var_str ("pbar")])
! call field%set (mass_data=model%get_par_real_ptr (12))
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("HADRON_REMNANT_SINGLET"), 91)
call field%set (color_type=1)
call field%set (name = [var_str ("hr1")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("HADRON_REMNANT_TRIPLET"), 92)
call field%set (color_type=3)
call field%set (name = [var_str ("hr3")], anti = [var_str ("hr3bar")])
i = i + 1
field => model%get_field_ptr_by_index (i)
call field%init (var_str ("HADRON_REMNANT_OCTET"), 93)
call field%set (color_type=8)
call field%set (name = [var_str ("hr8")])
call model%freeze_fields ()
i = 0
i = i + 1
!!! QED
call model%set_vertex (i, [var_str ("dbar"), var_str ("d"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("ubar"), var_str ("u"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("sbar"), var_str ("s"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("cbar"), var_str ("c"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("bbar"), var_str ("b"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("tbar"), var_str ("t"), var_str ("A")])
i = i + 1
!!!
call model%set_vertex (i, [var_str ("e+"), var_str ("e-"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("mu+"), var_str ("mu-"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("tau+"), var_str ("tau-"), var_str ("A")])
i = i + 1
!!! QCD
call model%set_vertex (i, [var_str ("gl"), var_str ("gl"), var_str ("gl")])
i = i + 1
call model%set_vertex (i, [var_str ("gl"), var_str ("gl"), &
var_str ("gl"), var_str ("gl")])
i = i + 1
!!!
call model%set_vertex (i, [var_str ("dbar"), var_str ("d"), var_str ("gl")])
i = i + 1
call model%set_vertex (i, [var_str ("ubar"), var_str ("u"), var_str ("gl")])
i = i + 1
call model%set_vertex (i, [var_str ("sbar"), var_str ("s"), var_str ("gl")])
i = i + 1
call model%set_vertex (i, [var_str ("cbar"), var_str ("c"), var_str ("gl")])
i = i + 1
call model%set_vertex (i, [var_str ("bbar"), var_str ("b"), var_str ("gl")])
i = i + 1
call model%set_vertex (i, [var_str ("tbar"), var_str ("t"), var_str ("gl")])
i = i + 1
!!! Neutral currents
call model%set_vertex (i, [var_str ("dbar"), var_str ("d"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("ubar"), var_str ("u"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("sbar"), var_str ("s"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("cbar"), var_str ("c"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("bbar"), var_str ("b"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("tbar"), var_str ("t"), var_str ("Z")])
i = i + 1
!!!
call model%set_vertex (i, [var_str ("e+"), var_str ("e-"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("mu+"), var_str ("muu-"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("tau+"), var_str ("tau-"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("nuebar"), var_str ("nue"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("numubar"), var_str ("numu"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("nutaubar"), var_str ("nutau"), &
var_str ("Z")])
i = i + 1
!!! Charged currents
call model%set_vertex (i, [var_str ("ubar"), var_str ("d"), var_str ("W+")])
i = i + 1
call model%set_vertex (i, [var_str ("cbar"), var_str ("s"), var_str ("W+")])
i = i + 1
call model%set_vertex (i, [var_str ("tbar"), var_str ("b"), var_str ("W+")])
i = i + 1
call model%set_vertex (i, [var_str ("dbar"), var_str ("u"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("sbar"), var_str ("c"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("bbar"), var_str ("t"), var_str ("W-")])
i = i + 1
!!!
call model%set_vertex (i, [var_str ("nuebar"), var_str ("e-"), var_str ("W+")])
i = i + 1
call model%set_vertex (i, [var_str ("numubar"), var_str ("mu-"), var_str ("W+")])
i = i + 1
call model%set_vertex (i, [var_str ("nutaubar"), var_str ("tau-"), var_str ("W+")])
i = i + 1
call model%set_vertex (i, [var_str ("e+"), var_str ("nue"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("mu+"), var_str ("numu"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("tau+"), var_str ("nutau"), var_str ("W-")])
i = i + 1
!!! Yukawa
!!! keeping only 3rd generation for the moment
! call model%set_vertex (i, [var_str ("sbar"), var_str ("s"), var_str ("H")])
! i = i + 1
! call model%set_vertex (i, [var_str ("cbar"), var_str ("c"), var_str ("H")])
! i = i + 1
call model%set_vertex (i, [var_str ("bbar"), var_str ("b"), var_str ("H")])
i = i + 1
call model%set_vertex (i, [var_str ("tbar"), var_str ("t"), var_str ("H")])
i = i + 1
! call model%set_vertex (i, [var_str ("mubar"), var_str ("mu"), var_str ("H")])
! i = i + 1
call model%set_vertex (i, [var_str ("taubar"), var_str ("tau"), var_str ("H")])
i = i + 1
!!! Vector-boson self-interactions
call model%set_vertex (i, [var_str ("W+"), var_str ("W-"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("W+"), var_str ("W-"), var_str ("Z")])
i = i + 1
!!!
call model%set_vertex (i, [var_str ("W+"), var_str ("W-"), var_str ("Z"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("W+"), var_str ("W+"), var_str ("W-"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("W+"), var_str ("W-"), var_str ("Z"), var_str ("A")])
i = i + 1
call model%set_vertex (i, [var_str ("W+"), var_str ("W-"), var_str ("A"), var_str ("A")])
i = i + 1
!!! Higgs - vector boson
! call model%set_vertex (i, [var_str ("H"), var_str ("Z"), var_str ("A")])
! i = i + 1
! call model%set_vertex (i, [var_str ("H"), var_str ("A"), var_str ("A")])
! i = i + 1
! call model%set_vertex (i, [var_str ("H"), var_str ("gl"), var_str ("gl")])
! i = i + 1
!!!
call model%set_vertex (i, [var_str ("H"), var_str ("W+"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("H"), var_str ("Z"), var_str ("Z")])
i = i + 1
call model%set_vertex (i, [var_str ("H"), var_str ("H"), var_str ("W+"), var_str ("W-")])
i = i + 1
call model%set_vertex (i, [var_str ("H"), var_str ("H"), var_str ("Z"), var_str ("Z")])
i = i + 1
!!! Higgs self-interactions
call model%set_vertex (i, [var_str ("H"), var_str ("H"), var_str ("H")])
i = i + 1
call model%set_vertex (i, [var_str ("H"), var_str ("H"), var_str ("H"), var_str ("H")])
i = i + 1
call model%freeze_vertices ()
end subroutine init_sm_full_test
@ %def init_sm_full_test
@ Initialize a [[part_prop]] object by passing a [[particle_label]],
which is simply the particle name. [[part_prop]] should be part of the
[[particle]] array of [[feyngraph_set]]. We use the [[model]] of
[[feyngraph_set]] to obtain the relevant data of the particle which is
needed to find [[phase_space]] parametrizations. When a [[part_prop]]
is initialized, we add and initialize also the corresponding anti-
particle [[part_prop]] if it is not yet in the array.
<<Cascades2: part prop: TBP>>=
procedure :: init => part_prop_init
<<Cascades2: sub interfaces>>=
recursive module subroutine part_prop_init &
(part_prop, feyngraph_set, particle_label)
class(part_prop_t), intent(out), target :: part_prop
type(feyngraph_set_t), intent(inout) :: feyngraph_set
character(len=*), intent(in) :: particle_label
end subroutine part_prop_init
<<Cascades2: procedures>>=
recursive module subroutine part_prop_init &
(part_prop, feyngraph_set, particle_label)
class(part_prop_t), intent(out), target :: part_prop
type(feyngraph_set_t), intent(inout) :: feyngraph_set
character(len=*), intent(in) :: particle_label
type(flavor_t) :: flv, anti
type(string_t) :: name
integer :: i
name = particle_label
call flv%init (name, feyngraph_set%model)
part_prop%particle_label = particle_label
part_prop%pdg = flv%get_pdg ()
part_prop%mass = flv%get_mass ()
part_prop%width = flv%get_width()
part_prop%spin_type = flv%get_spin_type ()
part_prop%is_vector = flv%get_spin_type () == VECTOR
part_prop%empty = .false.
part_prop%tex_name = flv%get_tex_name ()
anti = flv%anti ()
if (flv%get_pdg() == anti%get_pdg()) then
select type (part_prop)
type is (part_prop_t)
part_prop%anti => part_prop
end select
else
do i=1, size (feyngraph_set%particle)
if (feyngraph_set%particle(i)%pdg == (- part_prop%pdg)) then
part_prop%anti => feyngraph_set%particle(i)
exit
else if (feyngraph_set%particle(i)%empty) then
part_prop%anti => feyngraph_set%particle(i)
call feyngraph_set%particle(i)%init &
(feyngraph_set, char(anti%get_name()))
exit
end if
end do
end if
end subroutine part_prop_init
@ %def part_prop_init
@ This subroutine assigns to a node the particle properties. Since these
properties do not change and are simply read from the model file, we
use pointers to the elements of the [[particle]] array of the
[[feyngraph_set]]. If there is no corresponding array element, we
have to initialize the first empty element of the array.
<<Cascades2: parameters>>=
integer, parameter :: PRT_ARRAY_SIZE = 200
<<Cascades2: f node: TBP>>=
procedure :: assign_particle_properties => f_node_assign_particle_properties
<<Cascades2: sub interfaces>>=
module subroutine f_node_assign_particle_properties (node, feyngraph_set)
class(f_node_t), intent(inout ) :: node
type(feyngraph_set_t), intent(inout) :: feyngraph_set
end subroutine f_node_assign_particle_properties
<<Cascades2: procedures>>=
module subroutine f_node_assign_particle_properties (node, feyngraph_set)
class(f_node_t), intent(inout ) :: node
type(feyngraph_set_t), intent(inout) :: feyngraph_set
character(len=LABEL_LEN) :: particle_label
integer :: i
particle_label = node%particle_label(1:index (node%particle_label, '[')-1)
if (.not. associated (feyngraph_set%particle)) then
allocate (feyngraph_set%particle (PRT_ARRAY_SIZE))
end if
do i = 1, size (feyngraph_set%particle)
if (particle_label == feyngraph_set%particle(i)%particle_label) then
node%particle => feyngraph_set%particle(i)
exit
else if (feyngraph_set%particle(i)%empty) then
call feyngraph_set%particle(i)%init (feyngraph_set, particle_label)
node%particle => feyngraph_set%particle(i)
exit
end if
end do
!!! Since the O'Mega output uses the anti-particles instead of the
!!! particles specified in the process definition, we revert this
!!! here. An exception is the first particle in the parsable DAG output
node%particle => node%particle%anti
end subroutine f_node_assign_particle_properties
@ %def f_node_assign_particle_properties
@ From the output of a Feynman diagram (in the non-factorized output)
we need to find out how many daughter nodes would be required to
reconstruct it correctly, to make sure that we keep
only those [[feyngraphs]] which are constructed solely on the basis of
the 3-vertices which are provided by the model. The number of daughter
particles can easily be determined from the syntax of O'Mega's output:
The particle which appears before the colon ':' is the mother particle.
The particles or subtrees (i.e. whole parentheses) follow after the
colon and are separated by commas.
<<Cascades2: procedures>>=
function get_n_daughters (subtree_string, pos_first_colon) &
result (n_daughters)
character(len=*), intent(in) :: subtree_string
integer, intent(in) :: pos_first_colon
integer :: n_daughters
integer :: n_open_par
integer :: i
n_open_par = 1
n_daughters = 0
if (len_trim(subtree_string) > 0) then
if (pos_first_colon > 0) then
do i=pos_first_colon, len_trim(subtree_string)
if (subtree_string(i:i) == ',') then
if (n_open_par == 1) n_daughters = n_daughters + 1
else if (subtree_string(i:i) == '(') then
n_open_par = n_open_par + 1
else if (subtree_string(i:i) == ')') then
n_open_par = n_open_par - 1
end if
end do
if (n_open_par == 0) then
n_daughters = n_daughters + 1
end if
end if
end if
end function get_n_daughters
@ %def get_n_daughters
@
\subsection{Reconstruction of trees}
The reconstruction of a tree or subtree with the non-factorized input can
be done recursively, i.e. we first find the root of the tree in the
string and create an [[f_node]]. Then we look for daughters, which in the
string appear either as single particles or subtrees (which are of the
same form as the tree which we want to reconstruct. Therefore the
subroutine can simply be called again and again until there are no more
daughter nodes to create. When we meet a vertex which requires more than
two daughter particles, we stop the recursion and disable the node using
its [[keep]] variable. Whenever a daughter node is not kept, we do not
keep the mother node as well.
<<Cascades2: procedures>>=
recursive subroutine node_construct_subtree_rec (feyngraph_set, &
feyngraph, subtree_string, mother_node)
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(feyngraph_t), intent(inout) :: feyngraph
character(len=*), intent(in) :: subtree_string
type(f_node_t), pointer, intent(inout) :: mother_node
integer :: n_daughters
integer :: pos_first_colon
integer :: current_daughter
integer :: pos_subtree_begin, pos_subtree_end
integer :: i
integer :: n_open_par
if (.not. associated (mother_node)) then
call feyngraph_set%f_node_list%add_entry (subtree_string, mother_node, .true.)
current_daughter = 1
n_open_par = 1
pos_first_colon = index (subtree_string, ':')
n_daughters = get_n_daughters (subtree_string, pos_first_colon)
if (pos_first_colon == 0) then
mother_node%particle_label = subtree_string
else
mother_node%particle_label = subtree_string(2:pos_first_colon-1)
end if
if (.not. associated (mother_node%particle)) then
call mother_node%assign_particle_properties (feyngraph_set)
end if
if (n_daughters /= 2 .and. n_daughters /= 0) then
mother_node%keep = .false.
feyngraph%keep = .false.
return
end if
pos_subtree_begin = pos_first_colon + 1
do i = pos_first_colon + 1, len(trim(subtree_string))
if (current_daughter == 2) then
pos_subtree_end = len(trim(subtree_string)) - 1
call node_construct_subtree_rec (feyngraph_set, feyngraph, &
subtree_string(pos_subtree_begin:pos_subtree_end), &
mother_node%daughter2)
exit
else if (subtree_string(i:i) == ',') then
if (n_open_par == 1) then
pos_subtree_end = i - 1
call node_construct_subtree_rec (feyngraph_set, feyngraph, &
subtree_string(pos_subtree_begin:pos_subtree_end), &
mother_node%daughter1)
current_daughter = 2
pos_subtree_begin = i + 1
end if
else if (subtree_string(i:i) == '(') then
n_open_par = n_open_par + 1
else if (subtree_string(i:i) == ')') then
n_open_par = n_open_par - 1
end if
end do
end if
if (associated (mother_node%daughter1)) then
if (.not. mother_node%daughter1%keep) then
mother_node%keep = .false.
end if
end if
if (associated (mother_node%daughter2)) then
if (.not. mother_node%daughter2%keep) then
mother_node%keep = .false.
end if
end if
if (associated (mother_node%daughter1) .and. &
associated (mother_node%daughter2)) then
mother_node%n_subtree_nodes = &
mother_node%daughter1%n_subtree_nodes &
+ mother_node%daughter2%n_subtree_nodes + 1
end if
if (.not. mother_node%keep) then
feyngraph%keep = .false.
end if
end subroutine node_construct_subtree_rec
@ %def node_construct_subtree_rec
@ When the non-factorized version of the O'Mega output is used, the
[[feyngraph]] is reconstructed from the contents of its [[string_t]]
variable [[omega_feyngraph_output]]. This can be used for the recursive
reconstruction of the tree of [[k_nodes]] with
[[node_construct_subtree_rec]].
<<Cascades2: procedures>>=
subroutine feyngraph_construct (feyngraph_set, feyngraph)
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(feyngraph_t), pointer, intent(inout) :: feyngraph
call node_construct_subtree_rec (feyngraph_set, feyngraph, &
char(feyngraph%omega_feyngraph_output), feyngraph%root)
feyngraph%n_nodes = feyngraph%root%n_subtree_nodes
end subroutine feyngraph_construct
@ %def feyngraph_construct
@ We introduce another node type, which is called [[dag_node_t]] and
is used to reproduce the dag structure which is represented by the input.
The [[dag_nodes]] can have several combinations of daughters 1 and 2.
The [[dag]] type contains an array of [[dag_nodes]] and is only used
for the reconstruction of [[feyngraphs]] which are factorized as well, but
in the other direction as the original output. This means in particular
that the outgoing particles in the output file (which there can appear
many times) exist only once as [[f_nodes]]. To represent combinations of
daughters and alternatives (options), we further use the types
[[dag_options_t]] and [[dag_combination_t]]. The [[dag_nodes]],
[[dag_options]] and [[dag_combinations]] correspond to a substring of
the string which has been read from file (and transformed into an object
of type [[dag_string_t]], which is simply another compact representation
of this string), or a modified version of this substring. The aim is to
create only one object for a given substring, even if it appears several
times in the original string and then create trees of [[f_nodes]], which
build up the [[feyngraph]], such that as many [[f_nodes]] as possible can be reused.
An outgoing particle (always interpreting the input as a decay) is
called a [[leaf]] in the context of a [[dag]].
<<Cascades2: types>>=
type :: dag_node_t
integer :: string_len
type(dag_string_t) :: string
logical :: leaf = .false.
type(f_node_ptr_t), dimension (:), allocatable :: f_node
integer :: subtree_size = 0
contains
<<Cascades2: dag node: TBP>>
end type dag_node_t
@ %def dag_node_t
<<Cascades2: dag node: TBP>>=
procedure :: final => dag_node_final
<<Cascades2: sub interfaces>>=
module subroutine dag_node_final (dag_node)
class(dag_node_t), intent(inout) :: dag_node
end subroutine dag_node_final
<<Cascades2: procedures>>=
module subroutine dag_node_final (dag_node)
class(dag_node_t), intent(inout) :: dag_node
integer :: i
call dag_node%string%final ()
if (allocated (dag_node%f_node)) then
do i=1, size (dag_node%f_node)
if (associated (dag_node%f_node(i)%node)) then
call dag_node%f_node(i)%node%final ()
deallocate (dag_node%f_node(i)%node)
end if
end do
deallocate (dag_node%f_node)
end if
end subroutine dag_node_final
@ %def dag_node_final
@ Whenever there are more than one possible subtrees (represented by
a [[dag_node]]) or combinations of subtrees to daughters (represented
by [[dag_combination_t]]), we use the type [[dag_options_t]]. In the
syntax of the factorized output, options are listed within curly
braces, separated by horizontal bars.
<<Cascades2: types>>=
type :: dag_options_t
integer :: string_len
type(dag_string_t) :: string
type(f_node_ptr_t), dimension (:), allocatable :: f_node_ptr1
type(f_node_ptr_t), dimension (:), allocatable :: f_node_ptr2
contains
<<Cascades2: dag options: TBP>>
end type dag_options_t
@ %def dag_node_options_t
<<Cascades2: dag options: TBP>>=
procedure :: final => dag_options_final
<<Cascades2: sub interfaces>>=
module subroutine dag_options_final (dag_options)
class(dag_options_t), intent(inout) :: dag_options
end subroutine dag_options_final
<<Cascades2: procedures>>=
module subroutine dag_options_final (dag_options)
class(dag_options_t), intent(inout) :: dag_options
integer :: i
call dag_options%string%final ()
if (allocated (dag_options%f_node_ptr1)) then
do i=1, size (dag_options%f_node_ptr1)
dag_options%f_node_ptr1(i)%node => null ()
end do
deallocate (dag_options%f_node_ptr1)
end if
if (allocated (dag_options%f_node_ptr2)) then
do i=1, size (dag_options%f_node_ptr2)
dag_options%f_node_ptr2(i)%node => null ()
end do
deallocate (dag_options%f_node_ptr2)
end if
end subroutine dag_options_final
@ %def dag_options_final
@ A pair of two daughters (which can be [[dag_nodes]] or [[dag_options]])
is represented by the type [[dag_combination_t]]. In the original string,
a [[dag_combination]] appears between parentheses, which contain a comma,
but not a colon. If we find a colon between these parentheses, it is a
a [[dag_node]] instead.
<<Cascades2: types>>=
type :: dag_combination_t
integer :: string_len
type(dag_string_t) :: string
integer, dimension (2) :: combination
type(f_node_ptr_t), dimension (:), allocatable :: f_node_ptr1
type(f_node_ptr_t), dimension (:), allocatable :: f_node_ptr2
contains
<<Cascades2: dag combination: TBP>>
end type dag_combination_t
@ %def dag_combination_t
<<Cascades2: dag combination: TBP>>=
procedure :: final => dag_combination_final
<<Cascades2: sub interfaces>>=
module subroutine dag_combination_final (dag_combination)
class(dag_combination_t), intent(inout) :: dag_combination
end subroutine dag_combination_final
<<Cascades2: procedures>>=
module subroutine dag_combination_final (dag_combination)
class(dag_combination_t), intent(inout) :: dag_combination
integer :: i
call dag_combination%string%final ()
if (allocated (dag_combination%f_node_ptr1)) then
do i=1, size (dag_combination%f_node_ptr1)
dag_combination%f_node_ptr1(i)%node => null ()
end do
deallocate (dag_combination%f_node_ptr1)
end if
if (allocated (dag_combination%f_node_ptr2)) then
do i=1, size (dag_combination%f_node_ptr2)
dag_combination%f_node_ptr2(i)%node => null ()
end do
deallocate (dag_combination%f_node_ptr2)
end if
end subroutine dag_combination_final
@ %def dag_combination_final
@ Here is the type representing the DAG, i.e. it holds arrays of the
[[dag_nodes]], [[dag_options]] and [[dag_combinations]]. The root node
of the [[dag]] is the last filled element of the [[node]] array.
<<Cascades2: types>>=
type :: dag_t
type(dag_string_t) :: string
type(dag_node_t), dimension (:), allocatable :: node
type(dag_options_t), dimension (:), allocatable :: options
type(dag_combination_t), dimension (:), allocatable :: combination
integer :: n_nodes = 0
integer :: n_options = 0
integer :: n_combinations = 0
contains
<<Cascades2: dag: TBP>>
end type dag_t
@ %def dag_t
<<Cascades2: dag: TBP>>=
procedure :: final => dag_final
<<Cascades2: sub interfaces>>=
module subroutine dag_final (dag)
class(dag_t), intent(inout) :: dag
end subroutine dag_final
<<Cascades2: procedures>>=
module subroutine dag_final (dag)
class(dag_t), intent(inout) :: dag
integer :: i
call dag%string%final ()
if (allocated (dag%node)) then
do i=1, size (dag%node)
call dag%node(i)%final ()
end do
deallocate (dag%node)
end if
if (allocated (dag%options)) then
do i=1, size (dag%options)
call dag%options(i)%final ()
end do
deallocate (dag%options)
end if
if (allocated (dag%combination)) then
do i=1, size (dag%combination)
call dag%combination(i)%final ()
end do
deallocate (dag%combination)
end if
end subroutine dag_final
@ %def dag_final
@ We construct the DAG from the given [[dag_string]] which is modified
several times so that in the end the remaining string corresponds to a
simple [[dag_node]], the root of the factorized tree. This means that
we first identify the leaves, i.e. outgoing particles. Then we identify
[[dag_nodes]], [[dag_combinations]] and [[options]] until the number of
these objects does not change any more. Identifying means that we add
a corresponding object to the array (if not yet present), which can be
identified with the corresponding substring, and replace the substring
in the original [[dag_string]] by a [[dag_token]] of the corresponding
type (in the char output of this token, this corresponds to a place
holder like e.g. '<O23>' which in this particular case corresponds to
an option and can be found at the position 23 in the array). The
character output of the substrings turns out to be very useful for
debugging.
<<Cascades2: dag: TBP>>=
procedure :: construct => dag_construct
<<Cascades2: sub interfaces>>=
module subroutine dag_construct (dag, feyngraph_set)
class(dag_t), intent(inout) :: dag
type(feyngraph_set_t), intent(inout) :: feyngraph_set
end subroutine dag_construct
<<Cascades2: procedures>>=
module subroutine dag_construct (dag, feyngraph_set)
class(dag_t), intent(inout) :: dag
type(feyngraph_set_t), intent(inout) :: feyngraph_set
integer :: n_nodes
integer :: n_options
integer :: n_combinations
logical :: continue_loop
integer :: subtree_size
integer :: i,j
subtree_size = 1
call dag%get_nodes_and_combinations (leaves = .true.)
do i=1, dag%n_nodes
call dag%node(i)%make_f_nodes (feyngraph_set, dag)
end do
continue_loop = .true.
subtree_size = subtree_size + 2
do while (continue_loop)
n_nodes = dag%n_nodes
n_options = dag%n_options
n_combinations = dag%n_combinations
call dag%get_nodes_and_combinations (leaves = .false.)
if (n_nodes /= dag%n_nodes) then
dag%node(n_nodes+1:dag%n_nodes)%subtree_size = subtree_size
do i = n_nodes+1, dag%n_nodes
call dag%node(i)%make_f_nodes (feyngraph_set, dag)
end do
subtree_size = subtree_size + 2
end if
if (n_combinations /= dag%n_combinations) then
!$OMP PARALLEL DO
do i = n_combinations+1, dag%n_combinations
call dag%combination(i)%make_f_nodes (feyngraph_set, dag)
end do
!$OMP END PARALLEL DO
end if
call dag%get_options ()
if (n_options /= dag%n_options) then
!$OMP PARALLEL DO
do i = n_options+1, dag%n_options
call dag%options(i)%make_f_nodes (feyngraph_set, dag)
end do
!$OMP END PARALLEL DO
end if
if (n_nodes == dag%n_nodes .and. n_options == dag%n_options &
.and. n_combinations == dag%n_combinations) then
continue_loop = .false.
end if
end do
!!! add root node to dag
call dag%add_node (dag%string%t, leaf = .false.)
dag%node(dag%n_nodes)%subtree_size = subtree_size
call dag%node(dag%n_nodes)%make_f_nodes (feyngraph_set, dag)
if (debug2_active (D_PHASESPACE)) then
call dag%write (output_unit)
end if
!!! set indices for all f_nodes
do i=1, dag%n_nodes
if (allocated (dag%node(i)%f_node)) then
do j=1, size (dag%node(i)%f_node)
if (associated (dag%node(i)%f_node(j)%node)) &
call dag%node(i)%f_node(j)%node%set_index ()
end do
end if
end do
end subroutine dag_construct
@ %def dag_construct
@ Identify [[dag_nodes]] and [[dag_combinations]]. Leaves are simply
nodes (i.e. of type [[NODE_TK]]) where only one bit in the bincode is
set. The [[dag_nodes]] and [[dag_combinations]] have in common that they
are surrounded by parentheses. There is however a way to distinguish
between them because the corresponding substring contains a colon (or
[[dag_token]] with type [[COLON_TK]]) if it is a [[dag_node]]. Otherwise
it is a [[dag_combination]]. The string of the [[dag_node]] or
[[dag_combination]] should not contain curly braces, because these
correspond to [[dag_options]] and should be identified before.
<<Cascades2: dag: TBP>>=
procedure :: get_nodes_and_combinations => dag_get_nodes_and_combinations
<<Cascades2: sub interfaces>>=
module subroutine dag_get_nodes_and_combinations (dag, leaves)
class(dag_t), intent(inout) :: dag
logical, intent(in) :: leaves
end subroutine dag_get_nodes_and_combinations
<<Cascades2: procedures>>=
module subroutine dag_get_nodes_and_combinations (dag, leaves)
class(dag_t), intent(inout) :: dag
logical, intent(in) :: leaves
type(dag_string_t) :: new_string
integer :: i, j, k
integer :: i_node
integer :: new_size
integer :: first_colon
logical :: combination
!!! Create nodes also for external particles, except for the incoming one
!!! which appears as the root of the tree. These can easily be identified
!!! by their bincodes, since they should contain only one bit which is set.
if (leaves) then
first_colon = &
minloc (dag%string%t%type, 1, dag%string%t%type == COLON_TK)
do i = first_colon + 1, size (dag%string%t)
if (dag%string%t(i)%type == NODE_TK) then
if (popcnt(dag%string%t(i)%bincode) == 1) then
call dag%add_node (dag%string%t(i:i), .true., i_node)
call dag%string%t(i)%init_dag_object_token (DAG_NODE_TK, i_node)
end if
end if
end do
call dag%string%update_char_len ()
else
!!! Create a node or combination for every closed pair of parentheses
!!! which do not contain any other parentheses or curly braces.
!!! A node (not outgoing) contains a colon. This is not the case
!!! for combinations, which we use as the criteria to distinguish
!!! between both.
allocate (new_string%t (size (dag%string%t)))
i = 1
new_size = 0
do while (i <= size(dag%string%t))
if (dag%string%t(i)%type == OPEN_PAR_TK) then
combination = .true.
do j = i+1, size (dag%string%t)
select case (dag%string%t(j)%type)
case (CLOSED_PAR_TK)
new_size = new_size + 1
if (combination) then
call dag%add_combination (dag%string%t(i:j), i_node)
call new_string%t(new_size)%init_dag_object_token &
(DAG_COMBINATION_TK, i_node)
else
call dag%add_node (dag%string%t(i:j), leaves, i_node)
call new_string%t(new_size)%init_dag_object_token &
(DAG_NODE_TK, i_node)
end if
i = j + 1
exit
case (OPEN_PAR_TK, OPEN_CURLY_TK, CLOSED_CURLY_TK)
new_size = new_size + 1
new_string%t(new_size) = dag%string%t(i)
i = i + 1
exit
case (COLON_TK)
combination = .false.
end select
end do
else
new_size = new_size + 1
new_string%t(new_size) = dag%string%t(i)
i = i + 1
end if
end do
dag%string = new_string%t(:new_size)
call dag%string%update_char_len ()
end if
end subroutine dag_get_nodes_and_combinations
@ %def dag_get_nodes_and_combinations
@ Identify [[dag_options]], i.e. lists of rival nodes or combinations
of nodes. These are identified by the surrounding curly braces. They
should not contain any parentheses any more, because these correspond
either to nodes or to combinations and should be identified before.
<<Cascades2: dag: TBP>>=
procedure :: get_options => dag_get_options
<<Cascades2: sub interfaces>>=
module subroutine dag_get_options (dag)
class(dag_t), intent(inout) :: dag
end subroutine dag_get_options
<<Cascades2: procedures>>=
module subroutine dag_get_options (dag)
class(dag_t), intent(inout) :: dag
type(dag_string_t) :: new_string
integer :: i, j, k
integer :: new_size
integer :: i_options
character(len=10) :: index_char
integer :: index_start, index_end
!!! Create a node or combination for every closed pair of parentheses
!!! which do not contain any other parentheses or curly braces.
!!! A node (not outgoing) contains a colon. This is not the case
!!! for combinations, which we use as the criteria to distinguish
!!! between both.
allocate (new_string%t (size (dag%string%t)))
i = 1
new_size = 0
do while (i <= size(dag%string%t))
if (dag%string%t(i)%type == OPEN_CURLY_TK) then
do j = i+1, size (dag%string%t)
select case (dag%string%t(j)%type)
case (CLOSED_CURLY_TK)
new_size = new_size + 1
call dag%add_options (dag%string%t(i:j), i_options)
call new_string%t(new_size)%init_dag_object_token (DAG_OPTIONS_TK, i_options)
i = j + 1
exit
case (OPEN_PAR_TK, CLOSED_PAR_TK, OPEN_CURLY_TK)
new_size = new_size + 1
new_string%t(new_size) = dag%string%t(i)
i = i + 1
exit
end select
end do
else
new_size = new_size + 1
new_string%t(new_size) = dag%string%t(i)
i = i + 1
end if
end do
dag%string = new_string%t(:new_size)
call dag%string%update_char_len ()
end subroutine dag_get_options
@ %def dag_get_options
@ Add a [[dag_node]] to the list. The optional argument returns the index
of the node. The node might already exist. In this case we only return
the index.
<<Cascades2: dag: TBP>>=
procedure :: add_node => dag_add_node
<<Cascades2: parameters>>=
integer, parameter :: DAG_STACK_SIZE = 1000
<<Cascades2: sub interfaces>>=
module subroutine dag_add_node (dag, string, leaf, i_node)
class(dag_t), intent(inout) :: dag
type(dag_token_t), dimension (:), intent(in) :: string
logical, intent(in) :: leaf
integer, intent(out), optional :: i_node
end subroutine dag_add_node
<<Cascades2: procedures>>=
module subroutine dag_add_node (dag, string, leaf, i_node)
class(dag_t), intent(inout) :: dag
type(dag_token_t), dimension (:), intent(in) :: string
logical, intent(in) :: leaf
integer, intent(out), optional :: i_node
type(dag_node_t), dimension (:), allocatable :: tmp_node
integer :: string_len
integer :: i
string_len = sum (string%char_len)
if (.not. allocated (dag%node)) then
allocate (dag%node (DAG_STACK_SIZE))
else if (dag%n_nodes == size (dag%node)) then
allocate (tmp_node (dag%n_nodes))
tmp_node = dag%node
deallocate (dag%node)
allocate (dag%node (dag%n_nodes+DAG_STACK_SIZE))
dag%node(:dag%n_nodes) = tmp_node
deallocate (tmp_node)
end if
do i = 1, dag%n_nodes
if (dag%node(i)%string_len == string_len) then
if (size (dag%node(i)%string%t) == size (string)) then
if (all(dag%node(i)%string%t == string)) then
if (present (i_node)) i_node = i
return
end if
end if
end if
end do
dag%n_nodes = dag%n_nodes + 1
dag%node(dag%n_nodes)%string = string
dag%node(dag%n_nodes)%string_len = string_len
if (present (i_node)) i_node = dag%n_nodes
dag%node(dag%n_nodes)%leaf = leaf
end subroutine dag_add_node
@ %def dag_add_node
@ A similar subroutine for options.
<<Cascades2: dag: TBP>>=
procedure :: add_options => dag_add_options
<<Cascades2: sub interfaces>>=
module subroutine dag_add_options (dag, string, i_options)
class(dag_t), intent(inout) :: dag
type(dag_token_t), dimension (:), intent(in) :: string
integer, intent(out), optional :: i_options
end subroutine dag_add_options
<<Cascades2: procedures>>=
module subroutine dag_add_options (dag, string, i_options)
class(dag_t), intent(inout) :: dag
type(dag_token_t), dimension (:), intent(in) :: string
integer, intent(out), optional :: i_options
type(dag_options_t), dimension (:), allocatable :: tmp_options
integer :: string_len
integer :: i
string_len = sum (string%char_len)
if (.not. allocated (dag%options)) then
allocate (dag%options (DAG_STACK_SIZE))
else if (dag%n_options == size (dag%options)) then
allocate (tmp_options (dag%n_options))
tmp_options = dag%options
deallocate (dag%options)
allocate (dag%options (dag%n_options+DAG_STACK_SIZE))
dag%options(:dag%n_options) = tmp_options
deallocate (tmp_options)
end if
do i = 1, dag%n_options
if (dag%options(i)%string_len == string_len) then
if (size (dag%options(i)%string%t) == size (string)) then
if (all(dag%options(i)%string%t == string)) then
if (present (i_options)) i_options = i
return
end if
end if
end if
end do
dag%n_options = dag%n_options + 1
dag%options(dag%n_options)%string = string
dag%options(dag%n_options)%string_len = string_len
if (present (i_options)) i_options = dag%n_options
end subroutine dag_add_options
@ %def dag_add_options
@ A similar subroutine for combinations.
<<Cascades2: dag: TBP>>=
procedure :: add_combination => dag_add_combination
<<Cascades2: sub interfaces>>=
module subroutine dag_add_combination (dag, string, i_combination)
class(dag_t), intent(inout) :: dag
type(dag_token_t), dimension (:), intent(in) :: string
integer, intent(out), optional :: i_combination
end subroutine dag_add_combination
<<Cascades2: procedures>>=
module subroutine dag_add_combination (dag, string, i_combination)
class(dag_t), intent(inout) :: dag
type(dag_token_t), dimension (:), intent(in) :: string
integer, intent(out), optional :: i_combination
type(dag_combination_t), dimension (:), allocatable :: tmp_combination
integer :: string_len
integer :: i
string_len = sum (string%char_len)
if (.not. allocated (dag%combination)) then
allocate (dag%combination (DAG_STACK_SIZE))
else if (dag%n_combinations == size (dag%combination)) then
allocate (tmp_combination (dag%n_combinations))
tmp_combination = dag%combination
deallocate (dag%combination)
allocate (dag%combination (dag%n_combinations+DAG_STACK_SIZE))
dag%combination(:dag%n_combinations) = tmp_combination
deallocate (tmp_combination)
end if
do i = 1, dag%n_combinations
if (dag%combination(i)%string_len == string_len) then
if (size (dag%combination(i)%string%t) == size (string)) then
if (all(dag%combination(i)%string%t == string)) then
i_combination = i
return
end if
end if
end if
end do
dag%n_combinations = dag%n_combinations + 1
dag%combination(dag%n_combinations)%string = string
dag%combination(dag%n_combinations)%string_len = string_len
if (present (i_combination)) i_combination = dag%n_combinations
end subroutine dag_add_combination
@ %def dag_add_combination
@ For a given [[dag_node]] we want to create all [[f_nodes]]. If the node
is not a leaf, it contains in its string placeholders for options or
combinations. For these objects there are similar subroutines which are
needed here to obtain the sets of daughter nodes. If the [[dag_node]] is
a leaf, it corresponds to an external particle and the token contains the
particle name.
<<Cascades2: dag node: TBP>>=
procedure :: make_f_nodes => dag_node_make_f_nodes
<<Cascades2: sub interfaces>>=
module subroutine dag_node_make_f_nodes (dag_node, feyngraph_set, dag)
class(dag_node_t), intent(inout) :: dag_node
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(dag_t), intent(inout) :: dag
end subroutine dag_node_make_f_nodes
<<Cascades2: procedures>>=
module subroutine dag_node_make_f_nodes (dag_node, feyngraph_set, dag)
class(dag_node_t), intent(inout) :: dag_node
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(dag_t), intent(inout) :: dag
character(len=LABEL_LEN) :: particle_label
integer :: i, j
integer, dimension (2) :: obj
integer, dimension (2) :: i_obj
integer :: n_obj
integer :: pos
integer :: new_size, size1, size2
integer, dimension(:), allocatable :: match
if (allocated (dag_node%f_node)) return
pos = minloc (dag_node%string%t%type, 1,dag_node%string%t%type == NODE_TK)
particle_label = char (dag_node%string%t(pos))
if (dag_node%leaf) then
!!! construct subtree with procedure similar to the one for the old output
allocate (dag_node%f_node(1))
allocate (dag_node%f_node(1)%node)
dag_node%f_node(1)%node%particle_label = particle_label
call dag_node%f_node(1)%node%assign_particle_properties (feyngraph_set)
if (.not. dag_node%f_node(1)%node%keep) then
deallocate (dag_node%f_node)
return
end if
else
n_obj = 0
do i = 1, size (dag_node%string%t)
select case (dag_node%string%t(i)%type)
case (DAG_NODE_TK, DAG_OPTIONS_TK, DAG_COMBINATION_TK)
n_obj = n_obj + 1
if (n_obj > 2) return
obj(n_obj) = dag_node%string%t(i)%type
i_obj(n_obj) = dag_node%string%t(i)%index
end select
end do
if (n_obj == 1) then
if (obj(1) == DAG_OPTIONS_TK) then
if (allocated (dag%options(i_obj(1))%f_node_ptr1)) then
size1 = size(dag%options(i_obj(1))%f_node_ptr1)
allocate (dag_node%f_node(size1))
do i=1, size1
allocate (dag_node%f_node(i)%node)
dag_node%f_node(i)%node%particle_label = particle_label
call dag_node%f_node(i)%node%assign_particle_properties (feyngraph_set)
dag_node%f_node(i)%node%daughter1 => dag%options(i_obj(1))%f_node_ptr1(i)%node
dag_node%f_node(i)%node%daughter2 => dag%options(i_obj(1))%f_node_ptr2(i)%node
dag_node%f_node(i)%node%n_subtree_nodes = &
dag%options(i_obj(1))%f_node_ptr1(i)%node%n_subtree_nodes &
+ dag%options(i_obj(1))%f_node_ptr2(i)%node%n_subtree_nodes + 1
end do
end if
else if (obj(1) == DAG_COMBINATION_TK) then
if (allocated (dag%combination(i_obj(1))%f_node_ptr1)) then
size1 = size(dag%combination(i_obj(1))%f_node_ptr1)
allocate (dag_node%f_node(size1))
do i=1, size1
allocate (dag_node%f_node(i)%node)
dag_node%f_node(i)%node%particle_label = particle_label
call dag_node%f_node(i)%node%assign_particle_properties (feyngraph_set)
dag_node%f_node(i)%node%daughter1 => dag%combination(i_obj(1))%f_node_ptr1(i)%node
dag_node%f_node(i)%node%daughter2 => dag%combination(i_obj(1))%f_node_ptr2(i)%node
dag_node%f_node(i)%node%n_subtree_nodes = &
dag%combination(i_obj(1))%f_node_ptr1(i)%node%n_subtree_nodes &
+ dag%combination(i_obj(1))%f_node_ptr2(i)%node%n_subtree_nodes + 1
end do
end if
end if
!!! simply set daughter pointers, daughters are already combined correctly
else if (n_obj == 2) then
size1 = 0
size2 = 0
if (obj(1) == DAG_NODE_TK) then
if (allocated (dag%node(i_obj(1))%f_node)) then
do i=1, size (dag%node(i_obj(1))%f_node)
if (dag%node(i_obj(1))%f_node(i)%node%keep) size1 = size1 + 1
end do
end if
else if (obj(1) == DAG_OPTIONS_TK) then
if (allocated (dag%options(i_obj(1))%f_node_ptr1)) then
do i=1, size (dag%options(i_obj(1))%f_node_ptr1)
if (dag%options(i_obj(1))%f_node_ptr1(i)%node%keep) size1 = size1 + 1
end do
end if
end if
if (obj(2) == DAG_NODE_TK) then
if (allocated (dag%node(i_obj(2))%f_node)) then
do i=1, size (dag%node(i_obj(2))%f_node)
if (dag%node(i_obj(2))%f_node(i)%node%keep) size2 = size2 + 1
end do
end if
else if (obj(2) == DAG_OPTIONS_TK) then
if (allocated (dag%options(i_obj(2))%f_node_ptr1)) then
do i=1, size (dag%options(i_obj(2))%f_node_ptr1)
if (dag%options(i_obj(2))%f_node_ptr1(i)%node%keep) size2 = size2 + 1
end do
end if
end if
!!! make all combinations of daughters
select case (obj(1))
case (DAG_NODE_TK)
select case (obj(2))
case (DAG_NODE_TK)
call combine_all_daughters(dag%node(i_obj(1))%f_node, &
dag%node(i_obj(2))%f_node)
case (DAG_OPTIONS_TK)
call combine_all_daughters(dag%node(i_obj(1))%f_node, &
dag%options(i_obj(2))%f_node_ptr1)
end select
case (DAG_OPTIONS_TK)
select case (obj(2))
case (DAG_NODE_TK)
call combine_all_daughters(dag%options(i_obj(1))%f_node_ptr1, &
dag%node(i_obj(2))%f_node)
case (DAG_OPTIONS_TK)
call combine_all_daughters(dag%options(i_obj(1))%f_node_ptr1, &
dag%options(i_obj(2))%f_node_ptr1)
end select
end select
end if
end if
contains
subroutine combine_all_daughters (daughter1_ptr, daughter2_ptr)
type(f_node_ptr_t), dimension (:), intent(in) :: daughter1_ptr
type(f_node_ptr_t), dimension (:), intent(in) :: daughter2_ptr
integer :: i, j
integer :: pos
new_size = size1*size2
allocate (dag_node%f_node(new_size))
pos = 0
do i = 1, size (daughter1_ptr)
if (daughter1_ptr(i)%node%keep) then
do j = 1, size (daughter2_ptr)
if (daughter2_ptr(j)%node%keep) then
pos = pos + 1
allocate (dag_node%f_node(pos)%node)
dag_node%f_node(pos)%node%particle_label = particle_label
call dag_node%f_node(pos)%node%assign_particle_properties (feyngraph_set)
dag_node%f_node(pos)%node%daughter1 => daughter1_ptr(i)%node
dag_node%f_node(pos)%node%daughter2 => daughter2_ptr(j)%node
dag_node%f_node(pos)%node%n_subtree_nodes = daughter1_ptr(i)%node%n_subtree_nodes &
+ daughter2_ptr(j)%node%n_subtree_nodes + 1
call feyngraph_set%model%match_vertex (daughter1_ptr(i)%node%particle%pdg, &
daughter2_ptr(j)%node%particle%pdg, match)
if (allocated (match)) then
if (any (abs(match) == abs(dag_node%f_node(pos)%node%particle%pdg))) then
dag_node%f_node(pos)%node%keep = .true.
else
dag_node%f_node(pos)%node%keep = .false.
end if
deallocate (match)
else
dag_node%f_node(pos)%node%keep = .false.
end if
end if
end do
end if
end do
end subroutine combine_all_daughters
end subroutine dag_node_make_f_nodes
@ %def dag_node_make_f_nodes
@ In [[dag_options_make_f_nodes_single]]
we obtain all [[f_nodes]] for [[dag_nodes]] which correspond to a
set of rival subtrees or nodes, which is the first possibility for
which [[dag_options]] can appear.
In [[dag_options_make_f_nodes_pair]]
the options are rival pairs ([[daughter1]], [[daughter2]]).
Therefore we have to pass two allocatable arrays of type [[f_node_ptr_t]]
to the subroutine.
<<Cascades2: dag options: TBP>>=
procedure :: make_f_nodes => dag_options_make_f_nodes
<<Cascades2: sub interfaces>>=
module subroutine dag_options_make_f_nodes (dag_options, &
feyngraph_set, dag)
class(dag_options_t), intent(inout) :: dag_options
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(dag_t), intent(inout) :: dag
end subroutine dag_options_make_f_nodes
<<Cascades2: procedures>>=
module subroutine dag_options_make_f_nodes (dag_options, &
feyngraph_set, dag)
class(dag_options_t), intent(inout) :: dag_options
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(dag_t), intent(inout) :: dag
integer, dimension (:), allocatable :: obj, i_obj
integer :: n_obj
integer :: i
integer :: pos
!!! read options
if (allocated (dag_options%f_node_ptr1)) return
n_obj = count ((dag_options%string%t%type == DAG_NODE_TK) .or. &
(dag_options%string%t%type == DAG_OPTIONS_TK) .or. &
(dag_options%string%t%type == DAG_COMBINATION_TK), 1)
allocate (obj(n_obj)); allocate (i_obj(n_obj))
pos = 0
do i = 1, size (dag_options%string%t)
select case (dag_options%string%t(i)%type)
case (DAG_NODE_TK, DAG_OPTIONS_TK, DAG_COMBINATION_TK)
pos = pos + 1
obj(pos) = dag_options%string%t(i)%type
i_obj(pos) = dag_options%string%t(i)%index
end select
end do
if (any (dag_options%string%t%type == DAG_NODE_TK)) then
call dag_options_make_f_nodes_single
else if (any (dag_options%string%t%type == DAG_COMBINATION_TK)) then
call dag_options_make_f_nodes_pair
end if
deallocate (obj, i_obj)
contains
subroutine dag_options_make_f_nodes_single
integer :: i_start, i_end
integer :: n_nodes
n_nodes = 0
do i=1, n_obj
if (allocated (dag%node(i_obj(i))%f_node)) then
n_nodes = n_nodes + size (dag%node(i_obj(i))%f_node)
end if
end do
if (n_nodes /= 0) then
allocate (dag_options%f_node_ptr1 (n_nodes))
i_end = 0
do i = 1, n_obj
if (allocated (dag%node(i_obj(i))%f_node)) then
i_start = i_end + 1
i_end = i_end + size (dag%node(i_obj(i))%f_node)
dag_options%f_node_ptr1(i_start:i_end) = dag%node(i_obj(i))%f_node
end if
end do
end if
end subroutine dag_options_make_f_nodes_single
subroutine dag_options_make_f_nodes_pair
integer :: i_start, i_end
integer :: n_nodes
!!! get f_nodes from each combination
n_nodes = 0
do i=1, n_obj
if (allocated (dag%combination(i_obj(i))%f_node_ptr1)) then
n_nodes = n_nodes + size (dag%combination(i_obj(i))%f_node_ptr1)
end if
end do
if (n_nodes /= 0) then
allocate (dag_options%f_node_ptr1 (n_nodes))
allocate (dag_options%f_node_ptr2 (n_nodes))
i_end = 0
do i=1, n_obj
if (allocated (dag%combination(i_obj(i))%f_node_ptr1)) then
i_start = i_end + 1
i_end = i_end + size (dag%combination(i_obj(i))%f_node_ptr1)
dag_options%f_node_ptr1(i_start:i_end) = dag%combination(i_obj(i))%f_node_ptr1
dag_options%f_node_ptr2(i_start:i_end) = dag%combination(i_obj(i))%f_node_ptr2
end if
end do
end if
end subroutine dag_options_make_f_nodes_pair
end subroutine dag_options_make_f_nodes
@ %def dag_options_make_f_nodes
@ We create all combinations of daughter [[f_nodes]] for a combination.
In the combination each daughter can be either a single [[dag_node]] or
[[dag_options]] which are a set of single [[dag_nodes]]. Therefore, we
first create all possible [[f_nodes]] for daughter1, then all possible
[[f_nodes]] for daughter2. In the end we combine all [[daughter1]] nodes
with all [[daughter2]] nodes.
<<Cascades2: dag combination: TBP>>=
procedure :: make_f_nodes => dag_combination_make_f_nodes
<<Cascades2: sub interfaces>>=
module subroutine dag_combination_make_f_nodes (dag_combination, &
feyngraph_set, dag)
class(dag_combination_t), intent(inout) :: dag_combination
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(dag_t), intent(inout) :: dag
end subroutine dag_combination_make_f_nodes
<<Cascades2: procedures>>=
module subroutine dag_combination_make_f_nodes (dag_combination, &
feyngraph_set, dag)
class(dag_combination_t), intent(inout) :: dag_combination
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(dag_t), intent(inout) :: dag
integer, dimension (2) :: obj, i_obj
integer :: n_obj
integer :: new_size, size1, size2
integer :: i, j, pos
if (allocated (dag_combination%f_node_ptr1)) return
n_obj = 0
do i = 1, size (dag_combination%string%t)
select case (dag_combination%string%t(i)%type)
case (DAG_NODE_TK, DAG_OPTIONS_TK, DAG_COMBINATION_TK)
n_obj = n_obj + 1
if (n_obj > 2) return
obj(n_obj) = dag_combination%string%t(i)%type
i_obj(n_obj) = dag_combination%string%t(i)%index
end select
end do
size1 = 0
size2 = 0
if (obj(1) == DAG_NODE_TK) then
if (allocated (dag%node(i_obj(1))%f_node)) &
size1 = size (dag%node(i_obj(1))%f_node)
else if (obj(1) == DAG_OPTIONS_TK) then
if (allocated (dag%options(i_obj(1))%f_node_ptr1)) &
size1 = size (dag%options(i_obj(1))%f_node_ptr1)
end if
if (obj(2) == DAG_NODE_TK) then
if (allocated (dag%node(i_obj(2))%f_node)) &
size2 = size (dag%node(i_obj(2))%f_node)
else if (obj(2) == DAG_OPTIONS_TK) then
if (allocated (dag%options(i_obj(2))%f_node_ptr1)) &
size2 = size (dag%options(i_obj(2))%f_node_ptr1)
end if
!!! combine the 2 arrays of f_nodes
new_size = size1*size2
if (new_size /= 0) then
allocate (dag_combination%f_node_ptr1 (new_size))
allocate (dag_combination%f_node_ptr2 (new_size))
pos = 0
select case (obj(1))
case (DAG_NODE_TK)
select case (obj(2))
case (DAG_NODE_TK)
do i = 1, size1
do j = 1, size2
pos = pos + 1
dag_combination%f_node_ptr1(pos) = &
dag%node(i_obj(1))%f_node(i)
dag_combination%f_node_ptr2(pos) = &
dag%node(i_obj(2))%f_node(j)
end do
end do
case (DAG_OPTIONS_TK)
do i = 1, size1
do j = 1, size2
pos = pos + 1
dag_combination%f_node_ptr1(pos) = &
dag%node(i_obj(1))%f_node(i)
dag_combination%f_node_ptr2(pos) = &
dag%options(i_obj(2))%f_node_ptr1(j)
end do
end do
end select
case (DAG_OPTIONS_TK)
select case (obj(2))
case (DAG_NODE_TK)
do i = 1, size1
do j = 1, size2
pos = pos + 1
dag_combination%f_node_ptr1(pos) = &
dag%options(i_obj(1))%f_node_ptr1(i)
dag_combination%f_node_ptr2(pos) = &
dag%node(i_obj(2))%f_node(j)
end do
end do
case (DAG_OPTIONS_TK)
do i = 1, size1
do j = 1, size2
pos = pos + 1
dag_combination%f_node_ptr1(pos) = &
dag%options(i_obj(1))%f_node_ptr1(i)
dag_combination%f_node_ptr2(pos) = &
dag%options(i_obj(2))%f_node_ptr1(j)
end do
end do
end select
end select
end if
end subroutine dag_combination_make_f_nodes
@ %def dag_combination_make_f_nodes
@ Here we create the [[feyngraphs]]. After the construction of the
[[dag]] the remaining [[dag_string]] should contain a token for a
single [[dag_node]] which corresponds to the roots of the
[[feyngraphs]]. Therefore we make all [[f_nodes]] for this [[dag_node]]
and create a [[feyngraph]] for each [[f_node]]. Note that only
3-vertices are accepted. All other vertices are rejected. The
starting point is the last dag node which has been added to the list,
since this corresponds to the root of the tree.
Is is important to understand that the structure of feyngraphs is not
the same as the structure of the dag which is read from file, because
for the calculations which are performed in this module we want to
reuse the nodes for the outgoing particles, which means that they
appear only once. In O'Mega's output, it is the first incoming particle
which appears only once and the outgoing particles appear many times. This
transition is incorporated in the subroutines which create [[f_nodes]]
from the different dag objects.
<<Cascades2: dag: TBP>>=
procedure :: make_feyngraphs => dag_make_feyngraphs
<<Cascades2: sub interfaces>>=
module subroutine dag_make_feyngraphs (dag, feyngraph_set)
class(dag_t), intent(inout) :: dag
type(feyngraph_set_t), intent(inout) :: feyngraph_set
end subroutine dag_make_feyngraphs
<<Cascades2: procedures>>=
module subroutine dag_make_feyngraphs (dag, feyngraph_set)
class(dag_t), intent(inout) :: dag
type(feyngraph_set_t), intent(inout) :: feyngraph_set
integer :: i
integer :: max_subtree_size
max_subtree_size = dag%node(dag%n_nodes)%subtree_size
if (allocated (dag%node(dag%n_nodes)%f_node)) then
do i = 1, size (dag%node(dag%n_nodes)%f_node)
if (.not. associated (feyngraph_set%first)) then
allocate (feyngraph_set%last)
feyngraph_set%first => feyngraph_set%last
else
allocate (feyngraph_set%last%next)
feyngraph_set%last => feyngraph_set%last%next
end if
feyngraph_set%last%root => dag%node(dag%n_nodes)%f_node(i)%node
!!! The first particle was correct in the O'Mega parsable DAG output.
!!! It was however changed to its anti-particle in
!!! f_node_assign_particle_properties, which we revert here.
feyngraph_set%last%root%particle => &
feyngraph_set%last%root%particle%anti
feyngraph_set%last%n_nodes = feyngraph_set%last%root%n_subtree_nodes
feyngraph_set%n_graphs = feyngraph_set%n_graphs + 1
end do
feyngraph_set%f_node_list%max_tree_size = feyngraph_set%first%n_nodes
end if
end subroutine dag_make_feyngraphs
@ %def dag_make_feyngraphs
@ A write procedure of the [[dag]] for debugging.
<<Cascades2: dag: TBP>>=
procedure :: write => dag_write
<<Cascades2: sub interfaces>>=
module subroutine dag_write (dag, u)
class(dag_t), intent(in) :: dag
integer, intent(in) :: u
end subroutine dag_write
<<Cascades2: procedures>>=
module subroutine dag_write (dag, u)
class(dag_t), intent(in) :: dag
integer, intent(in) :: u
integer :: i
write (u,fmt='(A)') 'nodes'
do i=1, dag%n_nodes
write (u,fmt='(I5,3X,A)') i, char (dag%node(i)%string)
end do
write (u,fmt='(A)') 'options'
do i=1, dag%n_options
write (u,fmt='(I5,3X,A)') i, char (dag%options(i)%string)
end do
write (u,fmt='(A)') 'combination'
do i=1, dag%n_combinations
write (u,fmt='(I5,3X,A)') i, char (dag%combination(i)%string)
end do
end subroutine dag_write
@ %def dag_write
@ Make a copy of a resonant [[k_node]], where the copy is kept
nonresonant.
<<Cascades2: procedures>>=
subroutine k_node_make_nonresonant_copy (k_node)
type(k_node_t), intent(in) :: k_node
type(k_node_t), pointer :: copy
call k_node%f_node%k_node_list%add_entry (copy, recycle=.true.)
copy%daughter1 => k_node%daughter1
copy%daughter2 => k_node%daughter2
copy = k_node
copy%mapping = NONRESONANT
copy%resonant = .false.
copy%on_shell = .false.
copy%mapping_assigned = .true.
copy%is_nonresonant_copy = .true.
end subroutine k_node_make_nonresonant_copy
@ %def k_node_make_nonresonant_copy
@ For a given [[feyngraph]] we create all possible [[kingraphs]]. Here
we use existing [[k_nodes]] which have already been created when the
mapping calculations of the pure s-channel subgraphs are performed. The
nodes for the incoming particles or the nodes on the t-line will have
to be created in all cases because they are not used in several graphs.
To obtain the existing [[k_nodes]], we use the subroutine
[[k_node_init_from_f_node]] which itself uses [[f_node_list_get_nodes]]
to obtain all active [[k_nodes]] in the [[k_node_list]] of the [[f_node]].
The created [[kingraphs]] are attached to the linked list
of the [[feyngraph]]. For scattering processes we have to split up the
t-line, because since all graphs are represented as a decay, different
nodes can share daughter nodes. This happens also for the t-line or
the incoming particle which appears as an outgoing particle. For the
[[t_line]] or [[incoming]] nodes we do not want to recycle nodes but
rather create a copy of this line for each [[kingraph]].
<<Cascades2: feyngraph: TBP>>=
procedure :: make_kingraphs => feyngraph_make_kingraphs
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_make_kingraphs (feyngraph, feyngraph_set)
class(feyngraph_t), intent(inout) :: feyngraph
type(feyngraph_set_t), intent(in) :: feyngraph_set
end subroutine feyngraph_make_kingraphs
<<Cascades2: procedures>>=
module subroutine feyngraph_make_kingraphs (feyngraph, feyngraph_set)
class(feyngraph_t), intent(inout) :: feyngraph
type(feyngraph_set_t), intent(in) :: feyngraph_set
type(k_node_ptr_t), dimension (:), allocatable :: kingraph_root
integer :: i
if (.not. associated (feyngraph%kin_first)) then
call k_node_init_from_f_node (feyngraph%root, &
kingraph_root, feyngraph_set)
if (.not. feyngraph%root%keep) return
if (feyngraph_set%process_type == SCATTERING) then
call split_up_t_lines (kingraph_root)
end if
do i=1, size (kingraph_root)
if (associated (feyngraph%kin_last)) then
allocate (feyngraph%kin_last%next)
feyngraph%kin_last => feyngraph%kin_last%next
else
allocate (feyngraph%kin_last)
feyngraph%kin_first => feyngraph%kin_last
end if
feyngraph%kin_last%root => kingraph_root(i)%node
feyngraph%kin_last%n_nodes = feyngraph%n_nodes
feyngraph%kin_last%keep = feyngraph%keep
if (feyngraph_set%process_type == SCATTERING) then
feyngraph%kin_last%root%bincode = &
f_node_get_external_bincode (feyngraph_set, feyngraph%root)
end if
end do
deallocate (kingraph_root)
end if
end subroutine feyngraph_make_kingraphs
@ %def feyngraph_make_kingraphs
@ Create all [[k_nodes]] for a given [[f_node]]. We return these nodes
using [[k_node_ptr]]. If the node is external, we assign also the bincode
to the [[k_nodes]] because this is determined from substrings of the
input file which belong to the [[feyngraphs]] and [[f_nodes]].
<<Cascades2: procedures>>=
recursive subroutine k_node_init_from_f_node (f_node, k_node_ptr, feyngraph_set)
type(f_node_t), target, intent(inout) :: f_node
type(k_node_ptr_t), allocatable, dimension (:), intent(out) :: k_node_ptr
type(feyngraph_set_t), intent(in) :: feyngraph_set
type(k_node_ptr_t), allocatable, dimension(:) :: daughter_ptr1, daughter_ptr2
integer :: n_nodes
integer :: i, j
integer :: pos
integer, save :: counter = 0
if (.not. (f_node%incoming .or. f_node%t_line)) then
call f_node%k_node_list%get_nodes (k_node_ptr)
if (.not. allocated (k_node_ptr) .and. f_node%k_node_list%n_entries > 0) then
f_node%keep = .false.
return
end if
end if
if (.not. allocated (k_node_ptr)) then
if (associated (f_node%daughter1) .and. associated (f_node%daughter2)) then
call k_node_init_from_f_node (f_node%daughter1, daughter_ptr1, &
feyngraph_set)
call k_node_init_from_f_node (f_node%daughter2, daughter_ptr2, &
feyngraph_set)
if (.not. (f_node%daughter1%keep .and. f_node%daughter2%keep)) then
f_node%keep = .false.
return
end if
n_nodes = size (daughter_ptr1) * size (daughter_ptr2)
allocate (k_node_ptr (n_nodes))
pos = 1
do i=1, size (daughter_ptr1)
do j=1, size (daughter_ptr2)
if (f_node%incoming .or. f_node%t_line) then
call f_node%k_node_list%add_entry (k_node_ptr(pos)%node, recycle = .false.)
else
call f_node%k_node_list%add_entry (k_node_ptr(pos)%node, recycle = .true.)
end if
k_node_ptr(pos)%node%f_node => f_node
k_node_ptr(pos)%node%daughter1 => daughter_ptr1(i)%node
k_node_ptr(pos)%node%daughter2 => daughter_ptr2(j)%node
k_node_ptr(pos)%node%f_node_index = f_node%index
k_node_ptr(pos)%node%incoming = f_node%incoming
k_node_ptr(pos)%node%t_line = f_node%t_line
k_node_ptr(pos)%node%particle => f_node%particle
pos = pos + 1
end do
end do
deallocate (daughter_ptr1, daughter_ptr2)
else
allocate (k_node_ptr(1))
if (f_node%incoming .or. f_node%t_line) then
call f_node%k_node_list%add_entry (k_node_ptr(1)%node, recycle=.false.)
else
call f_node%k_node_list%add_entry (k_node_ptr(1)%node, recycle=.true.)
end if
k_node_ptr(1)%node%f_node => f_node
k_node_ptr(1)%node%f_node_index = f_node%index
k_node_ptr(1)%node%incoming = f_node%incoming
k_node_ptr(1)%node%t_line = f_node%t_line
k_node_ptr(1)%node%particle => f_node%particle
k_node_ptr(1)%node%bincode = f_node_get_external_bincode (feyngraph_set, &
f_node)
end if
end if
end subroutine k_node_init_from_f_node
@ %def k_node_init_from_f_node
@ The graphs resulting from [[k_node_init_from_f_node]] are fine if they
are used only in one direction. This is however not the case when one
wants to invert the graphs, i.e. take the other incoming particle of a
scattering process as the decaying particle, because the outgoing
[[f_nodes]] (and hence also the [[k_nodes]]) exist only once. This
problem is solved here by creating a distinct t-line for each of the
graphs. The following subroutine disentangles the data structure by
creating new nodes such that the different t-lines are not connected
any more.
<<Cascades2: procedures>>=
recursive subroutine split_up_t_lines (t_node)
type(k_node_ptr_t), dimension(:), intent(inout) :: t_node
type(k_node_t), pointer :: ref_node => null ()
type(k_node_t), pointer :: ref_daughter => null ()
type(k_node_t), pointer :: new_daughter => null ()
type(k_node_ptr_t), dimension(:), allocatable :: t_daughter
integer :: ref_daughter_index
integer :: i, j
allocate (t_daughter (size (t_node)))
do i=1, size (t_node)
ref_node => t_node(i)%node
if (associated (ref_node%daughter1) .and. associated (ref_node%daughter2)) then
ref_daughter => null ()
if (ref_node%daughter1%incoming .or. ref_node%daughter1%t_line) then
ref_daughter => ref_node%daughter1
ref_daughter_index = 1
else if (ref_node%daughter2%incoming .or. ref_node%daughter2%t_line) then
ref_daughter => ref_node%daughter2
ref_daughter_index = 2
end if
do j=1, size (t_daughter)
if (.not. associated (t_daughter(j)%node)) then
t_daughter(j)%node => ref_daughter
exit
else if (t_daughter(j)%node%index == ref_daughter%index) then
new_daughter => null ()
call ref_daughter%f_node%k_node_list%add_entry (new_daughter, recycle=.false.)
new_daughter = ref_daughter
new_daughter%daughter1 => ref_daughter%daughter1
new_daughter%daughter2 => ref_daughter%daughter2
if (ref_daughter_index == 1) then
ref_node%daughter1 => new_daughter
else if (ref_daughter_index == 2) then
ref_node%daughter2 => new_daughter
end if
ref_daughter => new_daughter
end if
end do
else
return
end if
end do
call split_up_t_lines (t_daughter)
deallocate (t_daughter)
end subroutine split_up_t_lines
@ %def split_up_t_lines
@ This subroutine sets the [[inverse_daughters]] of a [[k_node]]. If we
invert a [[kingraph]] such that not the first but the second incoming
particle appears as the root of the tree, the [[incoming]] and [[t_line]]
particles obtain other daughters. These are the former mother node and
the sister node [[s_daughter]]. Here we set only the pointers for
the [[inverse_daughters]]. The inversion happens in [[kingraph_make_inverse_copy]]
and [[node_inverse_deep_copy]].
<<Cascades2: procedures>>=
subroutine kingraph_set_inverse_daughters (kingraph)
type(kingraph_t), intent(inout) :: kingraph
type(k_node_t), pointer :: mother
type(k_node_t), pointer :: t_daughter
type(k_node_t), pointer :: s_daughter
mother => kingraph%root
do while (associated (mother))
if (associated (mother%daughter1) .and. &
associated (mother%daughter2)) then
if (mother%daughter1%t_line .or. mother%daughter1%incoming) then
t_daughter => mother%daughter1; s_daughter => mother%daughter2
else if (mother%daughter2%t_line .or. mother%daughter2%incoming) then
t_daughter => mother%daughter2; s_daughter => mother%daughter1
else
exit
end if
t_daughter%inverse_daughter1 => mother
t_daughter%inverse_daughter2 => s_daughter
mother => t_daughter
else
exit
end if
end do
end subroutine kingraph_set_inverse_daughters
@ %def kingraph_set_inverse_daughters
@ Set the bincode of an [[f_node]] which corresponds to an external
particle. This is done on the basis of the [[particle_label]] which is a
substring of the input file. Here it is not the particle name which is
important, but the number(s) in brackets which in general indicate the
external particles which are connected to the current node. This function
is however only used for external particles, so there can either be
one or [[n_out + 1]] particles in the brackets (in the DAG input file
always one, because also for the root there is only a single number).
In all cases we check the number of particles (in the DAG input the
numbers are separated by a slash).
<<Cascades2: procedures>>=
function f_node_get_external_bincode (feyngraph_set, f_node) result (bincode)
type(feyngraph_set_t), intent(in) :: feyngraph_set
type(f_node_t), intent(in) :: f_node
integer (TC) :: bincode
character(len=LABEL_LEN) :: particle_label
integer :: start_pos, end_pos, n_out_decay
integer :: n_prt ! for DAG
integer :: i
bincode = 0
if (feyngraph_set%process_type == DECAY) then
n_out_decay = feyngraph_set%n_out
else
n_out_decay = feyngraph_set%n_out + 1
end if
particle_label = f_node%particle_label
start_pos = index (particle_label, '[') + 1
end_pos = index (particle_label, ']') - 1
particle_label = particle_label(start_pos:end_pos)
!!! n_out_decay is the number of outgoing particles in the
!!! O'Mega output, which is always represented as a decay
if (feyngraph_set%use_dag) then
n_prt = 1
do i=1, len(particle_label)
if (particle_label(i:i) == '/') n_prt = n_prt + 1
end do
else
n_prt = end_pos - start_pos + 1
end if
if (n_prt == 1) then
bincode = calculate_external_bincode (particle_label, &
feyngraph_set%process_type, n_out_decay)
else if (n_prt == n_out_decay) then
bincode = ibset (0, n_out_decay)
end if
end function f_node_get_external_bincode
@ %def f_node_get_external_bincode
@ Assign a bincode to an internal node, which is calculated from
the bincodes of [[daughter1]] and [[daughter2]].
<<Cascades2: procedures>>=
subroutine node_assign_bincode (node)
type(k_node_t), intent(inout) :: node
if (associated (node%daughter1) .and. associated (node%daughter2) &
.and. .not. node%incoming) then
node%bincode = ior(node%daughter1%bincode, node%daughter2%bincode)
end if
end subroutine node_assign_bincode
@ %def node_assign_bincode
@ Calculate the [[bincode]] from the number in the brackets of the
[[particle_label]], if the node is external. For the root in the
non-factorized output, this is calculated directly in
[[f_node_get_external_bincode]] because in this case all the other
external particle numbers appear between the brackets.
<<Cascades2: procedures>>=
function calculate_external_bincode (label_number_string, process_type, n_out_decay) result (bincode)
character(len=*), intent(in) :: label_number_string
integer, intent(in) :: process_type
integer, intent(in) :: n_out_decay
character :: number_char
integer :: number_int
integer (kind=TC) :: bincode
bincode = 0
read (label_number_string, fmt='(A)') number_char
!!! check if the character is a letter (A,B,C,...) or a number (1...9)
!!! numbers 1 and 2 are special cases
select case (number_char)
case ('1')
if (process_type == SCATTERING) then
number_int = n_out_decay + 3
else
number_int = n_out_decay + 2
end if
case ('2')
if (process_type == SCATTERING) then
number_int = n_out_decay + 2
else
number_int = 2
end if
case ('A')
number_int = 10
case ('B')
number_int = 11
case ('C')
number_int = 12
case ('D')
number_int = 13
case default
read (number_char, fmt='(I1)') number_int
end select
bincode = ibset (bincode, number_int - process_type - 1)
end function calculate_external_bincode
@ %def calculate_external_bincode
@
\subsection{Mapping calculations}
Once a [[k_node]] and its subtree nodes have been created, we can
perform the kinematical calculations and assign mappings, depending on
the particle properties and the results for the subtree nodes. This
could in principle be done recursively, calling the procedure first
for the daughter nodes and then perform the calculations for the actual
node. But for parallization and comparing the nodes, this will be done
simultaneously for all nodes with the same number of subtree nodes, and the number of
subtree nodes increases, starting from one, in steps of two. The
actual mapping calculations are done in complete analogy to cascades.
<<Cascades2: procedures>>=
subroutine node_assign_mapping_s (feyngraph, node, feyngraph_set)
type(feyngraph_t), intent(inout) :: feyngraph
type(k_node_t), intent(inout) :: node
type(feyngraph_set_t), intent(inout) :: feyngraph_set
real(default) :: eff_mass_sum
logical :: keep
if (.not. node%mapping_assigned) then
if (node%particle%mass > feyngraph_set%phs_par%m_threshold_s) then
node%effective_mass = node%particle%mass
end if
if (associated (node%daughter1) .and. associated (node%daughter2)) then
if (.not. (node%daughter1%keep .and. node%daughter2%keep)) then
node%keep = .false.; return
end if
node%ext_mass_sum = node%daughter1%ext_mass_sum &
+ node%daughter2%ext_mass_sum
keep = .false.
!!! Potentially resonant cases [sqrts = m_rea for on-shell decay]
if (node%particle%mass > node%ext_mass_sum &
.and. node%particle%mass <= feyngraph_set%phs_par%sqrts) then
if (node%particle%width /= 0) then
if (node%daughter1%on_shell .or. node%daughter2%on_shell) then
keep = .true.
node%mapping = S_CHANNEL
node%resonant = .true.
end if
else
call warn_decay (node%particle)
end if
!!! Collinear and IR singular cases
else if (node%particle%mass < feyngraph_set%phs_par%sqrts) then
!!! Massless splitting
if (node%daughter1%effective_mass == 0 &
.and. node%daughter2%effective_mass == 0 &
.and. .not. associated (node%daughter1%daughter1) &
.and. .not. associated (node%daughter1%daughter2) &
.and. .not. associated (node%daughter2%daughter1) &
.and. .not. associated (node%daughter2%daughter2)) then
keep = .true.
node%log_enhanced = .true.
if (node%particle%is_vector) then
if (node%daughter1%particle%is_vector &
.and. node%daughter2%particle%is_vector) then
node%mapping = COLLINEAR !!! three-vector-splitting
else
node%mapping = INFRARED !!! vector spliiting into matter
end if
else
if (node%daughter1%particle%is_vector &
.or. node%daughter2%particle%is_vector) then
node%mapping = COLLINEAR !!! vector radiation off matter
else
node%mapping = INFRARED !!! scalar radiation/splitting
end if
end if
!!! IR radiation off massive particle [cascades]
else if (node%effective_mass > 0 .and. &
node%daughter1%effective_mass > 0 .and. &
node%daughter2%effective_mass == 0 .and. &
(node%daughter1%on_shell .or. &
node%daughter1%mapping == RADIATION) .and. &
abs (node%effective_mass - &
node%daughter1%effective_mass) < feyngraph_set%phs_par%m_threshold_s) &
then
keep = .true.
node%log_enhanced = .true.
node%mapping = RADIATION
else if (node%effective_mass > 0 .and. &
node%daughter2%effective_mass > 0 .and. &
node%daughter1%effective_mass == 0 .and. &
(node%daughter2%on_shell .or. &
node%daughter2%mapping == RADIATION) .and. &
abs (node%effective_mass - &
node%daughter2%effective_mass) < feyngraph_set%phs_par%m_threshold_s) &
then
keep = .true.
node%log_enhanced = .true.
node%mapping = RADIATION
end if
end if
!!! Non-singular cases, including failed resonances [from cascades]
if (.not. keep) then
!!! Two on-shell particles from a virtual mother [from cascades, here eventually more than 2]
if (node%daughter1%on_shell .or. node%daughter2%on_shell) then
keep = .true.
eff_mass_sum = node%daughter1%effective_mass &
+ node%daughter2%effective_mass
node%effective_mass = max (node%ext_mass_sum, eff_mass_sum)
if (node%effective_mass < feyngraph_set%phs_par%m_threshold_s) then
node%effective_mass = 0
end if
end if
end if
!!! Complete and register feyngraph (make copy in case of resonance)
if (keep) then
node%on_shell = node%resonant .or. node%log_enhanced
if (node%resonant) then
if (feyngraph_set%phs_par%keep_nonresonant) then
call k_node_make_nonresonant_copy (node)
end if
node%ext_mass_sum = node%particle%mass
end if
end if
node%mapping_assigned = .true.
call node_assign_bincode (node)
call node%subtree%add_entry (node)
else !!! external (outgoing) particle
node%ext_mass_sum = node%particle%mass
node%mapping = EXTERNAL_PRT
node%multiplicity = 1
node%mapping_assigned = .true.
call node%subtree%add_entry (node)
node%on_shell = .true.
if (node%particle%mass >= feyngraph_set%phs_par%m_threshold_s) then
node%effective_mass = node%particle%mass
end if
end if
else if (node%is_nonresonant_copy) then
call node_assign_bincode (node)
call node%subtree%add_entry (node)
node%is_nonresonant_copy = .false.
end if
call node_count_specific_properties (node)
if (node%n_off_shell > feyngraph_set%phs_par%off_shell) then
node%keep = .false.
end if
contains
subroutine warn_decay (particle)
type(part_prop_t), intent(in) :: particle
integer :: i
integer, dimension(MAX_WARN_RESONANCE), save :: warned_code = 0
LOOP_WARNED: do i = 1, MAX_WARN_RESONANCE
if (warned_code(i) == 0) then
warned_code(i) = particle%pdg
write (msg_buffer, "(A)") &
& " Intermediate decay of zero-width particle " &
& // trim(particle%particle_label) &
& // " may be possible."
call msg_warning
exit LOOP_WARNED
else if (warned_code(i) == particle%pdg) then
exit LOOP_WARNED
end if
end do LOOP_WARNED
end subroutine warn_decay
end subroutine node_assign_mapping_s
@ %def node_assign_mapping_s
@ We determine the numbers [[n_resonances]], [[multiplicity]],
[[n_off_shell]] and [[n_log_enhanced]] for a given node.
<<Cascades2: procedures>>=
subroutine node_count_specific_properties (node)
type(k_node_t), intent(inout) :: node
if (associated (node%daughter1) .and. associated(node%daughter2)) then
if (node%resonant) then
node%multiplicity = 1
node%n_resonances &
= node%daughter1%n_resonances &
+ node%daughter2%n_resonances + 1
else
node%multiplicity &
= node%daughter1%multiplicity &
+ node%daughter2%multiplicity
node%n_resonances &
= node%daughter1%n_resonances &
+ node%daughter2%n_resonances
end if
if (node%log_enhanced) then
node%n_log_enhanced &
= node%daughter1%n_log_enhanced &
+ node%daughter2%n_log_enhanced + 1
else
node%n_log_enhanced &
= node%daughter1%n_log_enhanced &
+ node%daughter2%n_log_enhanced
end if
if (node%resonant) then
node%n_off_shell = 0
else if (node%log_enhanced) then
node%n_off_shell &
= node%daughter1%n_off_shell &
+ node%daughter2%n_off_shell
else
node%n_off_shell &
= node%daughter1%n_off_shell &
+ node%daughter2%n_off_shell + 1
end if
if (node%t_line) then
if (node%daughter1%t_line .or. node%daughter1%incoming) then
node%n_t_channel = node%daughter1%n_t_channel + 1
else if (node%daughter2%t_line .or. node%daughter2%incoming) then
node%n_t_channel = node%daughter2%n_t_channel + 1
end if
end if
end if
end subroutine node_count_specific_properties
@ %def node_count_specific_properties
@ The subroutine [[kingraph_assign_mappings_s]] completes kinematical
calculations for a decay process, considering the [[root]] node.
<<Cascades2: procedures>>=
subroutine kingraph_assign_mappings_s (feyngraph, kingraph, feyngraph_set)
type(feyngraph_t), intent(inout) :: feyngraph
type(kingraph_t), pointer, intent(inout) :: kingraph
type(feyngraph_set_t), intent(inout) :: feyngraph_set
if (.not. (kingraph%root%daughter1%keep .and. kingraph%root%daughter2%keep)) then
kingraph%keep = .false.
call kingraph%tree%final ()
end if
if (kingraph%keep) then
kingraph%root%on_shell = .true.
kingraph%root%mapping = EXTERNAL_PRT
kingraph%root%mapping_assigned = .true.
call node_assign_bincode (kingraph%root)
kingraph%root%ext_mass_sum = &
kingraph%root%daughter1%ext_mass_sum + &
kingraph%root%daughter2%ext_mass_sum
if (kingraph%root%ext_mass_sum >= feyngraph_set%phs_par%sqrts) then
kingraph%root%keep = .false.
kingraph%keep = .false.; call kingraph%tree%final (); return
end if
call kingraph%root%subtree%add_entry (kingraph%root)
kingraph%root%multiplicity &
= kingraph%root%daughter1%multiplicity &
+ kingraph%root%daughter2%multiplicity
kingraph%root%n_resonances &
= kingraph%root%daughter1%n_resonances &
+ kingraph%root%daughter2%n_resonances
kingraph%root%n_off_shell &
= kingraph%root%daughter1%n_off_shell &
+ kingraph%root%daughter2%n_off_shell
kingraph%root%n_log_enhanced &
= kingraph%root%daughter1%n_log_enhanced &
+ kingraph%root%daughter2%n_log_enhanced
if (kingraph%root%n_off_shell > feyngraph_set%phs_par%off_shell) then
kingraph%root%keep = .false.
kingraph%keep = .false.; call kingraph%tree%final (); return
else
kingraph%grove_prop%multiplicity = &
kingraph%root%multiplicity
kingraph%grove_prop%n_resonances = &
kingraph%root%n_resonances
kingraph%grove_prop%n_off_shell = &
kingraph%root%n_off_shell
kingraph%grove_prop%n_log_enhanced = &
kingraph%root%n_log_enhanced
end if
kingraph%tree = kingraph%root%subtree
end if
end subroutine kingraph_assign_mappings_s
@ %def kingraph_assign_mappings_s
@ Compute mappings for the [[t_line]] and [[incoming]] nodes. This is
done recursively using [[node_compute_t_line]].
<<Cascades2: procedures>>=
subroutine kingraph_compute_mappings_t_line (feyngraph, kingraph, feyngraph_set)
type(feyngraph_t), intent(inout) :: feyngraph
type(kingraph_t), pointer, intent(inout) :: kingraph
type(feyngraph_set_t), intent(inout) :: feyngraph_set
call node_compute_t_line (feyngraph, kingraph, kingraph%root, feyngraph_set)
if (.not. kingraph%root%keep) then
kingraph%keep = .false.
call kingraph%tree%final ()
end if
if (kingraph%keep) kingraph%tree = kingraph%root%subtree
end subroutine kingraph_compute_mappings_t_line
@ %def kingraph_compute_mappings_t_line
@ Perform the kinematical calculations and mapping assignment for a node
which is either [[incoming]] or [[t_line]]. This is done recursively,
going first to the daughter node which has this property. Therefore we
first set the pointer [[t_node]] to this daughter node and [[s_node]] to
the other one. The mapping determination happens again in the same way as
in [[cascades]].
<<Cascades2: procedures>>=
recursive subroutine node_compute_t_line (feyngraph, kingraph, node, feyngraph_set)
type(feyngraph_t), intent(inout) :: feyngraph
type(kingraph_t), intent(inout) :: kingraph
type(k_node_t), intent(inout) :: node
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(k_node_t), pointer :: s_node
type(k_node_t), pointer :: t_node
type(k_node_t), pointer :: new_s_node
if (.not. (node%daughter1%keep .and. node%daughter2%keep)) then
node%keep = .false.
return
end if
s_node => null ()
t_node => null ()
new_s_node => null ()
if (associated (node%daughter1) .and. associated (node%daughter2)) then
if (node%daughter1%t_line .or. node%daughter1%incoming) then
t_node => node%daughter1; s_node => node%daughter2
else if (node%daughter2%t_line .or. node%daughter2%incoming) then
t_node => node%daughter2; s_node => node%daughter1
end if
if (t_node%t_line) then
call node_compute_t_line (feyngraph, kingraph, t_node, feyngraph_set)
if (.not. t_node%keep) then
node%keep = .false.
return
end if
else if (t_node%incoming) then
t_node%mapping = EXTERNAL_PRT
t_node%on_shell = .true.
t_node%ext_mass_sum = t_node%particle%mass
if (t_node%particle%mass >= feyngraph_set%phs_par%m_threshold_t) then
t_node%effective_mass = t_node%particle%mass
end if
call t_node%subtree%add_entry (t_node)
end if
!!! root:
if (.not. node%incoming) then
if (t_node%incoming) then
node%ext_mass_sum = s_node%ext_mass_sum
else
node%ext_mass_sum &
= node%daughter1%ext_mass_sum &
+ node%daughter2%ext_mass_sum
end if
if (node%particle%mass > feyngraph_set%phs_par%m_threshold_t) then
node%effective_mass = max (node%particle%mass, &
s_node%effective_mass)
else if (s_node%effective_mass > feyngraph_set%phs_par%m_threshold_t) then
node%effective_mass = s_node%effective_mass
else
node%effective_mass = 0
end if
!!! Allowed decay of beam particle
if (t_node%incoming &
.and. t_node%particle%mass > s_node%particle%mass &
+ node%particle%mass) then
call beam_decay (feyngraph_set%fatal_beam_decay)
!!! Massless splitting
else if (t_node%effective_mass == 0 &
.and. s_node%effective_mass < feyngraph_set%phs_par%m_threshold_t &
.and. node%effective_mass == 0) then
node%mapping = U_CHANNEL
node%log_enhanced = .true.
!!! IR radiation off massive particle
else if (t_node%effective_mass /= 0 &
.and. s_node%effective_mass == 0 &
.and. node%effective_mass /= 0 &
.and. (t_node%on_shell &
.or. t_node%mapping == RADIATION) &
.and. abs (t_node%effective_mass - node%effective_mass) &
< feyngraph_set%phs_par%m_threshold_t) then
node%log_enhanced = .true.
node%mapping = RADIATION
end if
node%mapping_assigned = .true.
call node_assign_bincode (node)
call node%subtree%add_entry (node)
call node_count_specific_properties (node)
if (node%n_off_shell > feyngraph_set%phs_par%off_shell) then
node%keep = .false.
kingraph%keep = .false.; call kingraph%tree%final (); return
else if (node%n_t_channel > feyngraph_set%phs_par%t_channel) then
node%keep = .false.;
kingraph%keep = .false.; call kingraph%tree%final (); return
end if
else
node%mapping = EXTERNAL_PRT
node%on_shell = .true.
node%ext_mass_sum &
= t_node%ext_mass_sum &
+ s_node%ext_mass_sum
node%effective_mass = node%particle%mass
if (.not. (node%ext_mass_sum < feyngraph_set%phs_par%sqrts)) then
node%keep = .false.
kingraph%keep = .false.; call kingraph%tree%final (); return
end if
if (kingraph%keep) then
if (t_node%incoming .and. s_node%log_enhanced) then
call s_node%f_node%k_node_list%add_entry (new_s_node, recycle=.false.)
new_s_node = s_node
new_s_node%daughter1 => s_node%daughter1
new_s_node%daughter2 => s_node%daughter2
if (s_node%index == node%daughter1%index) then
node%daughter1 => new_s_node
else if (s_node%index == node%daughter2%index) then
node%daughter2 => new_s_node
end if
new_s_node%subtree = s_node%subtree
new_s_node%mapping = NO_MAPPING
new_s_node%log_enhanced = .false.
new_s_node%n_log_enhanced &
= new_s_node%n_log_enhanced - 1
new_s_node%log_enhanced = .false.
where (new_s_node%subtree%bc == new_s_node%bincode)
new_s_node%subtree%mapping = NO_MAPPING
endwhere
else if ((t_node%t_line .or. t_node%incoming) .and. &
t_node%mapping == U_CHANNEL) then
t_node%mapping = T_CHANNEL
where (t_node%subtree%bc == t_node%bincode)
t_node%subtree%mapping = T_CHANNEL
endwhere
else if (t_node%incoming .and. &
.not. associated (s_node%daughter1) .and. &
.not. associated (s_node%daughter2)) then
call s_node%f_node%k_node_list%add_entry (new_s_node, recycle=.false.)
new_s_node = s_node
new_s_node%mapping = ON_SHELL
new_s_node%daughter1 => s_node%daughter1
new_s_node%daughter2 => s_node%daughter2
new_s_node%subtree = s_node%subtree
if (s_node%index == node%daughter1%index) then
node%daughter1 => new_s_node
else if (s_node%index == node%daughter2%index) then
node%daughter2 => new_s_node
end if
where (new_s_node%subtree%bc == new_s_node%bincode)
new_s_node%subtree%mapping = ON_SHELL
endwhere
end if
end if
call node%subtree%add_entry (node)
node%multiplicity &
= node%daughter1%multiplicity &
+ node%daughter2%multiplicity
node%n_resonances &
= node%daughter1%n_resonances &
+ node%daughter2%n_resonances
node%n_off_shell &
= node%daughter1%n_off_shell &
+ node%daughter2%n_off_shell
node%n_log_enhanced &
= node%daughter1%n_log_enhanced &
+ node%daughter2%n_log_enhanced
node%n_t_channel &
= node%daughter1%n_t_channel &
+ node%daughter2%n_t_channel
if (node%n_off_shell > feyngraph_set%phs_par%off_shell) then
node%keep = .false.
kingraph%keep = .false.; call kingraph%tree%final (); return
else if (node%n_t_channel > feyngraph_set%phs_par%t_channel) then
node%keep = .false.
kingraph%keep = .false.; call kingraph%tree%final (); return
else
kingraph%grove_prop%multiplicity = node%multiplicity
kingraph%grove_prop%n_resonances = node%n_resonances
kingraph%grove_prop%n_off_shell = node%n_off_shell
kingraph%grove_prop%n_log_enhanced = node%n_log_enhanced
kingraph%grove_prop%n_t_channel = node%n_t_channel
end if
end if
end if
contains
subroutine beam_decay (fatal_beam_decay)
logical, intent(in) :: fatal_beam_decay
write (msg_buffer, "(1x,A,1x,'->',1x,A,1x,A)") &
t_node%particle%particle_label, &
node%particle%particle_label, &
s_node%particle%particle_label
call msg_message
write (msg_buffer, "(1x,'mass(',A,') =',1x,E17.10)") &
t_node%particle%particle_label, t_node%particle%mass
call msg_message
write (msg_buffer, "(1x,'mass(',A,') =',1x,E17.10)") &
node%particle%particle_label, node%particle%mass
call msg_message
write (msg_buffer, "(1x,'mass(',A,') =',1x,E17.10)") &
s_node%particle%particle_label, s_node%particle%mass
call msg_message
if (fatal_beam_decay) then
call msg_fatal (" Phase space: Initial beam particle can decay")
else
call msg_warning (" Phase space: Initial beam particle can decay")
end if
end subroutine beam_decay
end subroutine node_compute_t_line
@ %def node_compute_t_line
@ After all pure s-channel subdiagrams have already been created from the
corresponding [[f_nodes]] and mappings have been determined for their
nodes, we complete the calculations here. In a first step, the
[[kingraphs]] have to be created on the basis of the existing
[[k_nodes]], which means in particular that a [[feyngraph]] can give
rise to several [[kingraphs]] which will all be attached to the linked
list of the [[feyngraph]]. The calculations which remain are of different
kinds for decay and scattering processes. In a decay process the
kinematical calculations have to be done for the [[root]] node. In a
scattering process, after the creation of [[kingraphs]] in the first
step, there will be only [[kingraphs]] with the first incoming particle
as the [[root]] of the tree. For these graphs the [[inverse]] variable
has the value [[.false.]]. Before performing any calculations on these
graphs we make a so-called inverse copy of the graph (see below), which
will also be attached to the linked list. Since the s-channel subgraph
calculations have already been completed, only the t-line computations
remain.
<<Cascades2: feyngraph: TBP>>=
procedure :: make_inverse_kingraphs => feyngraph_make_inverse_kingraphs
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_make_inverse_kingraphs (feyngraph)
class(feyngraph_t), intent(inout) :: feyngraph
end subroutine feyngraph_make_inverse_kingraphs
<<Cascades2: procedures>>=
module subroutine feyngraph_make_inverse_kingraphs (feyngraph)
class(feyngraph_t), intent(inout) :: feyngraph
type(kingraph_t), pointer :: current
current => feyngraph%kin_first
do while (associated (current))
if (current%inverse) exit
call current%make_inverse_copy (feyngraph)
current => current%next
end do
end subroutine feyngraph_make_inverse_kingraphs
@ %def feyngraph_make_inverse_kingraphs
<<Cascades2: feyngraph: TBP>>=
procedure :: compute_mappings => feyngraph_compute_mappings
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_compute_mappings (feyngraph, feyngraph_set)
class(feyngraph_t), intent(inout) :: feyngraph
type(feyngraph_set_t), intent(inout) :: feyngraph_set
end subroutine feyngraph_compute_mappings
<<Cascades2: procedures>>=
module subroutine feyngraph_compute_mappings (feyngraph, feyngraph_set)
class(feyngraph_t), intent(inout) :: feyngraph
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(kingraph_t), pointer :: current
current => feyngraph%kin_first
do while (associated (current))
if (feyngraph_set%process_type == DECAY) then
call kingraph_assign_mappings_s (feyngraph, current, feyngraph_set)
else if (feyngraph_set%process_type == SCATTERING) then
call kingraph_compute_mappings_t_line &
(feyngraph, current, feyngraph_set)
end if
current => current%next
end do
end subroutine feyngraph_compute_mappings
@ %def feyngraph_compute_mappings
@ Here we control the mapping calculations for the nodes of s-channel
subgraphs. We start with the nodes with the smallest number of subtree
nodes and always increase this number by two because nodes have exactly
zero or two daughter nodes. We create the [[k_nodes]] using the
[[k_node_list]] of each [[f_node]]. The number of nodes which have to
be created depends of the number of existing daughter nodes, which means
that we have to create a node for each combination of existing and
valid (the ones which we [[keep]]) daughter nodes. If the node
corresponds to an external particle, we create only one node, since
there are no daughter nodes. If the particle is not external and
the daughter [[f_nodes]] do not contain any valid [[k_nodes]], we do
not create a new [[k_nodes]] either. When the calculations for all nodes
with the same number of subtree nodes have been completed, we compare
the valid nodes to eliminate equivalences (see below).
<<Cascades2: procedures>>=
subroutine f_node_list_compute_mappings_s (feyngraph_set)
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(f_node_ptr_t), dimension(:), allocatable :: set
type(k_node_ptr_t), dimension(:), allocatable :: k_set
type(k_node_entry_t), pointer :: k_entry
type(f_node_entry_t), pointer :: current
type(k_node_list_t), allocatable :: compare_list
integer :: n_entries
integer :: pos
integer :: i, j, k
do i = 1, feyngraph_set%f_node_list%max_tree_size - 2, 2
!!! Counter number of f_nodes with subtree size i for s channel calculations
n_entries = 0
if (feyngraph_set%use_dag) then
do j=1, feyngraph_set%dag%n_nodes
if (allocated (feyngraph_set%dag%node(j)%f_node)) then
do k=1, size(feyngraph_set%dag%node(j)%f_node)
if (associated (feyngraph_set%dag%node(j)%f_node(k)%node)) then
if (.not. (feyngraph_set%dag%node(j)%f_node(k)%node%incoming &
.or. feyngraph_set%dag%node(j)%f_node(k)%node%t_line) &
.and. feyngraph_set%dag%node(j)%f_node(k)%node%n_subtree_nodes == i) then
n_entries = n_entries + 1
end if
end if
end do
end if
end do
else
current => feyngraph_set%f_node_list%first
do while (associated (current))
if (.not. (current%node%incoming .or. current%node%t_line) &
.and. current%node%n_subtree_nodes == i) then
n_entries = n_entries + 1
end if
current => current%next
end do
end if
if (n_entries == 0) exit
!!! Create a temporary k node list for comparison
allocate (set(n_entries))
pos = 0
if (feyngraph_set%use_dag) then
do j=1, feyngraph_set%dag%n_nodes
if (allocated (feyngraph_set%dag%node(j)%f_node)) then
do k=1, size(feyngraph_set%dag%node(j)%f_node)
if (associated (feyngraph_set%dag%node(j)%f_node(k)%node)) then
if (.not. (feyngraph_set%dag%node(j)%f_node(k)%node%incoming &
.or. feyngraph_set%dag%node(j)%f_node(k)%node%t_line) &
.and. feyngraph_set%dag%node(j)%f_node(k)%node%n_subtree_nodes == i) then
pos = pos + 1
set(pos)%node => feyngraph_set%dag%node(j)%f_node(k)%node
end if
end if
end do
end if
end do
else
current => feyngraph_set%f_node_list%first
do while (associated (current))
if (.not. (current%node%incoming .or. current%node%t_line) &
.and. current%node%n_subtree_nodes == i) then
pos = pos + 1
set(pos)%node => current%node
end if
current => current%next
end do
end if
allocate (compare_list)
compare_list%observer = .true.
do j = 1, n_entries
call k_node_init_from_f_node (set(j)%node, k_set, &
feyngraph_set)
if (allocated (k_set)) deallocate (k_set)
end do
!$OMP PARALLEL DO PRIVATE (k_entry)
do j = 1, n_entries
k_entry => set(j)%node%k_node_list%first
do while (associated (k_entry))
call node_assign_mapping_s(feyngraph_set%first, k_entry%node, feyngraph_set)
k_entry => k_entry%next
end do
end do
!$OMP END PARALLEL DO
do j = 1, size (set)
k_entry => set(j)%node%k_node_list%first
do while (associated (k_entry))
if (k_entry%node%keep) then
if (k_entry%node%mapping == NO_MAPPING .or. k_entry%node%mapping == NONRESONANT) then
call compare_list%add_pointer (k_entry%node)
end if
end if
k_entry => k_entry%next
end do
end do
deallocate (set)
call compare_list%check_subtree_equivalences(feyngraph_set%model)
call compare_list%final
deallocate (compare_list)
end do
end subroutine f_node_list_compute_mappings_s
@ %def f_node_list_compute_mappings_s
@
\subsection{Fill the grove list}
Find the [[grove]] within the [[grove_list]] for a [[kingraph]] for
which the kinematical calculations and mapping assignments have been completed. The [[groves]]
are defined by the [[grove_prop]] entries and the value of the resonance
hash ([[res_hash]]). Whenever a matching grove does not exist, we
create one. In a first step we consider only part of the grove properties
(see [[grove_prop_match]]) and the resonance hash is ignored, which leads
to a preliminary grove list. In the end all numbers in [[grove_prop]] as
well as the resonance hash are compared, i.e. we create a new
[[grove_list]].
<<Cascades2: grove list: TBP>>=
procedure :: get_grove => grove_list_get_grove
<<Cascades2: sub interfaces>>=
module subroutine grove_list_get_grove (grove_list, kingraph, &
return_grove, preliminary)
class(grove_list_t), intent(inout) :: grove_list
type(kingraph_t), intent(in), pointer :: kingraph
type(grove_t), intent(inout), pointer :: return_grove
logical, intent(in) :: preliminary
end subroutine grove_list_get_grove
<<Cascades2: procedures>>=
module subroutine grove_list_get_grove (grove_list, kingraph, &
return_grove, preliminary)
class(grove_list_t), intent(inout) :: grove_list
type(kingraph_t), intent(in), pointer :: kingraph
type(grove_t), intent(inout), pointer :: return_grove
logical, intent(in) :: preliminary
type(grove_t), pointer :: current_grove
return_grove => null ()
if (.not. associated(grove_list%first)) then
allocate (grove_list%first)
grove_list%first%grove_prop = kingraph%grove_prop
return_grove => grove_list%first
return
end if
current_grove => grove_list%first
do while (associated (current_grove))
if ((preliminary .and. &
(current_grove%grove_prop .match. kingraph%grove_prop)) .or. &
(.not. preliminary .and. &
current_grove%grove_prop == kingraph%grove_prop)) then
return_grove => current_grove
exit
else if (.not. associated (current_grove%next)) then
allocate (current_grove%next)
current_grove%next%grove_prop = kingraph%grove_prop
if (size (kingraph%tree%bc) < 9) &
current_grove%compare_tree%depth = 1
return_grove => current_grove%next
exit
end if
if (associated (current_grove%next)) then
current_grove => current_grove%next
end if
end do
end subroutine grove_list_get_grove
@ %def grove_list_get_grove
@ Add a valid [[kingraph]] to a [[grove_list]]. We first look for the
[[grove]] which has the grove properties of the [[kingraph]]. If no such
[[grove]] exists so far, it is created.
<<Cascades2: grove list: TBP>>=
procedure :: add_kingraph => grove_list_add_kingraph
<<Cascades2: sub interfaces>>=
module subroutine grove_list_add_kingraph (grove_list, kingraph, &
preliminary, check, model)
class(grove_list_t), intent(inout) :: grove_list
type(kingraph_t), pointer, intent(inout) :: kingraph
logical, intent(in) :: preliminary
logical, intent(in) :: check
type(model_data_t), optional, intent(in) :: model
end subroutine grove_list_add_kingraph
<<Cascades2: procedures>>=
module subroutine grove_list_add_kingraph (grove_list, kingraph, &
preliminary, check, model)
class(grove_list_t), intent(inout) :: grove_list
type(kingraph_t), pointer, intent(inout) :: kingraph
logical, intent(in) :: preliminary
logical, intent(in) :: check
type(model_data_t), optional, intent(in) :: model
type(grove_t), pointer :: grove
type(kingraph_t), pointer :: current
integer, save :: index = 0
grove => null ()
current => null ()
if (preliminary) then
if (kingraph%index == 0) then
index = index + 1
kingraph%index = index
end if
end if
call grove_list%get_grove (kingraph, grove, preliminary)
if (check) then
call grove%compare_tree%check_kingraph (kingraph, model, preliminary)
end if
if (kingraph%keep) then
if (associated (grove%first)) then
grove%last%grove_next => kingraph
grove%last => kingraph
else
grove%first => kingraph
grove%last => kingraph
end if
end if
end subroutine grove_list_add_kingraph
@ %ref grove_list_add_kingraph
@ For a given [[feyngraph]] we store all valid [[kingraphs]] in the
[[grove_list]].
<<Cascades2: grove list: TBP>>=
procedure :: add_feyngraph => grove_list_add_feyngraph
<<Cascades2: sub interfaces>>=
module subroutine grove_list_add_feyngraph (grove_list, feyngraph, model)
class(grove_list_t), intent(inout) :: grove_list
type(feyngraph_t), intent(inout) :: feyngraph
type(model_data_t), intent(in) :: model
end subroutine grove_list_add_feyngraph
<<Cascades2: procedures>>=
module subroutine grove_list_add_feyngraph (grove_list, feyngraph, model)
class(grove_list_t), intent(inout) :: grove_list
type(feyngraph_t), intent(inout) :: feyngraph
type(model_data_t), intent(in) :: model
type(kingraph_t), pointer :: current_kingraph, add_kingraph
do while (associated (feyngraph%kin_first))
if (feyngraph%kin_first%keep) then
add_kingraph => feyngraph%kin_first
feyngraph%kin_first => feyngraph%kin_first%next
add_kingraph%next => null ()
call grove_list%add_kingraph (kingraph=add_kingraph, &
preliminary=.true., check=.true., model=model)
else
exit
end if
end do
if (associated (feyngraph%kin_first)) then
current_kingraph => feyngraph%kin_first
do while (associated (current_kingraph%next))
if (current_kingraph%next%keep) then
add_kingraph => current_kingraph%next
current_kingraph%next => current_kingraph%next%next
add_kingraph%next => null ()
call grove_list%add_kingraph (kingraph=add_kingraph, &
preliminary=.true., check=.true., model=model)
else
current_kingraph => current_kingraph%next
end if
end do
end if
end subroutine grove_list_add_feyngraph
@ %def grove_list_add_feyngraph
@ Compare two [[grove_prop]] objects. The [[.match.]] operator is used
for preliminary groves in which the [[kingraphs]] share only the 3
numbers [[n_resonances]], [[n_log_enhanced]] and [[n_t_channel]]. These
groves are only used for comparing the kingraphs, because only graphs
within these preliminary groves can be equivalent (the numbers which are
compared here are unambigously fixed by the combination of mappings in
these channels).
<<Cascades2: interfaces>>=
interface operator (.match.)
module procedure grove_prop_match
end interface operator (.match.)
<<Cascades2: sub interfaces>>=
module function grove_prop_match (grove_prop1, grove_prop2) &
result (gp_match)
type(grove_prop_t), intent(in) :: grove_prop1
type(grove_prop_t), intent(in) :: grove_prop2
logical :: gp_match
end function grove_prop_match
<<Cascades2: procedures>>=
module function grove_prop_match (grove_prop1, grove_prop2) result (gp_match)
type(grove_prop_t), intent(in) :: grove_prop1
type(grove_prop_t), intent(in) :: grove_prop2
logical :: gp_match
gp_match = (grove_prop1%n_resonances == grove_prop2%n_resonances) &
.and. (grove_prop1%n_log_enhanced == grove_prop2%n_log_enhanced) &
.and. (grove_prop1%n_t_channel == grove_prop2%n_t_channel)
end function grove_prop_match
@ %def grove_prop_match
@ The equal operator on the other hand will be used when all valid
[[kingraphs]] have been created and mappings have been determined, to
split up the existing (preliminary) grove list, i.e. to create new
groves which are determined by all entries in [[grove_prop_t]].
<<Cascades2: interfaces>>=
interface operator (==)
module procedure grove_prop_equal
end interface operator (==)
<<Cascades2: sub interfaces>>=
module function grove_prop_equal (grove_prop1, grove_prop2) &
result (gp_equal)
type(grove_prop_t), intent(in) :: grove_prop1
type(grove_prop_t), intent(in) :: grove_prop2
logical :: gp_equal
end function grove_prop_equal
<<Cascades2: procedures>>=
module function grove_prop_equal (grove_prop1, grove_prop2) result (gp_equal)
type(grove_prop_t), intent(in) :: grove_prop1
type(grove_prop_t), intent(in) :: grove_prop2
logical :: gp_equal
gp_equal = (grove_prop1%res_hash == grove_prop2%res_hash) &
.and. (grove_prop1%n_resonances == grove_prop2%n_resonances) &
.and. (grove_prop1%n_log_enhanced == grove_prop2%n_log_enhanced) &
.and. (grove_prop1%n_off_shell == grove_prop2%n_off_shell) &
.and. (grove_prop1%multiplicity == grove_prop2%multiplicity) &
.and. (grove_prop1%n_t_channel == grove_prop2%n_t_channel)
end function grove_prop_equal
@ %def grove_prop_equal
@
\subsection{Remove equivalent channels}
Here we define the equivalence condition for completed [[kingraphs]].
The aim is to keep those [[kingraphs]] which describe the strongest
peaks of the amplitude. The [[bincodes]] and [[mappings]] have to be
the same for an equivalence, but the [[pdgs]] can be different. At
the same time we check if the trees are exacly the same (up to the
sign of pdg codes) in which case we do not keep both of them. This
can be the case when the incoming particles are the same or their
mutual anti-particles and there are no t-channel lines in the
Feynman diagram to which the kingraph belongs.
<<Cascades2: parameters>>=
integer, parameter :: EMPTY = -999
<<Cascades2: procedures>>=
function kingraph_eqv (kingraph1, kingraph2) result (eqv)
type(kingraph_t), intent(in) :: kingraph1
type(kingraph_t), intent(inout) :: kingraph2
logical :: eqv
integer :: i
logical :: equal
eqv = .false.
do i = kingraph1%tree%n_entries, 1, -1
if (kingraph1%tree%bc(i) /= kingraph2%tree%bc(i)) return
end do
do i = kingraph1%tree%n_entries, 1, -1
if ( .not. (kingraph1%tree%mapping(i) == kingraph2%tree%mapping(i) &
.or. ((kingraph1%tree%mapping(i) == NO_MAPPING .or. &
kingraph1%tree%mapping(i) == NONRESONANT) .and. &
(kingraph2%tree%mapping(i) == NO_MAPPING .or. &
kingraph2%tree%mapping(i) == NONRESONANT)))) return
end do
equal = .true.
do i = kingraph1%tree%n_entries, 1, -1
if (abs(kingraph1%tree%pdg(i)) /= abs(kingraph2%tree%pdg(i))) then
equal = .false.;
select case (kingraph1%tree%mapping(i))
case (S_CHANNEL, RADIATION)
select case (kingraph2%tree%mapping(i))
case (S_CHANNEL, RADIATION)
return
end select
end select
end if
end do
if (equal) then
kingraph2%keep = .false.
call kingraph2%tree%final ()
else
eqv = .true.
end if
end function kingraph_eqv
@ %def kingraph_eqv
@ Select between two [[kingraphs]] which fulfill the equivalence
condition above. This is done by comparing the [[pdg]] values of the
[[tree]] for increasing bincode. If the particles are different at
some place, we usually choose the one which would be returned first by the
subroutine [[match_vertex]] of the model for the daughter [[pdg]] codes.
Since we work here only on the basis of the the [[trees]] of the
completed [[kingraphs]], we have to use the [[bc]] array to determine
the positions of the daughter nodes' entries in the array. The graph
which has to be kept should correspond to the stronger peak at the place
which is compared.
<<Cascades2: procedures>>=
subroutine kingraph_select (kingraph1, kingraph2, model, preliminary)
type(kingraph_t), intent(inout) :: kingraph1
type(kingraph_t), intent(inout) :: kingraph2
type(model_data_t), intent(in) :: model
logical, intent(in) :: preliminary
integer(TC), dimension(:), allocatable :: tmp_bc, daughter_bc
integer, dimension(:), allocatable :: tmp_pdg, daughter_pdg
integer, dimension (:), allocatable :: pdg_match
integer :: i, j
integer :: n_ext1, n_ext2
if (kingraph_eqv (kingraph1, kingraph2)) then
if (.not. preliminary) then
kingraph2%keep = .false.; call kingraph2%tree%final ()
return
end if
do i=1, size (kingraph1%tree%bc)
if (abs(kingraph1%tree%pdg(i)) /= abs(kingraph2%tree%pdg(i))) then
if (kingraph1%tree%mapping(i) /= EXTERNAL_PRT) then
n_ext1 = popcnt (kingraph1%tree%bc(i))
n_ext2 = n_ext1
do j=i+1, size (kingraph1%tree%bc)
if (abs(kingraph1%tree%pdg(j)) /= abs(kingraph2%tree%pdg(j))) then
n_ext2 = popcnt (kingraph1%tree%bc(j))
if (n_ext2 < n_ext1) exit
end if
end do
if (n_ext2 < n_ext1) cycle
allocate (tmp_bc(i-1))
tmp_bc = kingraph1%tree%bc(:i-1)
allocate (tmp_pdg(i-1))
tmp_pdg = kingraph1%tree%pdg(:i-1)
do j=i-1, 1, - 1
where (iand (tmp_bc(:j-1),tmp_bc(j)) /= 0 &
.or. iand(tmp_bc(:j-1),kingraph1%tree%bc(i)) == 0)
tmp_bc(:j-1) = 0
tmp_pdg(:j-1) = 0
endwhere
end do
allocate (daughter_bc(size(pack(tmp_bc, tmp_bc /= 0))))
daughter_bc = pack (tmp_bc, tmp_bc /= 0)
allocate (daughter_pdg(size(pack(tmp_pdg, tmp_pdg /= 0))))
daughter_pdg = pack (tmp_pdg, tmp_pdg /= 0)
if (size (daughter_pdg) == 2) then
call model%match_vertex(daughter_pdg(1), daughter_pdg(2), pdg_match)
end if
do j=1, size (pdg_match)
if (abs(pdg_match(j)) == abs(kingraph1%tree%pdg(i))) then
kingraph2%keep = .false.; call kingraph2%tree%final ()
exit
else if (abs(pdg_match(j)) == abs(kingraph2%tree%pdg(i))) then
kingraph1%keep = .false.; call kingraph1%tree%final ()
exit
end if
end do
deallocate (tmp_bc, tmp_pdg, daughter_bc, daughter_pdg, pdg_match)
if (.not. (kingraph1%keep .and. kingraph2%keep)) exit
end if
end if
end do
end if
end subroutine kingraph_select
@ %def kingraph_select
@ At the beginning we do not care about the resonance hash, but only
about part of the grove properties, which is defined in
[[grove_prop_match]]. In these resulting preliminary groves the kingraphs
can be equivalent, i.e. we do not have to compare all graphs with each
other but only all graphs within each of these preliminary groves. In the
end we create a new grove list where the grove properties of the
[[kingraphs]] within a [[grove]] have to be exactly the same and in
addition the groves are distinguished by the resonance hash values. Here
the kingraphs are not compared any more, which means that the number of
channels is not reduced any more.
<<Cascades2: grove list: TBP>>=
procedure :: merge => grove_list_merge
<<Cascades2: sub interfaces>>=
module subroutine grove_list_merge (target_list, grove_list, model, &
prc_component)
class(grove_list_t), intent(inout) :: target_list
type(grove_list_t), intent(inout) :: grove_list
type(model_data_t), intent(in) :: model
integer, intent(in) :: prc_component
end subroutine grove_list_merge
<<Cascades2: procedures>>=
module subroutine grove_list_merge (target_list, grove_list, model, &
prc_component)
class(grove_list_t), intent(inout) :: target_list
type(grove_list_t), intent(inout) :: grove_list
type(model_data_t), intent(in) :: model
integer, intent(in) :: prc_component
type(grove_t), pointer :: current_grove
type(kingraph_t), pointer :: current_graph
current_grove => grove_list%first
do while (associated (current_grove))
do while (associated (current_grove%first))
current_graph => current_grove%first
current_grove%first => current_grove%first%grove_next
current_graph%grove_next => null ()
if (current_graph%keep) then
current_graph%prc_component = prc_component
call target_list%add_kingraph(kingraph=current_graph, &
preliminary=.false., check=.true., model=model)
else
call current_graph%final ()
deallocate (current_graph)
end if
end do
current_grove => current_grove%next
end do
end subroutine grove_list_merge
@ %def grove_list_merge
@ Recreate a grove list where we have different groves for different
resonance hashes.
<<Cascades2: grove list: TBP>>=
procedure :: rebuild => grove_list_rebuild
<<Cascades2: sub interfaces>>=
module subroutine grove_list_rebuild (grove_list)
class(grove_list_t), intent(inout) :: grove_list
end subroutine grove_list_rebuild
<<Cascades2: procedures>>=
module subroutine grove_list_rebuild (grove_list)
class(grove_list_t), intent(inout) :: grove_list
type(grove_list_t) :: tmp_list
type(grove_t), pointer :: current_grove
type(grove_t), pointer :: remove_grove
type(kingraph_t), pointer :: current_graph
type(kingraph_t), pointer :: next_graph
tmp_list%first => grove_list%first
grove_list%first => null ()
current_grove => tmp_list%first
do while (associated (current_grove))
current_graph => current_grove%first
do while (associated (current_graph))
call current_graph%assign_resonance_hash ()
next_graph => current_graph%grove_next
current_graph%grove_next => null ()
if (current_graph%keep) then
call grove_list%add_kingraph (kingraph=current_graph, &
preliminary=.false., check=.false.)
end if
current_graph => next_graph
end do
current_grove => current_grove%next
end do
call tmp_list%final
end subroutine grove_list_rebuild
@ %def grove_list_rebuild
@
\subsection{Write the phase-space file}
The phase-space file is written from the graphs which survive the
calculations and equivalence checks and are in the grove list. It is
written grove by grove. The output should be the same as in the
corresponding procedure [[cascade_set_write_file_format]] of
[[cascades]], up to the order of groves and channels.
<<Cascades2: public>>=
public :: feyngraph_set_write_file_format
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_write_file_format (feyngraph_set, u)
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: u
end subroutine feyngraph_set_write_file_format
<<Cascades2: procedures>>=
module subroutine feyngraph_set_write_file_format (feyngraph_set, u)
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: u
type(grove_t), pointer :: grove
integer :: channel_number
integer :: grove_number
channel_number = 0
grove_number = 0
grove => feyngraph_set%grove_list%first
do while (associated (grove))
grove_number = grove_number + 1
call grove%write_file_format &
(feyngraph_set, grove_number, channel_number, u)
grove => grove%next
end do
end subroutine feyngraph_set_write_file_format
@ %def feyngraph_set_write_file_format
@ Write the relevant information of the [[kingraphs]] of a [[grove]] and
the grove properties in the file format.
<<Cascades2: grove: TBP>>=
procedure :: write_file_format => grove_write_file_format
<<Cascades2: sub interfaces>>=
recursive module subroutine grove_write_file_format &
(grove, feyngraph_set, gr_number, ch_number, u)
class(grove_t), intent(in) :: grove
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: u
integer, intent(inout) :: gr_number
integer, intent(inout) :: ch_number
end subroutine grove_write_file_format
<<Cascades2: procedures>>=
recursive module subroutine grove_write_file_format &
(grove, feyngraph_set, gr_number, ch_number, u)
class(grove_t), intent(in) :: grove
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: u
integer, intent(inout) :: gr_number
integer, intent(inout) :: ch_number
type(kingraph_t), pointer :: current
1 format(3x,A,1x,40(1x,I4))
write (u, "(A)")
write (u, "(1x,'!',1x,A,1x,I0,A)", advance='no') &
'Multiplicity =', grove%grove_prop%multiplicity, ","
select case (grove%grove_prop%n_resonances)
case (0)
write (u, '(1x,A)', advance='no') 'no resonances, '
case (1)
write (u, '(1x,A)', advance='no') '1 resonance, '
case default
write (u, '(1x,I0,1x,A)', advance='no') &
grove%grove_prop%n_resonances, 'resonances, '
end select
write (u, '(1x,I0,1x,A)', advance='no') &
grove%grove_prop%n_log_enhanced, 'logs, '
write (u, '(1x,I0,1x,A)', advance='no') &
grove%grove_prop%n_off_shell, 'off-shell, '
select case (grove%grove_prop%n_t_channel)
case (0); write (u, '(1x,A)') 's-channel graph'
case (1); write (u, '(1x,A)') '1 t-channel line'
case default
write(u,'(1x,I0,1x,A)') &
grove%grove_prop%n_t_channel, 't-channel lines'
end select
write (u, '(1x,A,I0)') 'grove #', gr_number
current => grove%first
do while (associated (current))
if (current%keep) then
ch_number = ch_number + 1
call current%write_file_format (feyngraph_set, ch_number, u)
end if
current => current%grove_next
end do
end subroutine grove_write_file_format
@ %def grove_write_file_format
@ Write the relevant information of a valid [[kingraph]] in the file
format. The information is extracted from the [[tree]].
<<Cascades2: kingraph: TBP>>=
procedure :: write_file_format => kingraph_write_file_format
<<Cascades2: sub interfaces>>=
module subroutine kingraph_write_file_format &
(kingraph, feyngraph_set, ch_number, u)
class(kingraph_t), intent(in) :: kingraph
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: ch_number
integer, intent(in) :: u
end subroutine kingraph_write_file_format
<<Cascades2: procedures>>=
module subroutine kingraph_write_file_format &
(kingraph, feyngraph_set, ch_number, u)
class(kingraph_t), intent(in) :: kingraph
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: ch_number
integer, intent(in) :: u
integer :: i
integer(TC) :: bincode_incoming
2 format(3X,'map',1X,I3,1X,A,1X,I9,1X,'!',1X,A)
!!! determine bincode of incoming particle from tree
bincode_incoming = maxval (kingraph%tree%bc)
write (unit=u, fmt='(1X,A,I0)') '! Channel #', ch_number
write (unit=u, fmt='(3X,A,1X)', advance='no') 'tree'
do i=1, size (kingraph%tree%bc)
if (kingraph%tree%mapping(i) >=0 &
.or. kingraph%tree%mapping(i) == NONRESONANT &
.or. (kingraph%tree%bc(i) == bincode_incoming &
.and. feyngraph_set%process_type == DECAY)) then
write (unit=u, fmt='(1X,I0)', advance='no') kingraph%tree%bc(i)
end if
end do
write (unit=u, fmt='(A)', advance='yes')
do i=1, size(kingraph%tree%bc)
select case (kingraph%tree%mapping(i))
case (NO_MAPPING, NONRESONANT, EXTERNAL_PRT)
case (S_CHANNEL)
write (unit=u, fmt=2) kingraph%tree%bc(i), 's_channel', &
kingraph%tree%pdg(i), &
trim(get_particle_name (feyngraph_set, kingraph%tree%pdg(i)))
case (T_CHANNEL)
write (unit=u, fmt=2) kingraph%tree%bc(i), 't_channel', &
abs (kingraph%tree%pdg(i)), &
trim(get_particle_name (feyngraph_set, abs(kingraph%tree%pdg(i))))
case (U_CHANNEL)
write (unit=u, fmt=2) kingraph%tree%bc(i), 'u_channel', &
abs (kingraph%tree%pdg(i)), &
trim(get_particle_name (feyngraph_set, abs(kingraph%tree%pdg(i))))
case (RADIATION)
write (unit=u, fmt=2) kingraph%tree%bc(i), 'radiation', &
kingraph%tree%pdg(i), &
trim(get_particle_name (feyngraph_set, kingraph%tree%pdg(i)))
case (COLLINEAR)
write (unit=u, fmt=2) kingraph%tree%bc(i), 'collinear', &
kingraph%tree%pdg(i), &
trim(get_particle_name (feyngraph_set, kingraph%tree%pdg(i)))
case (INFRARED)
write (unit=u, fmt=2) kingraph%tree%bc(i), 'infrared ', &
kingraph%tree%pdg(i), &
trim(get_particle_name (feyngraph_set, kingraph%tree%pdg(i)))
case (ON_SHELL)
write (unit=u, fmt=2) kingraph%tree%bc(i), 'on_shell ', &
kingraph%tree%pdg(i), &
trim(get_particle_name (feyngraph_set, kingraph%tree%pdg(i)))
case default
call msg_bug (" Impossible mapping mode encountered")
end select
end do
end subroutine kingraph_write_file_format
@ %def kingraph_write_file_format
@ Get the particle name from the [[particle]] array of the
[[feyngraph_set]]. This is needed for the phs file creation.
<<Cascades2: procedures>>=
function get_particle_name (feyngraph_set, pdg) result (particle_name)
type(feyngraph_set_t), intent(in) :: feyngraph_set
integer, intent(in) :: pdg
character(len=LABEL_LEN) :: particle_name
integer :: i
do i=1, size (feyngraph_set%particle)
if (feyngraph_set%particle(i)%pdg == pdg) then
particle_name = feyngraph_set%particle(i)%particle_label
exit
end if
end do
end function get_particle_name
@ %def get_particle_name
@
\subsection{Invert a graph}
All Feynman diagrams given by O'Mega look like a decay. The [[feyngraph]]
which is constructed from this output also looks like a decay, where one
of the incoming particles is the decaying particle (or the root of the
tree). The calculations can in principle be done on this data structure.
However, it is also performed with the other incoming particle as
the root. The first part of the calculation is the same for both cases.
For the second part we need to transform/turn the graphs such that the
other incoming particle becomes the root. This is done by identifying
the incoming particles from the O'Mega output (the first one is simply
the root of the existing tree, the second contains [2] in the
[[particle_label]]) and the nodes/particles which connect both incoming
particles (here we set [[t_line = .true.]]). At the same time we set the
pointers [[inverse_daughter1]] and [[inverse_daughter2]] for the
corresponding node, which point to the mother node and the other daughter
of the mother node; these will be the daughters of the node in the
inverted [[feyngraph]].
<<Cascades2: feyngraph: TBP>>=
procedure :: make_invertible => feyngraph_make_invertible
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_make_invertible (feyngraph)
class(feyngraph_t), intent(inout) :: feyngraph
end subroutine feyngraph_make_invertible
<<Cascades2: procedures>>=
module subroutine feyngraph_make_invertible (feyngraph)
class(feyngraph_t), intent(inout) :: feyngraph
logical :: t_line_found
feyngraph%root%incoming = .true.
t_line_found = .false.
if (associated (feyngraph%root%daughter1)) then
call f_node_t_line_check (feyngraph%root%daughter1, t_line_found)
if (.not. t_line_found) then
if (associated (feyngraph%root%daughter2)) then
call f_node_t_line_check (feyngraph%root%daughter2, t_line_found)
end if
end if
end if
contains
<<k node t line check>>
end subroutine feyngraph_make_invertible
@ %def feyngraph_make_invertible
@ Check if a node has to be [[t_line]] or [[incoming]] and assign
inverse daughter pointers.
<<k node t line check>>=
recursive subroutine f_node_t_line_check (node, t_line_found)
type(f_node_t), target, intent(inout) :: node
integer :: pos
logical, intent(inout) :: t_line_found
if (associated (node%daughter1)) then
call f_node_t_line_check (node%daughter1, t_line_found)
if (node%daughter1%incoming .or. node%daughter1%t_line) then
node%t_line = .true.
else if (associated (node%daughter2)) then
call f_node_t_line_check (node%daughter2, t_line_found)
if (node%daughter2%incoming .or. node%daughter2%t_line) then
node%t_line = .true.
end if
end if
else
pos = index (node%particle_label, '[') + 1
if (node%particle_label(pos:pos) == '2') then
node%incoming = .true.
t_line_found = .true.
end if
end if
end subroutine f_node_t_line_check
@ %def k_node_t_line_check
@ Make an inverted copy of a [[kingraph]] using the inverse daughter
pointers.
<<Cascades2: kingraph: TBP>>=
procedure :: make_inverse_copy => kingraph_make_inverse_copy
<<Cascades2: sub interfaces>>=
module subroutine kingraph_make_inverse_copy (original_kingraph, feyngraph)
class(kingraph_t), intent(inout) :: original_kingraph
type(feyngraph_t), intent(inout) :: feyngraph
end subroutine kingraph_make_inverse_copy
<<Cascades2: procedures>>=
module subroutine kingraph_make_inverse_copy (original_kingraph, feyngraph)
class(kingraph_t), intent(inout) :: original_kingraph
type(feyngraph_t), intent(inout) :: feyngraph
type(kingraph_t), pointer :: kingraph_copy
type(k_node_t), pointer :: potential_root
allocate (kingraph_copy)
if (associated (feyngraph%kin_last)) then
allocate (feyngraph%kin_last%next)
feyngraph%kin_last => feyngraph%kin_last%next
else
allocate(feyngraph%kin_first)
feyngraph%kin_last => feyngraph%kin_first
end if
kingraph_copy => feyngraph%kin_last
call kingraph_set_inverse_daughters (original_kingraph)
kingraph_copy%inverse = .true.
kingraph_copy%n_nodes = original_kingraph%n_nodes
kingraph_copy%keep = original_kingraph%keep
potential_root => original_kingraph%root
do while (.not. potential_root%incoming .or. &
(associated (potential_root%daughter1) .and. &
associated (potential_root%daughter2)))
if (potential_root%daughter1%incoming .or. &
potential_root%daughter1%t_line) then
potential_root => potential_root%daughter1
else if (potential_root%daughter2%incoming .or. &
potential_root%daughter2%t_line) then
potential_root => potential_root%daughter2
end if
end do
call node_inverse_deep_copy (potential_root, kingraph_copy%root)
end subroutine kingraph_make_inverse_copy
@ %def kingraph_make_inverse_copy
@ Recursively deep-copy nodes, but along the t-line the inverse daughters
become the new daughters. We need a deep copy only for the [[incoming]]
or [[t_line]] nodes. For the other nodes (of s-channel subgraphs) we set
only pointers to the existing nodes of the non-inverted graph.
<<Cascades2: procedures>>=
recursive subroutine node_inverse_deep_copy (original_node, node_copy)
type(k_node_t), intent(in) :: original_node
type(k_node_t), pointer, intent(out) :: node_copy
call original_node%f_node%k_node_list%add_entry(node_copy, recycle=.false.)
node_copy = original_node
if (node_copy%t_line .or. node_copy%incoming) then
node_copy%particle => original_node%particle%anti
else
node_copy%particle => original_node%particle
end if
if (associated (original_node%inverse_daughter1) .and. associated (original_node%inverse_daughter2)) then
if (original_node%inverse_daughter1%incoming .or. original_node%inverse_daughter1%t_line) then
node_copy%daughter2 => original_node%inverse_daughter2
call node_inverse_deep_copy (original_node%inverse_daughter1, &
node_copy%daughter1)
else if (original_node%inverse_daughter2%incoming .or. original_node%inverse_daughter2%t_line) then
node_copy%daughter1 => original_node%inverse_daughter1
call node_inverse_deep_copy (original_node%inverse_daughter2, &
node_copy%daughter2)
end if
end if
end subroutine node_inverse_deep_copy
@ %def node_inverse_deep_copy
@
\subsection{Find phase-space parametrizations}
Perform all mapping calculations for a single process and store valid
[[kingraphs]] (channels) into the grove list, without caring for instance
about the resonance hash values.
<<Cascades2: public>>=
public :: feyngraph_set_generate_single
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_generate_single (feyngraph_set, model, &
n_in, n_out, phs_par, fatal_beam_decay, u_in)
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(model_data_t), target, intent(in) :: model
integer, intent(in) :: n_in, n_out
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
integer, intent(in) :: u_in
end subroutine feyngraph_set_generate_single
<<Cascades2: procedures>>=
module subroutine feyngraph_set_generate_single (feyngraph_set, model, &
n_in, n_out, phs_par, fatal_beam_decay, u_in)
type(feyngraph_set_t), intent(inout) :: feyngraph_set
type(model_data_t), target, intent(in) :: model
integer, intent(in) :: n_in, n_out
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
integer, intent(in) :: u_in
feyngraph_set%n_in = n_in
feyngraph_set%n_out = n_out
feyngraph_set%process_type = n_in
feyngraph_set%phs_par = phs_par
feyngraph_set%model => model
if (debug_on) call msg_debug &
(D_PHASESPACE, "Construct relevant Feynman diagrams from Omega output")
call feyngraph_set%build (u_in)
if (debug_on) call msg_debug &
(D_PHASESPACE, "Find phase-space parametrizations")
call feyngraph_set_find_phs_parametrizations(feyngraph_set)
end subroutine feyngraph_set_generate_single
@ %def feyngraph_set_generate_single
@ Find the phase space parametrizations. We start with the computation
of pure s-channel subtrees, i.e. we determine mappings and compare
subtrees in order to reduce the number of channels. This can be
parallelized easily. When all s-channel [[k_nodes]] exist, the possible
[[kingraphs]] are created using these nodes and we determine mappings for
t-channel nodes.
<<Cascades2: procedures>>=
subroutine feyngraph_set_find_phs_parametrizations (feyngraph_set)
class(feyngraph_set_t), intent(inout) :: feyngraph_set
type(feyngraph_t), pointer :: current => null ()
type(feyngraph_ptr_t), dimension (:), allocatable :: set
integer :: pos
integer :: i
allocate (set (feyngraph_set%n_graphs))
pos = 0
current => feyngraph_set%first
do while (associated (current))
pos = pos + 1
set(pos)%graph => current
current => current%next
end do
if (feyngraph_set%process_type == SCATTERING) then
!$OMP PARALLEL DO
do i=1, feyngraph_set%n_graphs
if (set(i)%graph%keep) then
call set(i)%graph%make_invertible ()
end if
end do
!$OMP END PARALLEL DO
end if
call f_node_list_compute_mappings_s (feyngraph_set)
do i=1, feyngraph_set%n_graphs
if (set(i)%graph%keep) then
call set(i)%graph%make_kingraphs (feyngraph_set)
end if
end do
if (feyngraph_set%process_type == SCATTERING) then
do i=1, feyngraph_set%n_graphs
if (set(i)%graph%keep) then
call set(i)%graph%make_inverse_kingraphs ()
end if
end do
end if
do i=1, feyngraph_set%n_graphs
if (set(i)%graph%keep) then
call set(i)%graph%compute_mappings (feyngraph_set)
end if
end do
do i=1, feyngraph_set%n_graphs
if (set(i)%graph%keep) then
call feyngraph_set%grove_list%add_feyngraph (set(i)%graph, &
feyngraph_set%model)
end if
end do
end subroutine feyngraph_set_find_phs_parametrizations
@ %def feyngraph_set_find_phs_parametrizations
@ Compare objects of type [[tree_t]].
<<Cascades2: interfaces>>=
interface operator (==)
module procedure tree_equal
end interface operator (==)
<<Cascades2: sub interfaces>>=
elemental module function tree_equal (tree1, tree2) result (flag)
type(tree_t), intent(in) :: tree1, tree2
logical :: flag
end function tree_equal
<<Cascades2: procedures>>=
elemental module function tree_equal (tree1, tree2) result (flag)
type(tree_t), intent(in) :: tree1, tree2
logical :: flag
if (tree1%n_entries == tree2%n_entries) then
if (tree1%bc(size(tree1%bc)) == tree2%bc(size(tree2%bc))) then
flag = all (tree1%mapping == tree2%mapping) .and. &
all (tree1%bc == tree2%bc) .and. &
all (abs(tree1%pdg) == abs(tree2%pdg))
else
flag = .false.
end if
else
flag = .false.
end if
end function tree_equal
@ %def tree_equal
@ Select between equivalent subtrees (type [[tree_t]]). This is similar
to [[kingraph_select]], but we compare only positions with mappings
[[NONRESONANT]] and [[NO_MAPPING]].
<<Cascades2: interfaces>>=
interface operator (.eqv.)
module procedure subtree_eqv
end interface operator (.eqv.)
<<Cascades2: sub interfaces>>=
pure module function subtree_eqv (subtree1, subtree2) result (eqv)
type(tree_t), intent(in) :: subtree1, subtree2
logical :: eqv
end function subtree_eqv
<<Cascades2: procedures>>=
pure module function subtree_eqv (subtree1, subtree2) result (eqv)
type(tree_t), intent(in) :: subtree1, subtree2
logical :: eqv
integer :: root_pos
integer :: i
logical :: equal
eqv = .false.
if (subtree1%n_entries /= subtree2%n_entries) return
root_pos = subtree1%n_entries
if (subtree1%mapping(root_pos) == NONRESONANT .or. &
subtree2%mapping(root_pos) == NONRESONANT .or. &
(subtree1%mapping(root_pos) == NO_MAPPING .and. &
subtree2%mapping(root_pos) == NO_MAPPING .and. &
abs(subtree1%pdg(root_pos)) == abs(subtree2%pdg(root_pos)))) then
do i = subtree1%n_entries, 1, -1
if (subtree1%bc(i) /= subtree2%bc(i)) return
end do
equal = .true.
do i = subtree1%n_entries, 1, -1
if (abs(subtree1%pdg(i)) /= abs (subtree2%pdg(i))) then
select case (subtree1%mapping(i))
case (NO_MAPPING, NONRESONANT)
select case (subtree2%mapping(i))
case (NO_MAPPING, NONRESONANT)
equal = .false.
case default
return
end select
case default
return
end select
end if
end do
do i = subtree1%n_entries, 1, -1
if (subtree1%mapping(i) /= subtree2%mapping(i)) then
select case (subtree1%mapping(i))
case (NO_MAPPING, NONRESONANT)
select case (subtree2%mapping(i))
case (NO_MAPPING, NONRESONANT)
case default
return
end select
case default
return
end select
end if
end do
if (.not. equal) eqv = .true.
end if
end function subtree_eqv
@ %def subtree_eqv
<<Cascades2: procedures>>=
subroutine subtree_select (subtree1, subtree2, model)
type(tree_t), intent(inout) :: subtree1, subtree2
type(model_data_t), intent(in) :: model
integer :: j, k
integer(TC), dimension(:), allocatable :: tmp_bc, daughter_bc
integer, dimension(:), allocatable :: tmp_pdg, daughter_pdg
integer, dimension (:), allocatable :: pdg_match
if (subtree1 .eqv. subtree2) then
do j=1, subtree1%n_entries
if (abs(subtree1%pdg(j)) /= abs(subtree2%pdg(j))) then
tmp_bc = subtree1%bc(:j-1); tmp_pdg = subtree1%pdg(:j-1)
do k=j-1, 1, - 1
where (iand (tmp_bc(:k-1),tmp_bc(k)) /= 0 &
.or. iand(tmp_bc(:k-1),subtree1%bc(j)) == 0)
tmp_bc(:k-1) = 0
tmp_pdg(:k-1) = 0
endwhere
end do
daughter_bc = pack (tmp_bc, tmp_bc /= 0)
daughter_pdg = pack (tmp_pdg, tmp_pdg /= 0)
if (size (daughter_pdg) == 2) then
call model%match_vertex(daughter_pdg(1), daughter_pdg(2), pdg_match)
if (.not. allocated (pdg_match)) then
!!! Relevant if tree contains only abs (pdg). In this case, changing the
!!! sign of one of the pdg codes should give a result.
call model%match_vertex(-daughter_pdg(1), daughter_pdg(2), pdg_match)
end if
end if
do k=1, size (pdg_match)
if (abs(pdg_match(k)) == abs(subtree1%pdg(j))) then
if (subtree1%keep) subtree2%keep = .false.
exit
else if (abs(pdg_match(k)) == abs(subtree2%pdg(j))) then
if (subtree2%keep) subtree1%keep = .false.
exit
end if
end do
deallocate (tmp_bc, tmp_pdg, daughter_bc, daughter_pdg, pdg_match)
if (.not. (subtree1%keep .and. subtree2%keep)) exit
end if
end do
end if
end subroutine subtree_select
@ %def subtree_select
@ Assign a resonance hash value to a [[kingraph]], like in [[cascades]],
but here without the array [[tree_resonant]].
<<Cascades2: kingraph: TBP>>=
procedure :: assign_resonance_hash => kingraph_assign_resonance_hash
<<Cascades2: sub interfaces>>=
module subroutine kingraph_assign_resonance_hash (kingraph)
class(kingraph_t), intent(inout) :: kingraph
end subroutine kingraph_assign_resonance_hash
<<Cascades2: procedures>>=
module subroutine kingraph_assign_resonance_hash (kingraph)
class(kingraph_t), intent(inout) :: kingraph
logical, dimension (:), allocatable :: tree_resonant
integer(i8), dimension(1) :: mold
allocate (tree_resonant (kingraph%tree%n_entries))
tree_resonant = (kingraph%tree%mapping == S_CHANNEL)
kingraph%grove_prop%res_hash = hash (transfer &
([sort (pack (kingraph%tree%pdg, tree_resonant)), &
sort (pack (abs (kingraph%tree%pdg), &
kingraph%tree%mapping == T_CHANNEL .or. &
kingraph%tree%mapping == U_CHANNEL))], mold))
deallocate (tree_resonant)
end subroutine kingraph_assign_resonance_hash
@ %def kingraph_assign_resonance_hash
@ Write the process in the bincode format. This is again a copy of the
corresponding procedure in [[cascades]], using [[feyngraph_set]] instead
of [[cascade_set]] as an argument.
<<Cascades2: public>>=
public :: feyngraph_set_write_process_bincode_format
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_write_process_bincode_format &
(feyngraph_set, unit)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
integer, intent(in), optional :: unit
end subroutine feyngraph_set_write_process_bincode_format
<<Cascades2: procedures>>=
module subroutine feyngraph_set_write_process_bincode_format &
(feyngraph_set, unit)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
integer, intent(in), optional :: unit
integer, dimension(:), allocatable :: bincode, field_width
integer :: n_in, n_out, n_tot, n_flv
integer :: u, f, i, bc
character(20) :: str
type(string_t) :: fmt_head
type(string_t), dimension(:), allocatable :: fmt_proc
u = given_output_unit (unit); if (u < 0) return
if (.not. allocated (feyngraph_set%flv)) return
write (u, "('!',1x,A)") "List of subprocesses with particle bincodes:"
n_in = feyngraph_set%n_in
n_out = feyngraph_set%n_out
n_tot = n_in + n_out
n_flv = size (feyngraph_set%flv, 2)
allocate (bincode (n_tot), field_width (n_tot), fmt_proc (n_tot))
bc = 1
do i = 1, n_out
bincode(n_in + i) = bc
bc = 2 * bc
end do
do i = n_in, 1, -1
bincode(i) = bc
bc = 2 * bc
end do
do i = 1, n_tot
write (str, "(I0)") bincode(i)
field_width(i) = len_trim (str)
do f = 1, n_flv
field_width(i) = max (field_width(i), &
len (feyngraph_set%flv(i,f)%get_name ()))
end do
end do
fmt_head = "('!'"
do i = 1, n_tot
fmt_head = fmt_head // ",1x,"
fmt_proc(i) = "(1x,"
write (str, "(I0)") field_width(i)
fmt_head = fmt_head // "I" // trim(str)
fmt_proc(i) = fmt_proc(i) // "A" // trim(str)
if (i == n_in) then
fmt_head = fmt_head // ",1x,' '"
end if
end do
do i = 1, n_tot
fmt_proc(i) = fmt_proc(i) // ")"
end do
fmt_head = fmt_head // ")"
write (u, char (fmt_head)) bincode
do f = 1, n_flv
write (u, "('!')", advance="no")
do i = 1, n_tot
write (u, char (fmt_proc(i)), advance="no") &
char (feyngraph_set%flv(i,f)%get_name ())
if (i == n_in) write (u, "(1x,'=>')", advance="no")
end do
write (u, *)
end do
write (u, char (fmt_head)) bincode
end subroutine feyngraph_set_write_process_bincode_format
@ %def feyngraph_set_write_process_bincode_format
@ Write tex file for graphical display of channels.
<<Cascades2: public>>=
public :: feyngraph_set_write_graph_format
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_write_graph_format &
(feyngraph_set, filename, process_id, unit)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
type(string_t), intent(in) :: filename, process_id
integer, intent(in), optional :: unit
end subroutine feyngraph_set_write_graph_format
<<Cascades2: procedures>>=
module subroutine feyngraph_set_write_graph_format &
(feyngraph_set, filename, process_id, unit)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
type(string_t), intent(in) :: filename, process_id
integer, intent(in), optional :: unit
type(kingraph_t), pointer :: kingraph
type(grove_t), pointer :: grove
integer :: u, n_grove, count, pgcount
logical :: first_in_grove
u = given_output_unit (unit); if (u < 0) return
write (u, '(A)') "\documentclass[10pt]{article}"
write (u, '(A)') "\usepackage{amsmath}"
write (u, '(A)') "\usepackage{feynmp}"
write (u, '(A)') "\usepackage{url}"
write (u, '(A)') "\usepackage{color}"
write (u, *)
write (u, '(A)') "\textwidth 18.5cm"
write (u, '(A)') "\evensidemargin -1.5cm"
write (u, '(A)') "\oddsidemargin -1.5cm"
write (u, *)
write (u, '(A)') "\newcommand{\blue}{\color{blue}}"
write (u, '(A)') "\newcommand{\green}{\color{green}}"
write (u, '(A)') "\newcommand{\red}{\color{red}}"
write (u, '(A)') "\newcommand{\magenta}{\color{magenta}}"
write (u, '(A)') "\newcommand{\cyan}{\color{cyan}}"
write (u, '(A)') "\newcommand{\sm}{\footnotesize}"
write (u, '(A)') "\setlength{\parindent}{0pt}"
write (u, '(A)') "\setlength{\parsep}{20pt}"
write (u, *)
write (u, '(A)') "\begin{document}"
write (u, '(A)') "\begin{fmffile}{" // char (filename) // "}"
write (u, '(A)') "\fmfcmd{color magenta; magenta = red + blue;}"
write (u, '(A)') "\fmfcmd{color cyan; cyan = green + blue;}"
write (u, '(A)') "\begin{fmfshrink}{0.5}"
write (u, '(A)') "\begin{flushleft}"
write (u, *)
write (u, '(A)') "\noindent" // &
& "\textbf{\large\texttt{WHIZARD} phase space channels}" // &
& "\hfill\today"
write (u, *)
write (u, '(A)') "\vspace{10pt}"
write (u, '(A)') "\noindent" // &
& "\textbf{Process:} \url{" // char (process_id) // "}"
call feyngraph_set_write_process_tex_format (feyngraph_set, u)
write (u, *)
write (u, '(A)') "\noindent" // &
& "\textbf{Note:} These are pseudo Feynman graphs that "
write (u, '(A)') "visualize phase-space parameterizations " // &
& "(``integration channels''). "
write (u, '(A)') "They do \emph{not} indicate Feynman graphs used for the " // &
& "matrix element."
write (u, *)
write (u, '(A)') "\textbf{Color code:} " // &
& "{\blue resonance,} " // &
& "{\cyan t-channel,} " // &
& "{\green radiation,} "
write (u, '(A)') "{\red infrared,} " // &
& "{\magenta collinear,} " // &
& "external/off-shell"
write (u, *)
write (u, '(A)') "\noindent" // &
& "\textbf{Black square:} Keystone, indicates ordering of " // &
& "phase space parameters."
write (u, *)
write (u, '(A)') "\vspace{-20pt}"
count = 0
pgcount = 0
n_grove = 0
grove => feyngraph_set%grove_list%first
do while (associated (grove))
n_grove = n_grove + 1
write (u, *)
write (u, '(A)') "\vspace{20pt}"
write (u, '(A)') "\begin{tabular}{l}"
write (u, '(A,I5,A)') &
& "\fbox{\bf Grove \boldmath$", n_grove, "$} \\[10pt]"
write (u, '(A,I1,A)') "Multiplicity: ", &
grove%grove_prop%multiplicity, "\\"
write (u, '(A,I1,A)') "Resonances: ", &
grove%grove_prop%n_resonances, "\\"
write (u, '(A,I1,A)') "Log-enhanced: ", &
grove%grove_prop%n_log_enhanced, "\\"
write (u, '(A,I1,A)') "Off-shell: ", &
grove%grove_prop%n_off_shell, "\\"
write (u, '(A,I1,A)') "t-channel: ", &
grove%grove_prop%n_t_channel, ""
write (u, '(A)') "\end{tabular}"
kingraph => grove%first
do while (associated (kingraph))
count = count + 1
call kingraph_write_graph_format (kingraph, count, unit)
kingraph => kingraph%grove_next
end do
grove => grove%next
end do
write (u, '(A)') "\end{flushleft}"
write (u, '(A)') "\end{fmfshrink}"
write (u, '(A)') "\end{fmffile}"
write (u, '(A)') "\end{document}"
end subroutine feyngraph_set_write_graph_format
@ %def feyngraph_set_write_graph_format
@ Write the process as a \LaTeX\ expression. This is a slightly modified
copy of [[cascade_set_write_process_tex_format]] which has only been
adapted to the types which are used here.
<<Cascades2: procedures>>=
subroutine feyngraph_set_write_process_tex_format (feyngraph_set, unit)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
integer, intent(in), optional :: unit
integer :: n_tot
integer :: u, f, i
n_tot = feyngraph_set%n_in + feyngraph_set%n_out
u = given_output_unit (unit); if (u < 0) return
if (.not. allocated (feyngraph_set%flv)) return
write (u, "(A)") "\begin{align*}"
do f = 1, size (feyngraph_set%flv, 2)
do i = 1, feyngraph_set%n_in
if (i > 1) write (u, "(A)", advance="no") "\quad "
write (u, "(A)", advance="no") &
char (feyngraph_set%flv(i,f)%get_tex_name ())
end do
write (u, "(A)", advance="no") "\quad &\to\quad "
do i = feyngraph_set%n_in + 1, n_tot
if (i > feyngraph_set%n_in + 1) write (u, "(A)", advance="no") "\quad "
write (u, "(A)", advance="no") &
char (feyngraph_set%flv(i,f)%get_tex_name ())
end do
if (f < size (feyngraph_set%flv, 2)) then
write (u, "(A)") "\\"
else
write (u, "(A)") ""
end if
end do
write (u, "(A)") "\end{align*}"
end subroutine feyngraph_set_write_process_tex_format
@ %def feyngraph_set_write_process_tex_format
@ This creates metapost source for graphical display for a given [[kingraph]].
It is the analogon to [[cascade_write_graph_format]] (a modified copy).
<<Cascades2: procedures>>=
subroutine kingraph_write_graph_format (kingraph, count, unit)
type(kingraph_t), intent(in) :: kingraph
integer, intent(in) :: count
integer, intent(in), optional :: unit
integer :: u
type(string_t) :: left_str, right_str
u = given_output_unit (unit); if (u < 0) return
left_str = ""
right_str = ""
write (u, '(A)') "\begin{minipage}{105pt}"
write (u, '(A)') "\vspace{30pt}"
write (u, '(A)') "\begin{center}"
write (u, '(A)') "\begin{fmfgraph*}(55,55)"
call graph_write_node (kingraph%root)
write (u, '(A)') "\fmfleft{" // char (extract (left_str, 2)) // "}"
write (u, '(A)') "\fmfright{" // char (extract (right_str, 2)) // "}"
write (u, '(A)') "\end{fmfgraph*}\\"
write (u, '(A,I5,A)') "\fbox{$", count, "$}"
write (u, '(A)') "\end{center}"
write (u, '(A)') "\end{minipage}"
write (u, '(A)') "%"
contains
recursive subroutine graph_write_node (node)
type(k_node_t), intent(in) :: node
if (associated (node%daughter1) .or. associated (node%daughter2)) then
if (node%daughter2%t_line .or. node%daughter2%incoming) then
call vertex_write (node, node%daughter2)
call vertex_write (node, node%daughter1)
else
call vertex_write (node, node%daughter1)
call vertex_write (node, node%daughter2)
end if
if (node%mapping == EXTERNAL_PRT) then
call line_write (node%bincode, 0, node%particle)
call external_write (node%bincode, node%particle%tex_name, &
left_str)
write (u, '(A,I0,A)') "\fmfv{d.shape=square}{v0}"
end if
else
if (node%incoming) then
call external_write (node%bincode, node%particle%anti%tex_name, &
left_str)
else
call external_write (node%bincode, node%particle%tex_name, &
right_str)
end if
end if
end subroutine graph_write_node
recursive subroutine vertex_write (node, daughter)
type(k_node_t), intent(in) :: node, daughter
integer :: bincode
if (associated (node%daughter1) .and. associated (node%daughter2) &
.and. node%mapping == EXTERNAL_PRT) then
bincode = 0
else
bincode = node%bincode
end if
call graph_write_node (daughter)
if (associated (node%daughter1) .or. associated (node%daughter2)) then
call line_write (bincode, daughter%bincode, daughter%particle, &
mapping=daughter%mapping)
else
call line_write (bincode, daughter%bincode, daughter%particle)
end if
end subroutine vertex_write
subroutine line_write (i1, i2, particle, mapping)
integer(TC), intent(in) :: i1, i2
type(part_prop_t), intent(in) :: particle
integer, intent(in), optional :: mapping
integer :: k1, k2
type(string_t) :: prt_type
select case (particle%spin_type)
case (SCALAR); prt_type = "plain"
case (SPINOR); prt_type = "fermion"
case (VECTOR); prt_type = "boson"
case (VECTORSPINOR); prt_type = "fermion"
case (TENSOR); prt_type = "dbl_wiggly"
case default; prt_type = "dashes"
end select
if (particle%pdg < 0) then
!!! anti-particle
k1 = i2; k2 = i1
else
k1 = i1; k2 = i2
end if
if (present (mapping)) then
select case (mapping)
case (S_CHANNEL)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=blue,lab=\sm\blue$" // &
& char (particle%tex_name) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (T_CHANNEL, U_CHANNEL)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=cyan,lab=\sm\cyan$" // &
& char (particle%tex_name) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (RADIATION)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=green,lab=\sm\green$" // &
& char (particle%tex_name) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (COLLINEAR)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=magenta,lab=\sm\magenta$" // &
& char (particle%tex_name) // "$}" // &
& "{v", k1, ",v", k2, "}"
case (INFRARED)
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=red,lab=\sm\red$" // &
& char (particle%tex_name) // "$}" // &
& "{v", k1, ",v", k2, "}"
case default
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& ",f=black}" // &
& "{v", k1, ",v", k2, "}"
end select
else
write (u, '(A,I0,A,I0,A)') "\fmf{" // char (prt_type) // &
& "}" // &
& "{v", k1, ",v", k2, "}"
end if
end subroutine line_write
subroutine external_write (bincode, name, ext_str)
integer(TC), intent(in) :: bincode
type(string_t), intent(in) :: name
type(string_t), intent(inout) :: ext_str
character(len=20) :: str
write (str, '(A2,I0)') ",v", bincode
ext_str = ext_str // trim (str)
write (u, '(A,I0,A,I0,A)') "\fmflabel{\sm$" &
// char (name) &
// "\,(", bincode, ")" &
// "$}{v", bincode, "}"
end subroutine external_write
end subroutine kingraph_write_graph_format
@ %def kingraph_write_graph_format
@ Generate a [[feyngraph_set]] for several subprocesses. Mapping
calculations are performed separately, but the final grove list is shared
between the subsets [[fset]] of the [[feyngraph_set]].
<<Cascades2: public>>=
public :: feyngraph_set_generate
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_generate &
(feyngraph_set, model, n_in, n_out, flv, phs_par, fatal_beam_decay, &
u_in, vis_channels, use_dag)
type(feyngraph_set_t), intent(out) :: feyngraph_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(flavor_t), dimension(:,:), intent(in) :: flv
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
integer, intent(in) :: u_in
logical, intent(in) :: vis_channels
logical, optional, intent(in) :: use_dag
end subroutine feyngraph_set_generate
<<Cascades2: procedures>>=
module subroutine feyngraph_set_generate &
(feyngraph_set, model, n_in, n_out, flv, phs_par, fatal_beam_decay, &
u_in, vis_channels, use_dag)
type(feyngraph_set_t), intent(out) :: feyngraph_set
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_in, n_out
type(flavor_t), dimension(:,:), intent(in) :: flv
type(phs_parameters_t), intent(in) :: phs_par
logical, intent(in) :: fatal_beam_decay
integer, intent(in) :: u_in
logical, intent(in) :: vis_channels
logical, optional, intent(in) :: use_dag
type(grove_t), pointer :: grove
integer :: i, j
type(kingraph_t), pointer :: kingraph
if (phase_space_vanishes (phs_par%sqrts, n_in, flv)) return
if (present (use_dag)) feyngraph_set%use_dag = use_dag
feyngraph_set%process_type = n_in
feyngraph_set%n_in = n_in
feyngraph_set%n_out = n_out
allocate (feyngraph_set%flv (size (flv, 1), size (flv, 2)))
do i = 1, size (flv, 2)
do j = 1, size (flv, 1)
call feyngraph_set%flv(j,i)%init (flv(j,i)%get_pdg (), model)
end do
end do
allocate (feyngraph_set%particle (PRT_ARRAY_SIZE))
allocate (feyngraph_set%grove_list)
allocate (feyngraph_set%fset (size (flv, 2)))
do i = 1, size (feyngraph_set%fset)
feyngraph_set%fset(i)%use_dag = feyngraph_set%use_dag
allocate (feyngraph_set%fset(i)%flv(size (flv,1),1))
feyngraph_set%fset(i)%flv(:,1) = flv(:,i)
feyngraph_set%fset(i)%particle => feyngraph_set%particle
allocate (feyngraph_set%fset(i)%grove_list)
call feyngraph_set_generate_single (feyngraph_set%fset(i), &
model, n_in, n_out, phs_par, fatal_beam_decay, u_in)
call feyngraph_set%grove_list%merge &
(feyngraph_set%fset(i)%grove_list, model, i)
if (.not. vis_channels) call feyngraph_set%fset(i)%final()
end do
call feyngraph_set%grove_list%rebuild ()
end subroutine feyngraph_set_generate
@ %def feyngraph_set_generate
@ Check whether the [[grove_list]] of the [[feyngraph_set]] contains any
[[kingraphs]] which are valid, i.e. where the [[keep]] variable has the
value [[.true.]]. This is necessary to write a non-empty phase-space
file. The function is the pendant to [[cascade_set_is_valid]].
<<Cascades2: public>>=
public :: feyngraph_set_is_valid
<<Cascades2: sub interfaces>>=
module function feyngraph_set_is_valid (feyngraph_set) result (flag)
class(feyngraph_set_t), intent(in) :: feyngraph_set
logical :: flag
end function feyngraph_set_is_valid
<<Cascades2: procedures>>=
module function feyngraph_set_is_valid (feyngraph_set) result (flag)
class(feyngraph_set_t), intent(in) :: feyngraph_set
type(kingraph_t), pointer :: kingraph
type(grove_t), pointer :: grove
logical :: flag
flag = .false.
if (associated (feyngraph_set%grove_list)) then
grove => feyngraph_set%grove_list%first
do while (associated (grove))
kingraph => grove%first
do while (associated (kingraph))
if (kingraph%keep) then
flag = .true.
return
end if
kingraph => kingraph%next
end do
grove => grove%next
end do
end if
end function feyngraph_set_is_valid
@ %def feyngraph_set_is_valid
@
\subsection{Return the resonance histories for subtraction}
The following procedures are copies of corresponding procedures in
[[cascades]], which only have been adapted to the new types used in
this module.
Extract the resonance set from a valid [[kingraph]] which is kept in the
final grove list.
<<Cascades2: kingraph: TBP>>=
procedure :: extract_resonance_history => kingraph_extract_resonance_history
<<Cascades2: sub interfaces>>=
module subroutine kingraph_extract_resonance_history &
(kingraph, res_hist, model, n_out)
class(kingraph_t), intent(in), target :: kingraph
type(resonance_history_t), intent(out) :: res_hist
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_out
end subroutine kingraph_extract_resonance_history
<<Cascades2: procedures>>=
module subroutine kingraph_extract_resonance_history &
(kingraph, res_hist, model, n_out)
class(kingraph_t), intent(in), target :: kingraph
type(resonance_history_t), intent(out) :: res_hist
class(model_data_t), intent(in), target :: model
integer, intent(in) :: n_out
type(resonance_info_t) :: resonance
integer :: i, mom_id, pdg
if (debug_on) call msg_debug2 &
(D_PHASESPACE, "kingraph_extract_resonance_history")
if (kingraph%grove_prop%n_resonances > 0) then
if (associated (kingraph%root%daughter1) .or. &
associated (kingraph%root%daughter2)) then
if (debug_on) call msg_debug2 &
(D_PHASESPACE, "kingraph has resonances, root has children")
do i = 1, kingraph%tree%n_entries
if (kingraph%tree%mapping(i) == S_CHANNEL) then
mom_id = kingraph%tree%bc (i)
pdg = kingraph%tree%pdg (i)
call resonance%init (mom_id, pdg, model, n_out)
if (debug2_active (D_PHASESPACE)) then
print *, 'D: Adding resonance'
call resonance%write ()
end if
call res_hist%add_resonance (resonance)
end if
end do
end if
end if
end subroutine kingraph_extract_resonance_history
@ %def kingraph_extract_resonance_history
@ Determine the number of valid [[kingraphs]] in [[grove_list]].
<<Cascades2: public>>=
public :: grove_list_get_n_trees
<<Cascades2: sub interfaces>>=
module function grove_list_get_n_trees (grove_list) result (n)
class(grove_list_t), intent(in) :: grove_list
integer :: n
end function grove_list_get_n_trees
<<Cascades2: procedures>>=
module function grove_list_get_n_trees (grove_list) result (n)
class(grove_list_t), intent(in) :: grove_list
integer :: n
type(kingraph_t), pointer :: kingraph
type(grove_t), pointer :: grove
if (debug_on) call msg_debug (D_PHASESPACE, "grove_list_get_n_trees")
n = 0
grove => grove_list%first
do while (associated (grove))
kingraph => grove%first
do while (associated (kingraph))
if (kingraph%keep) n = n + 1
kingraph => kingraph%grove_next
end do
grove => grove%next
end do
if (debug_on) call msg_debug (D_PHASESPACE, "n", n)
end function grove_list_get_n_trees
@ %def grove_list_get_n_trees
@ Extract the resonance histories from the [[feyngraph_set]], in complete
analogy to [[cascade_set_get_resonance_histories]]
<<Cascades2: public>>=
public :: feyngraph_set_get_resonance_histories
<<Cascades2: sub interfaces>>=
module subroutine feyngraph_set_get_resonance_histories &
(feyngraph_set, n_filter, res_hists)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
integer, intent(in), optional :: n_filter
type(resonance_history_t), dimension(:), allocatable, intent(out) :: &
res_hists
end subroutine feyngraph_set_get_resonance_histories
<<Cascades2: procedures>>=
module subroutine feyngraph_set_get_resonance_histories &
(feyngraph_set, n_filter, res_hists)
type(feyngraph_set_t), intent(in), target :: feyngraph_set
integer, intent(in), optional :: n_filter
type(resonance_history_t), dimension(:), allocatable, intent(out) :: &
res_hists
type(kingraph_t), pointer :: kingraph
type(grove_t), pointer :: grove
type(resonance_history_t) :: res_hist
type(resonance_history_set_t) :: res_hist_set
integer :: i_grove
if (debug_on) call msg_debug &
(D_PHASESPACE, "grove_list_get_resonance_histories")
call res_hist_set%init (n_filter = n_filter)
grove => feyngraph_set%grove_list%first
i_grove = 0
do while (associated (grove))
i_grove = i_grove + 1
kingraph => grove%first
do while (associated (kingraph))
if (kingraph%keep) then
if (debug_on) call msg_debug2 (D_PHASESPACE, "grove", i_grove)
call kingraph%extract_resonance_history &
(res_hist, feyngraph_set%model, feyngraph_set%n_out)
call res_hist_set%enter (res_hist)
end if
kingraph => kingraph%grove_next
end do
end do
call res_hist_set%freeze ()
call res_hist_set%to_array (res_hists)
end subroutine feyngraph_set_get_resonance_histories
@ %def feyngraph_set_get_resonance_histories
@
<<[[cascades2_ut.f90]]>>=
<<File header>>
module cascades2_ut
use unit_tests
use cascades2_uti
<<Standard module head>>
<<Cascades2: public test>>
contains
<<Cascades2: test driver>>
end module cascades2_ut
@ %def cascades2_ut
@
<<[[cascades2_uti.f90]]>>=
<<File header>>
module cascades2_uti
<<Use kinds>>
<<Use strings>>
use numeric_utils
use cascades2
use flavors
use phs_forests, only: phs_parameters_t
use model_data
<<Standard module head>>
<<Cascades2: test declarations>>
contains
<<Cascades2: tests>>
end module cascades2_uti
@ %def cascades2_uti
@ API: driver for the unit tests below.
<<Cascades2: public test>>=
public :: cascades2_test
<<Cascades2: test driver>>=
subroutine cascades2_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<Cascades2: execute tests>>
end subroutine cascades2_test
@ %def cascades2_test
@
<<Cascades2: execute tests>>=
call test (cascades2_1, "cascades2_1", &
"make phase-space", u, results)
call test (cascades2_2, "cascades2_2", &
"make phase-space (scattering)", u, results)
<<Cascades2: test declarations>>=
public :: cascades2_1
<<Cascades2: tests>>=
subroutine cascades2_1 (u)
integer, intent(in) :: u
type(feyngraph_set_t) :: feyngraph_set
type(model_data_t) :: model
integer :: n_in = 1
integer :: n_out = 6
type(flavor_t), dimension(7,1) :: flv
type(phs_parameters_t) :: phs_par
logical :: fatal_beam_decay = .true.
integer :: u_in = 8
write (u, "(A)") "* Test output: cascades2_1"
write (u, "(A)") "* Purpose: create a test phs file (decay) with the forest"
write (u, "(A)") "* output of O'Mega"
write (u, "(A)")
write (u, "(A)") "* Initializing"
write (u, "(A)")
call init_sm_full_test (model)
call flv(1,1)%init (6, model)
call flv(2,1)%init (5, model)
call flv(3,1)%init (-11, model)
call flv(4,1)%init (12, model)
call flv(5,1)%init (21, model)
call flv(6,1)%init (22, model)
call flv(7,1)%init (21, model)
phs_par%sqrts = 173.1_default
phs_par%m_threshold_s = 50._default
phs_par%m_threshold_t = 100._default
phs_par%keep_nonresonant = .true.
phs_par%off_shell = 2
open (unit=u_in, file="cascades2_1.fds", status='old', action='read')
write (u, "(A)")
write (u, "(A)") "* Generating phase-space parametrizations"
write (u, "(A)")
call feyngraph_set_generate (feyngraph_set, model, n_in, n_out, &
flv, phs_par, fatal_beam_decay, u_in, use_dag = .false., &
vis_channels = .false.)
call feyngraph_set_write_process_bincode_format (feyngraph_set, u)
call feyngraph_set_write_file_format (feyngraph_set, u)
write (u, "(A)") "* Cleanup"
write (u, "(A)")
close (u_in)
call feyngraph_set%final ()
call model%final ()
write (u, *)
write (u, "(A)") "* Test output end: cascades2_1"
end subroutine cascades2_1
@ %def cascades2_1
@
<<Cascades2: test declarations>>=
public :: cascades2_2
<<Cascades2: tests>>=
subroutine cascades2_2 (u)
integer, intent(in) :: u
type(feyngraph_set_t) :: feyngraph_set
type(model_data_t) :: model
integer :: n_in = 2
integer :: n_out = 5
type(flavor_t), dimension(7,1) :: flv
type(phs_parameters_t) :: phs_par
logical :: fatal_beam_decay = .true.
integer :: u_in = 8
write (u, "(A)") "* Test output: cascades2_2"
write (u, "(A)") "* Purpose: create a test phs file (scattering) with the"
write (u, "(A)") "* parsable DAG output of O'Mega"
write (u, "(A)")
write (u, "(A)") "* Initializing"
write (u, "(A)")
call init_sm_full_test (model)
call flv(1,1)%init (-11, model)
call flv(2,1)%init (11, model)
call flv(3,1)%init (-11, model)
call flv(4,1)%init (12, model)
call flv(5,1)%init (1, model)
call flv(6,1)%init (-2, model)
call flv(7,1)%init (22, model)
phs_par%sqrts = 500._default
phs_par%m_threshold_s = 50._default
phs_par%m_threshold_t = 100._default
phs_par%keep_nonresonant = .true.
phs_par%off_shell = 2
phs_par%t_channel = 6
open (unit=u_in, file="cascades2_2.fds", &
status='old', action='read')
write (u, "(A)")
write (u, "(A)") "* Generating phase-space parametrizations"
write (u, "(A)")
call feyngraph_set_generate (feyngraph_set, model, n_in, n_out, &
flv, phs_par, fatal_beam_decay, u_in, use_dag = .true., &
vis_channels = .false.)
call feyngraph_set_write_process_bincode_format (feyngraph_set, u)
call feyngraph_set_write_file_format (feyngraph_set, u)
write (u, "(A)") "* Cleanup"
write (u, "(A)")
close (u_in)
call feyngraph_set%final ()
call model%final ()
write (u, *)
write (u, "(A)") "* Test output end: cascades2_2"
end subroutine cascades2_2
@ %def cascades2_2
@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{WOOD phase space}
This is the module that interfaces the [[phs_forests]] phase-space
treatment and the [[cascades]] module for generating phase-space
channels. As an extension of the [[phs_base]] abstract type,
the phase-space configuration and instance implement the standard API.
(Currently, this is the only generic phase-space implementation of
\whizard. For trivial two-particle phase space, there is
[[phs_wood]] as an alternative.)
<<[[phs_wood.f90]]>>=
<<File header>>
module phs_wood
<<Use kinds>>
<<Use strings>>
use os_interface
use lorentz
use model_data
use flavors
use phs_base
use mappings
use resonances, only: resonance_history_set_t
use phs_forests
use cascades
use cascades2
<<Standard module head>>
<<PHS wood: public>>
<<PHS wood: types>>
interface
<<PHS wood: sub interfaces>>
end interface
contains
<<PHS wood: main procedures>>
end module phs_wood
@ %def phs_wood
@
<<[[phs_wood_sub.f90]]>>=
<<File header>>
submodule (phs_wood) phs_wood_s
use io_units
use constants
use numeric_utils
use diagnostics
use physics_defs
use md5
use process_constants
use sf_mappings
use sf_base
!!! Intel oneAPI 2022/23 regression workaround
use resonances, only: resonance_history_t
use resonances, only: resonance_history_set_t
implicit none
contains
<<PHS wood: procedures>>
end submodule phs_wood_s
@ %def phs_wood_s
@
\subsection{Configuration}
<<PHS wood: public>>=
public :: phs_wood_config_t
<<PHS wood: types>>=
type, extends (phs_config_t) :: phs_wood_config_t
character(32) :: md5sum_forest = ""
type(string_t) :: phs_path
integer :: io_unit = 0
logical :: io_unit_keep_open = .false.
logical :: use_equivalences = .false.
logical :: fatal_beam_decay = .true.
type(mapping_defaults_t) :: mapping_defaults
type(phs_parameters_t) :: par
type(string_t) :: run_id
type(cascade_set_t), allocatable :: cascade_set
logical :: use_cascades2 = .false.
type(feyngraph_set_t), allocatable :: feyngraph_set
type(phs_forest_t) :: forest
type(os_data_t) :: os_data
logical :: is_combined_integration = .false.
contains
<<PHS wood: phs wood config: TBP>>
end type phs_wood_config_t
@ %def phs_wood_config_t
@ Finalizer. We should delete the cascade set and the forest subobject.
Also close the I/O unit, just in case. (We assume that [[io_unit]] is
not standard input/output.)
<<PHS wood: phs wood config: TBP>>=
procedure :: final => phs_wood_config_final
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_final (object)
class(phs_wood_config_t), intent(inout) :: object
end subroutine phs_wood_config_final
<<PHS wood: procedures>>=
module subroutine phs_wood_config_final (object)
class(phs_wood_config_t), intent(inout) :: object
logical :: opened
if (object%io_unit /= 0) then
inquire (unit = object%io_unit, opened = opened)
if (opened) close (object%io_unit)
end if
call object%clear_phase_space ()
call object%forest%final ()
end subroutine phs_wood_config_final
@ %def phs_wood_config_final
@
<<PHS wood: phs wood config: TBP>>=
procedure :: increase_n_par => phs_wood_config_increase_n_par
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_increase_n_par (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_increase_n_par
<<PHS wood: procedures>>=
module subroutine phs_wood_config_increase_n_par (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
if (phs_config%is_combined_integration) then
phs_config%n_par = phs_config%n_par + 3
end if
end subroutine phs_wood_config_increase_n_par
@ %def phs_wood_config_increase_n_par
@ Output. The contents of the PHS forest are not printed explicitly.
<<PHS wood: phs wood config: TBP>>=
procedure :: write => phs_wood_config_write
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_write (object, unit, include_id)
class(phs_wood_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
end subroutine phs_wood_config_write
<<PHS wood: procedures>>=
module subroutine phs_wood_config_write (object, unit, include_id)
class(phs_wood_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u
u = given_output_unit (unit)
write (u, "(1x,A)") &
"Partonic phase-space configuration (phase-space forest):"
call object%base_write (unit)
write (u, "(1x,A)") "Phase-space configuration parameters:"
call object%par%write (u)
call object%mapping_defaults%write (u)
write (u, "(3x,A,A,A)") "Run ID: '", char (object%run_id), "'"
end subroutine phs_wood_config_write
@ %def phs_wood_config_write
@ Print the PHS forest contents.
<<PHS wood: phs wood config: TBP>>=
procedure :: write_forest => phs_wood_config_write_forest
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_write_forest (object, unit)
class(phs_wood_config_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine phs_wood_config_write_forest
<<PHS wood: procedures>>=
module subroutine phs_wood_config_write_forest (object, unit)
class(phs_wood_config_t), intent(in) :: object
integer, intent(in), optional :: unit
integer :: u
u = given_output_unit (unit)
call object%forest%write (u)
end subroutine phs_wood_config_write_forest
@ %def phs_wood_config_write_forest
@ Set the phase-space parameters that the configuration generator requests.
<<PHS wood: phs wood config: TBP>>=
procedure :: set_parameters => phs_wood_config_set_parameters
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_set_parameters (phs_config, par)
class(phs_wood_config_t), intent(inout) :: phs_config
type(phs_parameters_t), intent(in) :: par
end subroutine phs_wood_config_set_parameters
<<PHS wood: procedures>>=
module subroutine phs_wood_config_set_parameters (phs_config, par)
class(phs_wood_config_t), intent(inout) :: phs_config
type(phs_parameters_t), intent(in) :: par
phs_config%par = par
end subroutine phs_wood_config_set_parameters
@ %def phs_wood_config_set_parameters
@ Enable the generation of channel equivalences (when calling [[configure]]).
<<PHS wood: phs wood config: TBP>>=
procedure :: enable_equivalences => phs_wood_config_enable_equivalences
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_enable_equivalences (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_enable_equivalences
<<PHS wood: procedures>>=
module subroutine phs_wood_config_enable_equivalences (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
phs_config%use_equivalences = .true.
end subroutine phs_wood_config_enable_equivalences
@ %def phs_wood_config_enable_equivalences
@ Set the phase-space mapping parameters that the configuration generator
requests.g
<<PHS wood: phs wood config: TBP>>=
procedure :: set_mapping_defaults => phs_wood_config_set_mapping_defaults
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_set_mapping_defaults &
(phs_config, mapping_defaults)
class(phs_wood_config_t), intent(inout) :: phs_config
type(mapping_defaults_t), intent(in) :: mapping_defaults
end subroutine phs_wood_config_set_mapping_defaults
<<PHS wood: procedures>>=
module subroutine phs_wood_config_set_mapping_defaults &
(phs_config, mapping_defaults)
class(phs_wood_config_t), intent(inout) :: phs_config
type(mapping_defaults_t), intent(in) :: mapping_defaults
phs_config%mapping_defaults = mapping_defaults
end subroutine phs_wood_config_set_mapping_defaults
@ %def phs_wood_config_set_mapping_defaults
@ Define the input stream for the phase-space file as an open logical unit.
The unit must be connected.
<<PHS wood: phs wood config: TBP>>=
procedure :: set_input => phs_wood_config_set_input
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_set_input (phs_config, unit)
class(phs_wood_config_t), intent(inout) :: phs_config
integer, intent(in) :: unit
end subroutine phs_wood_config_set_input
<<PHS wood: procedures>>=
module subroutine phs_wood_config_set_input (phs_config, unit)
class(phs_wood_config_t), intent(inout) :: phs_config
integer, intent(in) :: unit
phs_config%io_unit = unit
rewind (unit)
end subroutine phs_wood_config_set_input
@ %def phs_wood_config_set_input
@
\subsection{Phase-space generation}
This subroutine generates a phase space configuration using the
[[cascades]] module. Note that this may take time, and the
[[cascade_set]] subobject may consume a large amount of memory.
<<PHS wood: phs wood config: TBP>>=
procedure :: generate_phase_space => phs_wood_config_generate_phase_space
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_generate_phase_space (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_generate_phase_space
<<PHS wood: procedures>>=
module subroutine phs_wood_config_generate_phase_space (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
integer :: off_shell, extra_off_shell
logical :: valid
integer :: unit_fds
type(string_t) :: file_name
logical :: file_exists
call msg_message ("Phase space: generating configuration ...")
off_shell = phs_config%par%off_shell
if (phs_config%use_cascades2) then
file_name = char (phs_config%id) // ".fds"
inquire (file=char (file_name), exist=file_exists)
if (.not. file_exists) call msg_fatal &
("The O'Mega input file " // char (file_name) // &
" does not exist. " // "Please make sure that the " // &
"variable ?omega_write_phs_output has been set correctly.")
unit_fds = free_unit ()
open (unit=unit_fds, file=char(file_name), status='old', action='read')
do extra_off_shell = 0, max (phs_config%n_tot - 3, 0)
phs_config%par%off_shell = off_shell + extra_off_shell
allocate (phs_config%feyngraph_set)
call feyngraph_set_generate (phs_config%feyngraph_set, &
phs_config%model, phs_config%n_in, phs_config%n_out, &
phs_config%flv, &
phs_config%par, phs_config%fatal_beam_decay, unit_fds, &
phs_config%vis_channels)
if (feyngraph_set_is_valid (phs_config%feyngraph_set)) then
exit
else
call msg_message ("Phase space: ... failed. &
&Increasing phs_off_shell ...")
call phs_config%feyngraph_set%final ()
deallocate (phs_config%feyngraph_set)
end if
end do
close (unit_fds)
else
allocate (phs_config%cascade_set)
do extra_off_shell = 0, max (phs_config%n_tot - 3, 0)
phs_config%par%off_shell = off_shell + extra_off_shell
call cascade_set_generate (phs_config%cascade_set, &
phs_config%model, phs_config%n_in, phs_config%n_out, &
phs_config%flv, &
phs_config%par, phs_config%fatal_beam_decay)
if (cascade_set_is_valid (phs_config%cascade_set)) then
exit
else
call msg_message ("Phase space: ... failed. &
&Increasing phs_off_shell ...")
end if
end do
end if
if (phs_config%use_cascades2) then
valid = feyngraph_set_is_valid (phs_config%feyngraph_set)
else
valid = cascade_set_is_valid (phs_config%cascade_set)
end if
if (valid) then
call msg_message ("Phase space: ... success.")
else
call msg_fatal ("Phase-space: generation failed")
end if
end subroutine phs_wood_config_generate_phase_space
@ %def phs_wood_config_generate_phase_space
@ Using the generated phase-space configuration, write an appropriate
phase-space file to the stored (or explicitly specified) I/O unit.
<<PHS wood: phs wood config: TBP>>=
procedure :: write_phase_space => phs_wood_config_write_phase_space
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_write_phase_space (phs_config, &
filename_vis, unit)
class(phs_wood_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
type(string_t), intent(in), optional :: filename_vis
end subroutine phs_wood_config_write_phase_space
<<PHS wood: procedures>>=
module subroutine phs_wood_config_write_phase_space (phs_config, &
filename_vis, unit)
class(phs_wood_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
type(string_t), intent(in), optional :: filename_vis
type(string_t) :: setenv_tex, setenv_mp, pipe, pipe_dvi
integer :: u, unit_tex, unit_dev, status
if (allocated (phs_config%cascade_set) .or. &
allocated (phs_config%feyngraph_set)) then
if (present (unit)) then
u = unit
else
u = phs_config%io_unit
end if
write (u, "(1x,A,A)") "process ", char (phs_config%id)
write (u, "(A)")
if (phs_config%use_cascades2) then
call feyngraph_set_write_process_bincode_format (phs_config%feyngraph_set, u)
else
call cascade_set_write_process_bincode_format (phs_config%cascade_set, u)
end if
write (u, "(A)")
write (u, "(3x,A,A,A32,A)") "md5sum_process = ", &
'"', phs_config%md5sum_process, '"'
write (u, "(3x,A,A,A32,A)") "md5sum_model_par = ", &
'"', phs_config%md5sum_model_par, '"'
write (u, "(3x,A,A,A32,A)") "md5sum_phs_config = ", &
'"', phs_config%md5sum_phs_config, '"'
call phs_config%par%write (u)
if (phs_config%use_cascades2) then
call feyngraph_set_write_file_format (phs_config%feyngraph_set, u)
else
call cascade_set_write_file_format (phs_config%cascade_set, u)
end if
if (phs_config%vis_channels) then
unit_tex = free_unit ()
open (unit=unit_tex, file=char(filename_vis // ".tex"), &
action="write", status="replace")
if (phs_config%use_cascades2) then
call feyngraph_set_write_graph_format (phs_config%feyngraph_set, &
filename_vis // "-graphs", phs_config%id, unit_tex)
else
call cascade_set_write_graph_format (phs_config%cascade_set, &
filename_vis // "-graphs", phs_config%id, unit_tex)
end if
close (unit_tex)
call msg_message ("Phase space: visualizing channels in file " &
// char(trim(filename_vis)) // "...")
if (phs_config%os_data%event_analysis_ps) then
BLOCK: do
unit_dev = free_unit ()
open (file = "/dev/null", unit = unit_dev, &
action = "write", iostat = status)
if (status /= 0) then
pipe = ""
pipe_dvi = ""
else
pipe = " > /dev/null"
pipe_dvi = " 2>/dev/null 1>/dev/null"
end if
close (unit_dev)
if (phs_config%os_data%whizard_texpath /= "") then
setenv_tex = "TEXINPUTS=" // &
phs_config%os_data%whizard_texpath // ":$TEXINPUTS "
setenv_mp = "MPINPUTS=" // &
phs_config%os_data%whizard_texpath // ":$MPINPUTS "
else
setenv_tex = ""
setenv_mp = ""
end if
call os_system_call (setenv_tex // &
phs_config%os_data%latex // " " // &
filename_vis // ".tex " // pipe, status)
if (status /= 0) exit BLOCK
if (phs_config%os_data%mpost /= "") then
call os_system_call (setenv_mp // &
phs_config%os_data%mpost // " " // &
filename_vis // "-graphs.mp" // pipe, status)
else
call msg_fatal ("Could not use MetaPOST.")
end if
if (status /= 0) exit BLOCK
call os_system_call (setenv_tex // &
phs_config%os_data%latex // " " // &
filename_vis // ".tex" // pipe, status)
if (status /= 0) exit BLOCK
call os_system_call &
(phs_config%os_data%dvips // " -o " // filename_vis &
// ".ps " // filename_vis // ".dvi" // pipe_dvi, status)
if (status /= 0) exit BLOCK
if (phs_config%os_data%event_analysis_pdf) then
call os_system_call (phs_config%os_data%ps2pdf // " " // &
filename_vis // ".ps", status)
if (status /= 0) exit BLOCK
end if
exit BLOCK
end do BLOCK
if (status /= 0) then
call msg_error ("Unable to compile analysis output file")
end if
end if
end if
else
call msg_fatal ("Phase-space configuration: &
&no phase space object generated")
end if
end subroutine phs_wood_config_write_phase_space
@ %def phs_config_write_phase_space
@ Clear the phase-space configuration. This is useful since the
object may become \emph{really} large.
<<PHS wood: phs wood config: TBP>>=
procedure :: clear_phase_space => phs_wood_config_clear_phase_space
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_clear_phase_space (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_clear_phase_space
<<PHS wood: procedures>>=
module subroutine phs_wood_config_clear_phase_space (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
if (allocated (phs_config%cascade_set)) then
call cascade_set_final (phs_config%cascade_set)
deallocate (phs_config%cascade_set)
end if
if (allocated (phs_config%feyngraph_set)) then
call phs_config%feyngraph_set%final ()
deallocate (phs_config%feyngraph_set)
end if
end subroutine phs_wood_config_clear_phase_space
@ %def phs_wood_config_clear_phase_space
@
Extract the set of resonance histories
<<PHS wood: phs wood config: TBP>>=
procedure :: extract_resonance_history_set &
=> phs_wood_config_extract_resonance_history_set
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_extract_resonance_history_set &
(phs_config, res_set, include_trivial)
class(phs_wood_config_t), intent(in) :: phs_config
type(resonance_history_set_t), intent(out) :: res_set
logical, intent(in), optional :: include_trivial
end subroutine phs_wood_config_extract_resonance_history_set
<<PHS wood: procedures>>=
module subroutine phs_wood_config_extract_resonance_history_set &
(phs_config, res_set, include_trivial)
class(phs_wood_config_t), intent(in) :: phs_config
type(resonance_history_set_t), intent(out) :: res_set
logical, intent(in), optional :: include_trivial
call phs_config%forest%extract_resonance_history_set &
(res_set, include_trivial)
end subroutine phs_wood_config_extract_resonance_history_set
@ %def phs_wood_config_extract_resonance_history_set
@
\subsection{Phase-space configuration}
We read the phase-space configuration from the stored I/O unit. If
this is not set, we assume that we have to generate a phase space
configuration. When done, we open a scratch file and write the
configuration.
If [[rebuild]] is set, we should trash any existing phase space file
and build a new one. Otherwise, we try to use an old one, which we
check for existence and integrity. If [[ignore_mismatch]] is set, we
reuse an existing file even if it does not match the current setup.
<<PHS wood: phs wood config: TBP>>=
procedure :: configure => phs_wood_config_configure
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_wood_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_wood_config_configure
<<PHS wood: procedures>>=
module subroutine phs_wood_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_wood_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
type(string_t) :: filename, filename_vis
logical :: variable_limits
logical :: ok, exist, found, check, match, rebuild_phs
integer :: g, c0, c1, n
if (present (nlo_type)) then
phs_config%nlo_type = nlo_type
else
phs_config%nlo_type = BORN
end if
phs_config%sqrts = sqrts
phs_config%par%sqrts = sqrts
if (present (sqrts_fixed)) &
phs_config%sqrts_fixed = sqrts_fixed
if (present (lab_is_cm)) &
phs_config%lab_is_cm = lab_is_cm
if (present (azimuthal_dependence)) &
phs_config%azimuthal_dependence = azimuthal_dependence
if (present (rebuild)) then
rebuild_phs = rebuild
else
rebuild_phs = .true.
end if
if (present (ignore_mismatch)) then
check = .not. ignore_mismatch
if (ignore_mismatch) &
call msg_warning ("Reading phs file: MD5 sum check disabled")
else
check = .true.
end if
phs_config%md5sum_forest = ""
call phs_config%compute_md5sum (include_id = .false.)
if (phs_config%io_unit == 0) then
filename = phs_config%make_phs_filename (subdir)
filename_vis = phs_config%make_phs_filename (subdir) // "-vis"
if (.not. rebuild_phs) then
if (check) then
call phs_config%read_phs_file (exist, found, match, subdir=subdir)
rebuild_phs = .not. (exist .and. found .and. match)
else
call phs_config%read_phs_file (exist, found, subdir=subdir)
rebuild_phs = .not. (exist .and. found)
end if
end if
if (.not. mpi_is_comm_master ()) then
rebuild_phs = .false.
call msg_message ("MPI: Workers do not build phase space configuration.")
end if
if (rebuild_phs) then
call phs_config%generate_phase_space ()
phs_config%io_unit = free_unit ()
if (phs_config%id /= "") then
call msg_message ("Phase space: writing configuration file '" &
// char (filename) // "'")
open (phs_config%io_unit, file = char (filename), &
status = "replace", action = "readwrite")
else
open (phs_config%io_unit, status = "scratch", action = "readwrite")
end if
call phs_config%write_phase_space (filename_vis)
rewind (phs_config%io_unit)
else
call msg_message ("Phase space: keeping configuration file '" &
// char (filename) // "'")
end if
end if
if (phs_config%io_unit == 0) then
ok = .true.
else
call phs_config%forest%read (phs_config%io_unit, phs_config%id, &
phs_config%n_in, phs_config%n_out, phs_config%model, ok)
if (.not. phs_config%io_unit_keep_open) then
close (phs_config%io_unit)
phs_config%io_unit = 0
end if
end if
if (ok) then
call phs_config%forest%set_flavors (phs_config%flv(:,1))
variable_limits = .not. phs_config%lab_is_cm
call phs_config%forest%set_parameters (phs_config%mapping_defaults, &
variable_limits)
call phs_config%forest%setup_prt_combinations ()
phs_config%n_channel = phs_config%forest%get_n_channels ()
phs_config%n_par = phs_config%forest%get_n_parameters ()
allocate (phs_config%channel (phs_config%n_channel))
if (phs_config%use_equivalences) then
call phs_config%forest%set_equivalences ()
call phs_config%forest%get_equivalences (phs_config%channel, &
phs_config%azimuthal_dependence)
phs_config%provides_equivalences = .true.
end if
call phs_config%forest%set_s_mappings ()
call phs_config%record_on_shell ()
if (phs_config%mapping_defaults%enable_s_mapping) then
call phs_config%record_s_mappings ()
end if
allocate (phs_config%chain (phs_config%n_channel), source = 0)
do g = 1, phs_config%forest%get_n_groves ()
call phs_config%forest%get_grove_bounds (g, c0, c1, n)
phs_config%chain (c0:c1) = g
end do
phs_config%provides_chains = .true.
call phs_config%compute_md5sum_forest ()
else
write (msg_buffer, "(A,A,A)") &
"Phase space: process '", &
char (phs_config%id), "' not found in configuration file"
call msg_fatal ()
end if
end subroutine phs_wood_config_configure
@ %def phs_wood_config_configure
@ The MD5 sum of the forest is computed in addition to the MD5 sum of
the configuration. The reason is that the forest may depend on a
user-provided external file. On the other hand, this MD5 sum encodes
all information that is relevant for further processing. Therefore,
the [[get_md5sum]] method returns this result, once it is available.
<<PHS wood: phs wood config: TBP>>=
procedure :: compute_md5sum_forest => phs_wood_config_compute_md5sum_forest
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_compute_md5sum_forest (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_compute_md5sum_forest
<<PHS wood: procedures>>=
module subroutine phs_wood_config_compute_md5sum_forest (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
integer :: u
u = free_unit ()
open (u, status = "scratch", action = "readwrite")
call phs_config%write_forest (u)
rewind (u)
phs_config%md5sum_forest = md5sum (u)
close (u)
end subroutine phs_wood_config_compute_md5sum_forest
@ %def phs_wood_config_compute_md5sum_forest
@ Create filenames according to standard conventions. The [[id]] is the
process name including the suffix [[_iX]] where [[X]] stands for the component
identifier (an integer). The [[run_id]] may be set or unset.
The convention for file names that include the run ID is to separate prefix, run
ID, and any extensions by dots. We construct the file name by concatenating
the individual elements accordingly. If there is no run ID, we nevertheless
replace [[_iX]] by [[.iX]].
<<PHS wood: phs wood config: TBP>>=
procedure :: make_phs_filename => phs_wood_make_phs_filename
<<PHS wood: sub interfaces>>=
module function phs_wood_make_phs_filename &
(phs_config, subdir) result (filename)
class(phs_wood_config_t), intent(in) :: phs_config
type(string_t), intent(in), optional :: subdir
type(string_t) :: filename
end function phs_wood_make_phs_filename
<<PHS wood: procedures>>=
module function phs_wood_make_phs_filename &
(phs_config, subdir) result (filename)
class(phs_wood_config_t), intent(in) :: phs_config
type(string_t), intent(in), optional :: subdir
type(string_t) :: filename
type(string_t) :: basename, suffix, comp_code, comp_index
basename = phs_config%id
call split (basename, suffix, "_", back=.true.)
comp_code = extract (suffix, 1, 1)
comp_index = extract (suffix, 2)
if (comp_code == "i" .and. verify (comp_index, "1234567890") == 0) then
suffix = "." // comp_code // comp_index
else
basename = phs_config%id
suffix = ""
end if
if (phs_config%run_id /= "") then
filename = basename // "." // phs_config%run_id // suffix // ".phs"
else
filename = basename // suffix // ".phs"
end if
if (present (subdir)) then
filename = subdir // "/" // filename
end if
end function phs_wood_make_phs_filename
@ %def phs_wood_make_phs_filename
@
<<PHS wood: phs wood config: TBP>>=
procedure :: reshuffle_flavors => phs_wood_config_reshuffle_flavors
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_reshuffle_flavors &
(phs_config, reshuffle, flv_extra)
class(phs_wood_config_t), intent(inout) :: phs_config
integer, intent(in), dimension(:), allocatable :: reshuffle
type(flavor_t), intent(in) :: flv_extra
end subroutine phs_wood_config_reshuffle_flavors
<<PHS wood: procedures>>=
module subroutine phs_wood_config_reshuffle_flavors &
(phs_config, reshuffle, flv_extra)
class(phs_wood_config_t), intent(inout) :: phs_config
integer, intent(in), dimension(:), allocatable :: reshuffle
type(flavor_t), intent(in) :: flv_extra
call phs_config%forest%set_flavors (phs_config%flv(:,1), reshuffle, &
flv_extra)
end subroutine phs_wood_config_reshuffle_flavors
@ %def phs_wood_config_reshuffle_flavors
@
<<PHS wood: phs wood config: TBP>>=
procedure :: set_momentum_links => phs_wood_config_set_momentum_links
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_set_momentum_links (phs_config, reshuffle)
class(phs_wood_config_t), intent(inout) :: phs_config
integer, intent(in), dimension(:), allocatable :: reshuffle
end subroutine phs_wood_config_set_momentum_links
<<PHS wood: procedures>>=
module subroutine phs_wood_config_set_momentum_links (phs_config, reshuffle)
class(phs_wood_config_t), intent(inout) :: phs_config
integer, intent(in), dimension(:), allocatable :: reshuffle
call phs_config%forest%set_momentum_links (reshuffle)
end subroutine phs_wood_config_set_momentum_links
@ %def phs_wood_config_set_momentum_links
@ Identify resonances which are marked by s-channel mappings for the
whole phase space and report them to the channel array.
<<PHS wood: phs wood config: TBP>>=
procedure :: record_s_mappings => phs_wood_config_record_s_mappings
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_record_s_mappings (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_record_s_mappings
<<PHS wood: procedures>>=
module subroutine phs_wood_config_record_s_mappings (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
logical :: flag
real(default) :: mass, width
integer :: c
do c = 1, phs_config%n_channel
call phs_config%forest%get_s_mapping (c, flag, mass, width)
if (flag) then
if (mass == 0) then
call msg_fatal ("Phase space: s-channel resonance " &
// " has zero mass")
end if
if (width == 0) then
call msg_fatal ("Phase space: s-channel resonance " &
// " has zero width")
end if
call phs_config%channel(c)%set_resonant (mass, width)
end if
end do
end subroutine phs_wood_config_record_s_mappings
@ %def phs_wood_config_record_s_mappings
@ Identify on-shell mappings for the whole phase space and report them
to the channel array.
<<PHS wood: phs wood config: TBP>>=
procedure :: record_on_shell => phs_wood_config_record_on_shell
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_record_on_shell (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
end subroutine phs_wood_config_record_on_shell
<<PHS wood: procedures>>=
module subroutine phs_wood_config_record_on_shell (phs_config)
class(phs_wood_config_t), intent(inout) :: phs_config
logical :: flag
real(default) :: mass
integer :: c
do c = 1, phs_config%n_channel
call phs_config%forest%get_on_shell (c, flag, mass)
if (flag) then
call phs_config%channel(c)%set_on_shell (mass)
end if
end do
end subroutine phs_wood_config_record_on_shell
@ %def phs_wood_config_record_on_shell
@ Return the most relevant MD5 sum. This overrides the method of the
base type.
<<PHS wood: phs wood config: TBP>>=
procedure :: get_md5sum => phs_wood_config_get_md5sum
<<PHS wood: sub interfaces>>=
module function phs_wood_config_get_md5sum (phs_config) result (md5sum)
class(phs_wood_config_t), intent(in) :: phs_config
character(32) :: md5sum
end function phs_wood_config_get_md5sum
<<PHS wood: procedures>>=
module function phs_wood_config_get_md5sum (phs_config) result (md5sum)
class(phs_wood_config_t), intent(in) :: phs_config
character(32) :: md5sum
if (phs_config%md5sum_forest /= "") then
md5sum = phs_config%md5sum_forest
else
md5sum = phs_config%md5sum_phs_config
end if
end function phs_wood_config_get_md5sum
@ %def phs_wood_config_get_md5sum
@ Check whether a phase-space configuration for the current process exists.
We look for the phase-space file that should correspond to the current
process. If we find it, we check the MD5 sums stored in the file against the
MD5 sums in the current configuration (if required).
If successful, read the PHS file.
<<PHS wood: phs wood config: TBP>>=
procedure :: read_phs_file => phs_wood_read_phs_file
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_read_phs_file &
(phs_config, exist, found, match, subdir)
class(phs_wood_config_t), intent(inout) :: phs_config
logical, intent(out) :: exist
logical, intent(out) :: found
logical, intent(out), optional :: match
type(string_t), intent(in), optional :: subdir
end subroutine phs_wood_read_phs_file
<<PHS wood: procedures>>=
module subroutine phs_wood_read_phs_file &
(phs_config, exist, found, match, subdir)
class(phs_wood_config_t), intent(inout) :: phs_config
logical, intent(out) :: exist
logical, intent(out) :: found
logical, intent(out), optional :: match
type(string_t), intent(in), optional :: subdir
type(string_t) :: filename
integer :: u
filename = phs_config%make_phs_filename (subdir)
inquire (file = char (filename), exist = exist)
if (exist) then
u = free_unit ()
open (u, file = char (filename), action = "read", status = "old")
call phs_config%forest%read (u, phs_config%id, phs_config%n_in, &
phs_config%n_out, phs_config%model, found, &
phs_config%md5sum_process, phs_config%md5sum_model_par, &
phs_config%md5sum_phs_config, match = match)
close (u)
else
found = .false.
if (present (match)) match = .false.
end if
end subroutine phs_wood_read_phs_file
@ %def phs_wood_read_phs_file
@ Startup message, after configuration is complete.
<<PHS wood: phs wood config: TBP>>=
procedure :: startup_message => phs_wood_config_startup_message
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_config_startup_message (phs_config, unit)
class(phs_wood_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
end subroutine phs_wood_config_startup_message
<<PHS wood: procedures>>=
module subroutine phs_wood_config_startup_message (phs_config, unit)
class(phs_wood_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
integer :: n_groves, n_eq
n_groves = phs_config%forest%get_n_groves ()
n_eq = phs_config%forest%get_n_equivalences ()
call phs_config%base_startup_message (unit)
if (phs_config%n_channel == 1) then
write (msg_buffer, "(A,2(I0,A))") &
"Phase space: found ", phs_config%n_channel, &
" channel, collected in ", n_groves, &
" grove."
else if (n_groves == 1) then
write (msg_buffer, "(A,2(I0,A))") &
"Phase space: found ", phs_config%n_channel, &
" channels, collected in ", n_groves, &
" grove."
else
write (msg_buffer, "(A,2(I0,A))") &
"Phase space: found ", phs_config%n_channel, &
" channels, collected in ", n_groves, &
" groves."
end if
call msg_message (unit = unit)
if (phs_config%use_equivalences) then
if (n_eq == 1) then
write (msg_buffer, "(A,I0,A)") &
"Phase space: Using ", n_eq, &
" equivalence between channels."
else
write (msg_buffer, "(A,I0,A)") &
"Phase space: Using ", n_eq, &
" equivalences between channels."
end if
else
write (msg_buffer, "(A)") &
"Phase space: no equivalences between channels used."
end if
call msg_message (unit = unit)
write (msg_buffer, "(A,2(1x,I0,1x,A))") &
"Phase space: wood"
call msg_message (unit = unit)
end subroutine phs_wood_config_startup_message
@ %def phs_wood_config_startup_message
@ Allocate an instance: the actual phase-space object.
Gfortran 7/8/9 bug, has to remain in the main module.
<<PHS wood: phs wood config: TBP>>=
procedure, nopass :: allocate_instance => phs_wood_config_allocate_instance
<<PHS wood: main procedures>>=
subroutine phs_wood_config_allocate_instance (phs)
class(phs_t), intent(inout), pointer :: phs
allocate (phs_wood_t :: phs)
end subroutine phs_wood_config_allocate_instance
@ %def phs_wood_config_allocate_instance
@
\subsection{Kinematics implementation}
We generate $\cos\theta$ and $\phi$ uniformly, covering the solid angle.
<<PHS wood: public>>=
public :: phs_wood_t
<<PHS wood: types>>=
type, extends (phs_t) :: phs_wood_t
real(default) :: sqrts = 0
type(phs_forest_t) :: forest
real(default), dimension(3) :: r_real
integer :: n_r_born = 0
contains
<<PHS wood: phs wood: TBP>>
end type phs_wood_t
@ %def phs_wood_t
@ Output. The [[verbose]] setting is irrelevant, we just display the contents
of the base object.
<<PHS wood: phs wood: TBP>>=
procedure :: write => phs_wood_write
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_write (object, unit, verbose)
class(phs_wood_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine phs_wood_write
<<PHS wood: procedures>>=
module subroutine phs_wood_write (object, unit, verbose)
class(phs_wood_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u
u = given_output_unit (unit)
call object%base_write (u)
end subroutine phs_wood_write
@ %def phs_wood_write
@ Write the forest separately.
<<PHS wood: phs wood: TBP>>=
procedure :: write_forest => phs_wood_write_forest
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_write_forest (object, unit)
class(phs_wood_t), intent(in) :: object
integer, intent(in), optional :: unit
end subroutine phs_wood_write_forest
<<PHS wood: procedures>>=
module subroutine phs_wood_write_forest (object, unit)
class(phs_wood_t), intent(in) :: object
integer, intent(in), optional :: unit
integer :: u
u = given_output_unit (unit)
call object%forest%write (u)
end subroutine phs_wood_write_forest
@ %def phs_wood_write_forest
@ Finalizer.
<<PHS wood: phs wood: TBP>>=
procedure :: final => phs_wood_final
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_final (object)
class(phs_wood_t), intent(inout) :: object
end subroutine phs_wood_final
<<PHS wood: procedures>>=
module subroutine phs_wood_final (object)
class(phs_wood_t), intent(inout) :: object
call object%forest%final ()
end subroutine phs_wood_final
@ %def phs_wood_final
@ Initialization. We allocate arrays ([[base_init]]) and adjust the
phase-space volume. The two-particle phase space volume is
\begin{equation}
\Phi_2 = \frac{1}{4(2\pi)^5} = 2.55294034614 \times 10^{-5}
\end{equation}
independent of the particle masses.
<<PHS wood: phs wood: TBP>>=
procedure :: init => phs_wood_init
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_init (phs, phs_config)
class(phs_wood_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_wood_init
<<PHS wood: procedures>>=
module subroutine phs_wood_init (phs, phs_config)
class(phs_wood_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
call phs%base_init (phs_config)
select type(phs_config)
type is (phs_wood_config_t)
phs%forest = phs_config%forest
if (phs_config%is_combined_integration) then
phs%n_r_born = phs_config%n_par - 3
end if
end select
end subroutine phs_wood_init
@ %def phs_wood_init
@
\subsection{Evaluation}
We compute the outgoing momenta from the incoming momenta and
the input parameter set [[r_in]] in channel [[r_in]]. We also compute the
[[r]] parameters and Jacobians [[f]] for all other channels.
We do \emph{not} need to a apply a transformation from/to the c.m.\ frame,
because in [[phs_base]] the momenta are already boosted to the c.m.\ frame
before assigning them in the [[phs]] object, and inversely boosted when
extracting them.
<<PHS wood: phs wood: TBP>>=
procedure :: evaluate_selected_channel => phs_wood_evaluate_selected_channel
procedure :: evaluate_other_channels => phs_wood_evaluate_other_channels
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_evaluate_selected_channel (phs, c_in, r_in)
class(phs_wood_t), intent(inout) :: phs
real(default), intent(in), dimension(:) :: r_in
integer, intent(in) :: c_in
end subroutine phs_wood_evaluate_selected_channel
module subroutine phs_wood_evaluate_other_channels (phs, c_in)
class(phs_wood_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_wood_evaluate_other_channels
<<PHS wood: procedures>>=
module subroutine phs_wood_evaluate_selected_channel (phs, c_in, r_in)
class(phs_wood_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
logical :: ok
phs%q_defined = .false.
if (phs%p_defined) then
call phs%forest%set_prt_in (phs%p)
phs%r(:,c_in) = r_in
call phs%forest%evaluate_selected_channel (c_in, phs%active_channel, &
phs%sqrts_hat, phs%r, phs%f, phs%volume, ok)
select type (config => phs%config)
type is (phs_wood_config_t)
if (config%is_combined_integration) then
if (phs%n_r_born >= 0) then
phs%r_real = r_in (phs%n_r_born + 1 : phs%n_r_born + 3)
else
call msg_fatal ("n_r_born should be larger than 0!")
end if
end if
end select
if (ok) then
phs%q = phs%forest%get_momenta_out ()
phs%q_defined = .true.
end if
end if
end subroutine phs_wood_evaluate_selected_channel
module subroutine phs_wood_evaluate_other_channels (phs, c_in)
class(phs_wood_t), intent(inout) :: phs
integer, intent(in) :: c_in
integer :: c
if (phs%q_defined) then
call phs%forest%evaluate_other_channels (c_in, phs%active_channel, &
phs%sqrts_hat, phs%r, phs%f, combine=.true.)
select type (config => phs%config)
type is (phs_wood_config_t)
if (config%is_combined_integration) then
if (phs%n_r_born >= 0) then
do c = 1, size (phs%r, 2)
phs%r(phs%n_r_born + 1 : phs%n_r_born + 3, c) = phs%r_real
end do
else
phs%r_defined = .false.
end if
end if
end select
phs%r_defined = .true.
end if
end subroutine phs_wood_evaluate_other_channels
@ %def phs_wood_evaluate_selected_channel
@ %def phs_wood_evaluate_other_channels
@ Inverse evaluation.
<<PHS wood: phs wood: TBP>>=
procedure :: inverse => phs_wood_inverse
<<PHS wood: sub interfaces>>=
module subroutine phs_wood_inverse (phs)
class(phs_wood_t), intent(inout) :: phs
end subroutine phs_wood_inverse
<<PHS wood: procedures>>=
module subroutine phs_wood_inverse (phs)
class(phs_wood_t), intent(inout) :: phs
if (phs%p_defined .and. phs%q_defined) then
call phs%forest%set_prt_in (phs%p)
call phs%forest%set_prt_out (phs%q)
call phs%forest%recover_channel (1, phs%sqrts_hat, phs%r, &
phs%f, phs%volume)
call phs%forest%evaluate_other_channels (1, phs%active_channel, &
phs%sqrts_hat, phs%r, phs%f, combine=.false.)
phs%r_defined = .true.
end if
end subroutine phs_wood_inverse
@ %def phs_wood_inverse
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[phs_wood_ut.f90]]>>=
<<File header>>
module phs_wood_ut
use unit_tests
use phs_wood_uti
<<Standard module head>>
<<PHS wood: public test>>
<<PHS wood: public test auxiliary>>
contains
<<PHS wood: test driver>>
end module phs_wood_ut
@ %def phs_wood_ut
@
<<[[phs_wood_uti.f90]]>>=
<<File header>>
module phs_wood_uti
<<Use kinds>>
<<Use strings>>
use io_units
use os_interface
use lorentz
use flavors
use model_data
use process_constants
use mappings
use phs_base
use phs_forests
use phs_wood
use phs_base_ut, only: init_test_process_data, init_test_decay_data
<<Standard module head>>
<<PHS wood: public test auxiliary>>
<<PHS wood: test declarations>>
contains
<<PHS wood: tests>>
<<PHS wood: test auxiliary>>
end module phs_wood_uti
@ %def phs_wood_ut
@ API: driver for the unit tests below.
<<PHS wood: public test>>=
public :: phs_wood_test
<<PHS wood: test driver>>=
subroutine phs_wood_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS wood: execute tests>>
end subroutine phs_wood_test
@ %def phs_wood_test
<<PHS wood: public test>>=
public :: phs_wood_vis_test
<<PHS wood: test driver>>=
subroutine phs_wood_vis_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<PHS wood: execute vis tests>>
end subroutine phs_wood_vis_test
@ %def phs_wood_vis_test
@
\subsubsection{Phase-space configuration data}
Construct and display a test phase-space configuration object. Also
check the [[azimuthal_dependence]] flag.
This auxiliary routine writes a phase-space configuration file to unit
[[u_phs]].
<<PHS wood: public test auxiliary>>=
public :: write_test_phs_file
<<PHS wood: test auxiliary>>=
subroutine write_test_phs_file (u_phs, procname)
integer, intent(in) :: u_phs
type(string_t), intent(in), optional :: procname
if (present (procname)) then
write (u_phs, "(A,A)") "process ", char (procname)
else
write (u_phs, "(A)") "process testproc"
end if
write (u_phs, "(A,A)") " md5sum_process = ", '""'
write (u_phs, "(A,A)") " md5sum_model_par = ", '""'
write (u_phs, "(A,A)") " md5sum_phs_config = ", '""'
write (u_phs, "(A)") " sqrts = 1000"
write (u_phs, "(A)") " m_threshold_s = 50"
write (u_phs, "(A)") " m_threshold_t = 100"
write (u_phs, "(A)") " off_shell = 2"
write (u_phs, "(A)") " t_channel = 6"
write (u_phs, "(A)") " keep_nonresonant = T"
write (u_phs, "(A)") " grove #1"
write (u_phs, "(A)") " tree 3"
end subroutine write_test_phs_file
@ %def write_test_phs_file
@
<<PHS wood: execute tests>>=
call test (phs_wood_1, "phs_wood_1", &
"phase-space configuration", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_1
<<PHS wood: tests>>=
subroutine phs_wood_1 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
type(mapping_defaults_t) :: mapping_defaults
real(default) :: sqrts
integer :: u_phs, iostat
character(32) :: buffer
write (u, "(A)") "* Test output: phs_wood_1"
write (u, "(A)") "* Purpose: initialize and display &
&phase-space configuration data"
write (u, "(A)")
call model%init_test ()
call syntax_phs_forest_init ()
write (u, "(A)") "* Initialize a process"
write (u, "(A)")
call init_test_process_data (var_str ("phs_wood_1"), process_data)
write (u, "(A)") "* Create a scratch phase-space file"
write (u, "(A)")
u_phs = free_unit ()
open (u_phs, status = "scratch", action = "readwrite")
call write_test_phs_file (u_phs, var_str ("phs_wood_1"))
rewind (u_phs)
do
read (u_phs, "(A)", iostat = iostat) buffer
if (iostat /= 0) exit
write (u, "(A)") trim (buffer)
end do
write (u, "(A)")
write (u, "(A)") "* Setup phase-space configuration object"
write (u, "(A)")
mapping_defaults%step_mapping = .false.
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_input (u_phs)
call phs_data%set_mapping_defaults (mapping_defaults)
end select
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
write (u, "(A)")
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%write_forest (u)
end select
write (u, "(A)")
write (u, "(A)") "* Cleanup"
close (u_phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_1"
end subroutine phs_wood_1
@ %def phs_wood_1
@
\subsubsection{Phase space evaluation}
Compute kinematics for given parameters, also invert the calculation.
<<PHS wood: execute tests>>=
call test (phs_wood_2, "phs_wood_2", &
"phase-space evaluation", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_2
<<PHS wood: tests>>=
subroutine phs_wood_2 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(flavor_t) :: flv
type(process_constants_t) :: process_data
real(default) :: sqrts, E
class(phs_config_t), allocatable, target :: phs_data
class(phs_t), pointer :: phs => null ()
type(vector4_t), dimension(2) :: p, q
integer :: u_phs
write (u, "(A)") "* Test output: phs_wood_2"
write (u, "(A)") "* Purpose: test simple single-channel phase space"
write (u, "(A)")
call model%init_test ()
call flv%init (25, model)
write (u, "(A)") "* Initialize a process and a matching &
&phase-space configuration"
write (u, "(A)")
call init_test_process_data (var_str ("phs_wood_2"), process_data)
u_phs = free_unit ()
open (u_phs, status = "scratch", action = "readwrite")
call write_test_phs_file (u_phs, var_str ("phs_wood_2"))
rewind (u_phs)
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_input (u_phs)
end select
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%write (u, verbose=.true.)
write (u, "(A)")
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
E = sqrts / 2
p(1) = vector4_moving (E, sqrt (E**2 - flv%get_mass ()**2), 3)
p(2) = vector4_moving (E,-sqrt (E**2 - flv%get_mass ()**2), 3)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Compute phase-space point &
&for x = 0.125, 0.5"
write (u, "(A)")
call phs%evaluate_selected_channel (1, [0.125_default, 0.5_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
select type (phs)
type is (phs_wood_t)
call phs%write_forest (u)
end select
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
call phs%final ()
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
write (u, "(A)")
select type (phs)
type is (phs_wood_t)
call phs%write_forest (u)
end select
call phs%final ()
deallocate (phs)
close (u_phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_2"
end subroutine phs_wood_2
@ %def phs_wood_2
@
\subsubsection{Phase-space generation}
Generate phase space for a simple process.
<<PHS wood: execute tests>>=
call test (phs_wood_3, "phs_wood_3", &
"phase-space generation", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_3
<<PHS wood: tests>>=
subroutine phs_wood_3 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
type(phs_parameters_t) :: phs_par
class(phs_config_t), allocatable :: phs_data
integer :: iostat
character(80) :: buffer
write (u, "(A)") "* Test output: phs_wood_3"
write (u, "(A)") "* Purpose: generate a phase-space configuration"
write (u, "(A)")
call model%init_test ()
call syntax_phs_forest_init ()
write (u, "(A)") "* Initialize a process and phase-space parameters"
write (u, "(A)")
call init_test_process_data (var_str ("phs_wood_3"), process_data)
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%io_unit_keep_open = .true.
end select
write (u, "(A)")
write (u, "(A)") "* Generate a scratch phase-space file"
write (u, "(A)")
call phs_data%configure (phs_par%sqrts)
select type (phs_data)
type is (phs_wood_config_t)
rewind (phs_data%io_unit)
do
read (phs_data%io_unit, "(A)", iostat = iostat) buffer
if (iostat /= 0) exit
write (u, "(A)") trim (buffer)
end do
end select
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_3"
end subroutine phs_wood_3
@ %def phs_wood_3
@
\subsubsection{Nontrivial process}
Generate phase space for a $2\to 3$ process.
<<PHS wood: execute tests>>=
call test (phs_wood_4, "phs_wood_4", &
"nontrivial process", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_4
<<PHS wood: tests>>=
subroutine phs_wood_4 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
type(phs_parameters_t) :: phs_par
class(phs_config_t), allocatable, target :: phs_data
integer :: iostat
character(80) :: buffer
class(phs_t), pointer :: phs => null ()
real(default) :: E, pL
type(vector4_t), dimension(2) :: p
type(vector4_t), dimension(3) :: q
write (u, "(A)") "* Test output: phs_wood_4"
write (u, "(A)") "* Purpose: generate a phase-space configuration"
write (u, "(A)")
call model%init_test ()
call syntax_phs_forest_init ()
write (u, "(A)") "* Initialize a process and phase-space parameters"
write (u, "(A)")
process_data%id = "phs_wood_4"
process_data%model_name = "Test"
process_data%n_in = 2
process_data%n_out = 3
process_data%n_flv = 1
allocate (process_data%flv_state (process_data%n_in + process_data%n_out, &
process_data%n_flv))
process_data%flv_state(:,1) = [25, 25, 25, 6, -6]
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%io_unit_keep_open = .true.
end select
write (u, "(A)")
write (u, "(A)") "* Generate a scratch phase-space file"
write (u, "(A)")
call phs_data%configure (phs_par%sqrts)
select type (phs_data)
type is (phs_wood_config_t)
rewind (phs_data%io_unit)
do
read (phs_data%io_unit, "(A)", iostat = iostat) buffer
if (iostat /= 0) exit
write (u, "(A)") trim (buffer)
end do
end select
write (u, "(A)")
write (u, "(A)") "* Initialize the phase-space instance"
write (u, "(A)")
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
write (u, "(A)") "* Set incoming momenta"
write (u, "(A)")
select type (phs_data)
type is (phs_wood_config_t)
E = phs_data%sqrts / 2
pL = sqrt (E**2 - phs_data%flv(1,1)%get_mass ()**2)
end select
p(1) = vector4_moving (E, pL, 3)
p(2) = vector4_moving (E, -pL, 3)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
write (u, "(A)") "* Compute phase-space point &
&for x = 0.1, 0.2, 0.3, 0.4, 0.5"
write (u, "(A)")
call phs%evaluate_selected_channel (1, &
[0.1_default, 0.2_default, 0.3_default, 0.4_default, 0.5_default])
call phs%evaluate_other_channels (1)
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Inverse kinematics"
write (u, "(A)")
call phs%get_outgoing_momenta (q)
call phs%final ()
deallocate (phs)
call phs_data%allocate_instance (phs)
call phs%init (phs_data)
call phs%set_incoming_momenta (p)
call phs%compute_flux ()
call phs%set_outgoing_momenta (q)
call phs%inverse ()
call phs%write (u)
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call phs%final ()
deallocate (phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_4"
end subroutine phs_wood_4
@ %def phs_wood_4
@
\subsubsection{Equivalences}
Generate phase space for a simple process, including channel equivalences.
<<PHS wood: execute tests>>=
call test (phs_wood_5, "phs_wood_5", &
"equivalences", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_5
<<PHS wood: tests>>=
subroutine phs_wood_5 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
type(phs_parameters_t) :: phs_par
class(phs_config_t), allocatable :: phs_data
write (u, "(A)") "* Test output: phs_wood_5"
write (u, "(A)") "* Purpose: generate a phase-space configuration"
write (u, "(A)")
call model%init_test ()
call syntax_phs_forest_init ()
write (u, "(A)") "* Initialize a process and phase-space parameters"
write (u, "(A)")
call init_test_process_data (var_str ("phs_wood_5"), process_data)
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
call phs_data%enable_equivalences ()
end select
write (u, "(A)")
write (u, "(A)") "* Generate a scratch phase-space file"
write (u, "(A)")
call phs_data%configure (phs_par%sqrts)
call phs_data%write (u)
write (u, "(A)")
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%write_forest (u)
end select
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_5"
end subroutine phs_wood_5
@ %def phs_wood_5
@
\subsubsection{MD5 sum checks}
Generate phase space for a simple process. Repeat this with and without
parameter change.
<<PHS wood: execute tests>>=
call test (phs_wood_6, "phs_wood_6", &
"phase-space generation", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_6
<<PHS wood: tests>>=
subroutine phs_wood_6 (u)
integer, intent(in) :: u
type(model_data_t), target :: model
type(process_constants_t) :: process_data
type(phs_parameters_t) :: phs_par
class(phs_config_t), allocatable :: phs_data
logical :: exist, found, match
integer :: u_phs
character(*), parameter :: filename = "phs_wood_6_p.phs"
write (u, "(A)") "* Test output: phs_wood_6"
write (u, "(A)") "* Purpose: generate and check phase-space file"
write (u, "(A)")
call model%init_test ()
call syntax_phs_forest_init ()
write (u, "(A)") "* Initialize a process and phase-space parameters"
write (u, "(A)")
call init_test_process_data (var_str ("phs_wood_6"), process_data)
process_data%id = "phs_wood_6_p"
process_data%md5sum = "1234567890abcdef1234567890abcdef"
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
end select
write (u, "(A)") "* Remove previous phs file, if any"
write (u, "(A)")
inquire (file = filename, exist = exist)
if (exist) then
u_phs = free_unit ()
open (u_phs, file = filename, action = "write")
close (u_phs, status = "delete")
end if
write (u, "(A)") "* Check phase-space file (should fail)"
write (u, "(A)")
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%read_phs_file (exist, found, match)
write (u, "(1x,A,L1)") "exist = ", exist
write (u, "(1x,A,L1)") "found = ", found
write (u, "(1x,A,L1)") "match = ", match
end select
write (u, "(A)")
write (u, "(A)") "* Generate a phase-space file"
write (u, "(A)")
call phs_data%configure (phs_par%sqrts)
write (u, "(1x,A,A,A)") "MD5 sum (process) = '", &
phs_data%md5sum_process, "'"
write (u, "(1x,A,A,A)") "MD5 sum (model par) = '", &
phs_data%md5sum_model_par, "'"
write (u, "(1x,A,A,A)") "MD5 sum (phs config) = '", &
phs_data%md5sum_phs_config, "'"
write (u, "(A)")
write (u, "(A)") "* Check MD5 sum"
write (u, "(A)")
call phs_data%final ()
deallocate (phs_data)
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%sqrts = phs_par%sqrts
phs_data%par%sqrts = phs_par%sqrts
end select
call phs_data%compute_md5sum ()
write (u, "(1x,A,A,A)") "MD5 sum (process) = '", &
phs_data%md5sum_process, "'"
write (u, "(1x,A,A,A)") "MD5 sum (model par) = '", &
phs_data%md5sum_model_par, "'"
write (u, "(1x,A,A,A)") "MD5 sum (phs config) = '", &
phs_data%md5sum_phs_config, "'"
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%read_phs_file (exist, found, match)
write (u, "(1x,A,L1)") "exist = ", exist
write (u, "(1x,A,L1)") "found = ", found
write (u, "(1x,A,L1)") "match = ", match
end select
write (u, "(A)")
write (u, "(A)") "* Modify sqrts and check MD5 sum"
write (u, "(A)")
call phs_data%final ()
deallocate (phs_data)
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 500
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%sqrts = phs_par%sqrts
phs_data%par%sqrts = phs_par%sqrts
end select
call phs_data%compute_md5sum ()
write (u, "(1x,A,A,A)") "MD5 sum (process) = '", &
phs_data%md5sum_process, "'"
write (u, "(1x,A,A,A)") "MD5 sum (model par) = '", &
phs_data%md5sum_model_par, "'"
write (u, "(1x,A,A,A)") "MD5 sum (phs config) = '", &
phs_data%md5sum_phs_config, "'"
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%read_phs_file (exist, found, match)
write (u, "(1x,A,L1)") "exist = ", exist
write (u, "(1x,A,L1)") "found = ", found
write (u, "(1x,A,L1)") "match = ", match
end select
write (u, "(A)")
write (u, "(A)") "* Modify process and check MD5 sum"
write (u, "(A)")
call phs_data%final ()
deallocate (phs_data)
process_data%md5sum = "77777777777777777777777777777777"
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%sqrts = phs_par%sqrts
phs_data%par%sqrts = phs_par%sqrts
end select
call phs_data%compute_md5sum ()
write (u, "(1x,A,A,A)") "MD5 sum (process) = '", &
phs_data%md5sum_process, "'"
write (u, "(1x,A,A,A)") "MD5 sum (model par) = '", &
phs_data%md5sum_model_par, "'"
write (u, "(1x,A,A,A)") "MD5 sum (phs config) = '", &
phs_data%md5sum_phs_config, "'"
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%read_phs_file (exist, found, match)
write (u, "(1x,A,L1)") "exist = ", exist
write (u, "(1x,A,L1)") "found = ", found
write (u, "(1x,A,L1)") "match = ", match
end select
write (u, "(A)")
write (u, "(A)") "* Modify phs parameter and check MD5 sum"
write (u, "(A)")
call phs_data%final ()
deallocate (phs_data)
allocate (phs_wood_config_t :: phs_data)
process_data%md5sum = "1234567890abcdef1234567890abcdef"
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
phs_par%off_shell = 17
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%sqrts = phs_par%sqrts
phs_data%par%sqrts = phs_par%sqrts
end select
call phs_data%compute_md5sum ()
write (u, "(1x,A,A,A)") "MD5 sum (process) = '", &
phs_data%md5sum_process, "'"
write (u, "(1x,A,A,A)") "MD5 sum (model par) = '", &
phs_data%md5sum_model_par, "'"
write (u, "(1x,A,A,A)") "MD5 sum (phs config) = '", &
phs_data%md5sum_phs_config, "'"
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%read_phs_file (exist, found, match)
write (u, "(1x,A,L1)") "exist = ", exist
write (u, "(1x,A,L1)") "found = ", found
write (u, "(1x,A,L1)") "match = ", match
end select
write (u, "(A)")
write (u, "(A)") "* Modify model parameter and check MD5 sum"
write (u, "(A)")
call phs_data%final ()
deallocate (phs_data)
allocate (phs_wood_config_t :: phs_data)
call model%set_par (var_str ("ms"), 100._default)
call phs_data%init (process_data, model)
phs_par%sqrts = 1000
phs_par%off_shell = 1
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_parameters (phs_par)
phs_data%sqrts = phs_par%sqrts
phs_data%par%sqrts = phs_par%sqrts
end select
call phs_data%compute_md5sum ()
write (u, "(1x,A,A,A)") "MD5 sum (process) = '", &
phs_data%md5sum_process, "'"
write (u, "(1x,A,A,A)") "MD5 sum (model par) = '", &
phs_data%md5sum_model_par, "'"
write (u, "(1x,A,A,A)") "MD5 sum (phs config) = '", &
phs_data%md5sum_phs_config, "'"
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%read_phs_file (exist, found, match)
write (u, "(1x,A,L1)") "exist = ", exist
write (u, "(1x,A,L1)") "found = ", found
write (u, "(1x,A,L1)") "match = ", match
end select
write (u, "(A)")
write (u, "(A)") "* Cleanup"
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_6"
end subroutine phs_wood_6
@ %def phs_wood_6
@
<<PHS wood: execute vis tests>>=
call test (phs_wood_vis_1, "phs_wood_vis_1", &
"visualizing phase space channels", &
u, results)
<<PHS wood: test declarations>>=
public :: phs_wood_vis_1
<<PHS wood: tests>>=
subroutine phs_wood_vis_1 (u)
integer, intent(in) :: u
type(os_data_t) :: os_data
type(model_data_t), target :: model
type(process_constants_t) :: process_data
class(phs_config_t), allocatable :: phs_data
type(mapping_defaults_t) :: mapping_defaults
type(string_t) :: vis_file, pdf_file, ps_file
real(default) :: sqrts
logical :: exist, exist_pdf, exist_ps
integer :: u_phs, iostat, u_vis
character(95) :: buffer
write (u, "(A)") "* Test output: phs_wood_vis_1"
write (u, "(A)") "* Purpose: visualizing the &
&phase-space configuration"
write (u, "(A)")
call os_data%init ()
call model%init_test ()
call syntax_phs_forest_init ()
write (u, "(A)") "* Initialize a process"
write (u, "(A)")
call init_test_process_data (var_str ("phs_wood_vis_1"), process_data)
write (u, "(A)") "* Create a scratch phase-space file"
write (u, "(A)")
u_phs = free_unit ()
open (u_phs, status = "scratch", action = "readwrite")
call write_test_phs_file (u_phs, var_str ("phs_wood_vis_1"))
rewind (u_phs)
do
read (u_phs, "(A)", iostat = iostat) buffer
if (iostat /= 0) exit
write (u, "(A)") trim (buffer)
end do
write (u, "(A)")
write (u, "(A)") "* Setup phase-space configuration object"
write (u, "(A)")
mapping_defaults%step_mapping = .false.
allocate (phs_wood_config_t :: phs_data)
call phs_data%init (process_data, model)
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%set_input (u_phs)
call phs_data%set_mapping_defaults (mapping_defaults)
phs_data%os_data = os_data
phs_data%io_unit = 0
phs_data%io_unit_keep_open = .true.
phs_data%vis_channels = .true.
end select
sqrts = 1000._default
call phs_data%configure (sqrts)
call phs_data%write (u)
write (u, "(A)")
select type (phs_data)
type is (phs_wood_config_t)
call phs_data%write_forest (u)
end select
vis_file = "phs_wood_vis_1.phs-vis.tex"
ps_file = "phs_wood_vis_1.phs-vis.ps"
pdf_file = "phs_wood_vis_1.phs-vis.pdf"
inquire (file = char (vis_file), exist = exist)
if (exist) then
u_vis = free_unit ()
open (u_vis, file = char (vis_file), action = "read", status = "old")
iostat = 0
do while (iostat == 0)
read (u_vis, "(A)", iostat = iostat) buffer
if (iostat == 0) write (u, "(A)") trim (buffer)
end do
close (u_vis)
else
write (u, "(A)") "[Visualize LaTeX file is missing]"
end if
inquire (file = char (ps_file), exist = exist_ps)
if (exist_ps) then
write (u, "(A)") "[Visualize Postscript file exists and is nonempty]"
else
write (u, "(A)") "[Visualize Postscript file is missing/non-regular]"
end if
inquire (file = char (pdf_file), exist = exist_pdf)
if (exist_pdf) then
write (u, "(A)") "[Visualize PDF file exists and is nonempty]"
else
write (u, "(A)") "[Visualize PDF file is missing/non-regular]"
end if
write (u, "(A)")
write (u, "(A)") "* Cleanup"
close (u_phs)
call phs_data%final ()
call model%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_wood_vis_1"
end subroutine phs_wood_vis_1
@ %def phs_wood_vis_1
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{The FKS phase space}
<<[[phs_fks.f90]]>>=
<<File header>>
module phs_fks
<<Use kinds>>
<<Use strings>>
use constants
use lorentz
use phs_points
use models, only: model_t
use phs_base
use resonances, only: resonance_contributors_t, resonance_history_t
use phs_wood
<<Standard module head>>
<<PHS fks: public>>
<<PHS fks: parameters>>
<<PHS fks: types>>
interface
<<PHS fks: sub interfaces>>
end interface
contains
<<PHS fks: main procedures>>
end module phs_fks
@ %def phs_fks
@
<<[[phs_fks_sub.f90]]>>=
<<File header>>
submodule (phs_fks) phs_fks_s
<<Use debug>>
use diagnostics
use io_units, only: given_output_unit, free_unit
use format_defs, only: FMT_17
use format_utils, only: write_separator
use physics_defs
use flavors
use pdg_arrays, only: is_colored
use cascades
use cascades2
use ttv_formfactors, only: generate_on_shell_decay_threshold, m1s_to_mpole
!!! Intel oneAPI 2022/23 regression workaround
use resonances, only: resonance_history_t
use resonances, only: resonance_contributors_t
implicit none
<<PHS fks: interfaces>>
contains
<<PHS fks: procedures>>
end submodule phs_fks_s
@ %def phs_fks_s
@ A container for the $x_\oplus$- and $x_\ominus$-values for initial-state
phase spaces.
<<PHS fks: public>>=
public :: isr_kinematics_t
<<PHS fks: types>>=
type :: isr_kinematics_t
integer :: n_in
real(default), dimension(2) :: x = one
real(default), dimension(2) :: z = zero
real(default) :: sqrts_born = zero
real(default), dimension(:), allocatable :: beam_energy
real(default) :: fac_scale = zero
real(default), dimension(2) :: jacobian = one
integer :: isr_mode = SQRTS_FIXED
contains
<<PHS fks: isr kinematics: TBP>>
end type isr_kinematics_t
@ %def type isr_kinematics_t
@
<<PHS fks: isr kinematics: TBP>>=
procedure :: write => isr_kinematics_write
<<PHS fks: sub interfaces>>=
module subroutine isr_kinematics_write (isr, unit)
class(isr_kinematics_t), intent(in) :: isr
integer, intent(in), optional :: unit
end subroutine isr_kinematics_write
<<PHS fks: procedures>>=
module subroutine isr_kinematics_write (isr, unit)
class(isr_kinematics_t), intent(in) :: isr
integer, intent(in), optional :: unit
integer :: u, i
u = given_output_unit (unit); if (u < 0) return
write (u,"(A)") "ISR kinematics: "
write (u,"(A," // FMT_17 // ",1X)") "x(+): ", isr%x(1)
write (u,"(A," // FMT_17 // ",1X)") "x(-): ", isr%x(2)
write (u,"(A," // FMT_17 // ",1X)") "z(+): ", isr%z(1)
write (u,"(A," // FMT_17 // ",1X)") "z(-): ", isr%z(2)
write (u,"(A," // FMT_17 // ",1X)") "sqrts (Born): ", isr%sqrts_born
if (allocated (isr%beam_energy)) then
do i = 1, size (isr%beam_energy)
write (u,"(A," // FMT_17 // ",1X)") "Beam energy: ", &
isr%beam_energy(i)
end do
end if
write (u,"(A," // FMT_17 // ",1X)") "Fac. scale: ", isr%fac_scale
do i = 1, 2
write (u,"(A," // FMT_17 // ",1X)") "Jacobian: ", isr%jacobian(i)
end do
write (u,"(A,I0,1X)") "ISR mode: ", isr%isr_mode
end subroutine isr_kinematics_write
@ %def isr_kinematics_write
@
<<PHS fks: public>>=
public :: phs_point_set_t
<<PHS fks: types>>=
type :: phs_point_set_t
type(phs_point_t), dimension(:), allocatable :: phs_point
logical :: initialized = .false.
contains
<<PHS fks: phs point set: TBP>>
end type phs_point_set_t
@ %def phs_point_set_t
@
<<PHS fks: phs point set: TBP>>=
procedure :: init => phs_point_set_init
<<PHS fks: sub interfaces>>=
module subroutine phs_point_set_init (phs_point_set, n_particles, n_phs)
class(phs_point_set_t), intent(out) :: phs_point_set
integer, intent(in) :: n_particles, n_phs
end subroutine phs_point_set_init
<<PHS fks: procedures>>=
module subroutine phs_point_set_init (phs_point_set, n_particles, n_phs)
class(phs_point_set_t), intent(out) :: phs_point_set
integer, intent(in) :: n_particles, n_phs
integer :: i_phs
allocate (phs_point_set%phs_point (n_phs))
do i_phs = 1, n_phs
phs_point_set%phs_point(i_phs) = n_particles
end do
phs_point_set%initialized = .true.
end subroutine phs_point_set_init
@ %def phs_point_set_init
@
<<PHS fks: phs point set: TBP>>=
procedure :: write => phs_point_set_write
<<PHS fks: sub interfaces>>=
module subroutine phs_point_set_write (phs_point_set, i_phs, contributors, &
unit, show_mass, testflag, check_conservation, ultra, n_in)
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in), optional :: i_phs
integer, intent(in), dimension(:), optional :: contributors
integer, intent(in), optional :: unit
logical, intent(in), optional :: show_mass
logical, intent(in), optional :: testflag, ultra
logical, intent(in), optional :: check_conservation
integer, intent(in), optional :: n_in
end subroutine phs_point_set_write
<<PHS fks: procedures>>=
module subroutine phs_point_set_write (phs_point_set, i_phs, contributors, &
unit, show_mass, testflag, check_conservation, ultra, n_in)
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in), optional :: i_phs
integer, intent(in), dimension(:), optional :: contributors
integer, intent(in), optional :: unit
logical, intent(in), optional :: show_mass
logical, intent(in), optional :: testflag, ultra
logical, intent(in), optional :: check_conservation
integer, intent(in), optional :: n_in
integer :: i, u
type(vector4_t) :: p_sum
u = given_output_unit (unit); if (u < 0) return
if (present (i_phs)) then
call phs_point_set%phs_point(i_phs)%write &
(unit = u, show_mass = show_mass, testflag = testflag, &
check_conservation = check_conservation, ultra = ultra, n_in = n_in)
else
do i = 1, size(phs_point_set%phs_point)
call phs_point_set%phs_point(i)%write &
(unit = u, show_mass = show_mass, testflag = testflag, &
check_conservation = check_conservation, ultra = ultra,&
n_in = n_in)
end do
end if
if (present (contributors)) then
if (debug_on) call msg_debug &
(D_SUBTRACTION, "Invariant masses for real emission: ")
associate (pp => phs_point_set%phs_point(i_phs))
p_sum = sum (pp, [contributors, size (pp)])
end associate
if (debug_active (D_SUBTRACTION)) &
call vector4_write (p_sum, unit = unit, show_mass = show_mass, &
testflag = testflag, ultra = ultra)
end if
end subroutine phs_point_set_write
@ %def phs_point_set_write
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_n_momenta => phs_point_set_get_n_momenta
<<PHS fks: sub interfaces>>=
elemental module function phs_point_set_get_n_momenta &
(phs_point_set, i_res) result (n)
integer :: n
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_res
end function phs_point_set_get_n_momenta
<<PHS fks: procedures>>=
elemental module function phs_point_set_get_n_momenta &
(phs_point_set, i_res) result (n)
integer :: n
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_res
n = size (phs_point_set%phs_point(i_res))
end function phs_point_set_get_n_momenta
@ %def phs_point_set_get_n_momenta
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_momenta => phs_point_set_get_momenta
<<PHS fks: sub interfaces>>=
pure module function phs_point_set_get_momenta &
(phs_point_set, i_phs, n_in) result (p)
type(vector4_t), dimension(:), allocatable :: p
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
integer, intent(in), optional :: n_in
end function phs_point_set_get_momenta
<<PHS fks: procedures>>=
pure module function phs_point_set_get_momenta &
(phs_point_set, i_phs, n_in) result (p)
type(vector4_t), dimension(:), allocatable :: p
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
integer, intent(in), optional :: n_in
integer :: i
if (present (n_in)) then
p = phs_point_set%phs_point(i_phs)%select ([(i, i=1, n_in)])
else
p = phs_point_set%phs_point(i_phs)
end if
end function phs_point_set_get_momenta
@ %def phs_point_set_get_momenta
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_momentum => phs_point_set_get_momentum
<<PHS fks: sub interfaces>>=
pure module function phs_point_set_get_momentum &
(phs_point_set, i_phs, i_mom) result (p)
type(vector4_t) :: p
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs, i_mom
end function phs_point_set_get_momentum
<<PHS fks: procedures>>=
pure module function phs_point_set_get_momentum &
(phs_point_set, i_phs, i_mom) result (p)
type(vector4_t) :: p
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs, i_mom
p = phs_point_set%phs_point(i_phs)%select (i_mom)
end function phs_point_set_get_momentum
@ %def phs_point_set_get_momentum
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_energy => phs_point_set_get_energy
<<PHS fks: sub interfaces>>=
pure module function phs_point_set_get_energy &
(phs_point_set, i_phs, i_mom) result (E)
real(default) :: E
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs, i_mom
end function phs_point_set_get_energy
<<PHS fks: procedures>>=
pure module function phs_point_set_get_energy &
(phs_point_set, i_phs, i_mom) result (E)
real(default) :: E
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs, i_mom
E = energy (phs_point_set%phs_point(i_phs)%select (i_mom))
end function phs_point_set_get_energy
@ %def phs_point_set_get_energy
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_sqrts => phs_point_set_get_sqrts
<<PHS fks: sub interfaces>>=
module function phs_point_set_get_sqrts &
(phs_point_set, i_phs) result (sqrts)
real(default) :: sqrts
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
end function phs_point_set_get_sqrts
<<PHS fks: procedures>>=
module function phs_point_set_get_sqrts &
(phs_point_set, i_phs) result (sqrts)
real(default) :: sqrts
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
sqrts = sqrt (phs_point_set%phs_point(i_phs)%get_msq ([1,2]))
end function phs_point_set_get_sqrts
@ %def phs_point_set_get_sqrts
@
<<PHS fks: phs point set: TBP>>=
generic :: set_momenta => set_momenta_p, set_momenta_phs_point
procedure :: set_momenta_p => phs_point_set_set_momenta_p
<<PHS fks: sub interfaces>>=
module subroutine phs_point_set_set_momenta_p (phs_point_set, i_phs, p)
class(phs_point_set_t), intent(inout) :: phs_point_set
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p
end subroutine phs_point_set_set_momenta_p
<<PHS fks: procedures>>=
module subroutine phs_point_set_set_momenta_p (phs_point_set, i_phs, p)
class(phs_point_set_t), intent(inout) :: phs_point_set
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p
phs_point_set%phs_point(i_phs) = p
end subroutine phs_point_set_set_momenta_p
@ %def phs_point_set_set_momenta_p
@
<<PHS fks: phs point set: TBP>>=
procedure :: set_momenta_phs_point => phs_point_set_set_momenta_phs_point
<<PHS fks: sub interfaces>>=
module subroutine phs_point_set_set_momenta_phs_point &
(phs_point_set, i_phs, p)
class(phs_point_set_t), intent(inout) :: phs_point_set
integer, intent(in) :: i_phs
type(phs_point_t), intent(in) :: p
end subroutine phs_point_set_set_momenta_phs_point
<<PHS fks: procedures>>=
module subroutine phs_point_set_set_momenta_phs_point &
(phs_point_set, i_phs, p)
class(phs_point_set_t), intent(inout) :: phs_point_set
integer, intent(in) :: i_phs
type(phs_point_t), intent(in) :: p
phs_point_set%phs_point(i_phs) = p
end subroutine phs_point_set_set_momenta_phs_point
@ %def phs_point_set_set_momenta_phs_point
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_n_particles => phs_point_set_get_n_particles
<<PHS fks: sub interfaces>>=
module function phs_point_set_get_n_particles &
(phs_point_set, i) result (n_particles)
integer :: n_particles
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in), optional :: i
end function phs_point_set_get_n_particles
<<PHS fks: procedures>>=
module function phs_point_set_get_n_particles &
(phs_point_set, i) result (n_particles)
integer :: n_particles
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in), optional :: i
integer :: j
j = 1; if (present (i)) j = i
n_particles = size (phs_point_set%phs_point(j))
end function phs_point_set_get_n_particles
@ %def phs_point_set_get_n_particles
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_n_phs => phs_point_set_get_n_phs
<<PHS fks: sub interfaces>>=
module function phs_point_set_get_n_phs (phs_point_set) result (n_phs)
integer :: n_phs
class(phs_point_set_t), intent(in) :: phs_point_set
end function phs_point_set_get_n_phs
<<PHS fks: procedures>>=
module function phs_point_set_get_n_phs (phs_point_set) result (n_phs)
integer :: n_phs
class(phs_point_set_t), intent(in) :: phs_point_set
n_phs = size (phs_point_set%phs_point)
end function phs_point_set_get_n_phs
@ %def phs_point_set_get_n_phs
@
<<PHS fks: phs point set: TBP>>=
procedure :: get_invariant_mass => phs_point_set_get_invariant_mass
<<PHS fks: sub interfaces>>=
module function phs_point_set_get_invariant_mass &
(phs_point_set, i_phs, i_part) result (m2)
real(default) :: m2
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
integer, intent(in), dimension(:) :: i_part
end function phs_point_set_get_invariant_mass
<<PHS fks: procedures>>=
module function phs_point_set_get_invariant_mass &
(phs_point_set, i_phs, i_part) result (m2)
real(default) :: m2
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
integer, intent(in), dimension(:) :: i_part
m2 = phs_point_set%phs_point(i_phs)%get_msq (i_part)
end function phs_point_set_get_invariant_mass
@ %def phs_point_set_get_invariant_mass
@
<<PHS fks: phs point set: TBP>>=
procedure :: write_phs_point => phs_point_set_write_phs_point
<<PHS fks: sub interfaces>>=
module subroutine phs_point_set_write_phs_point (phs_point_set, i_phs, &
unit, show_mass, testflag, check_conservation, ultra, n_in)
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
integer, intent(in), optional :: unit
logical, intent(in), optional :: show_mass
logical, intent(in), optional :: testflag, ultra
logical, intent(in), optional :: check_conservation
integer, intent(in), optional :: n_in
end subroutine phs_point_set_write_phs_point
<<PHS fks: procedures>>=
module subroutine phs_point_set_write_phs_point (phs_point_set, i_phs, &
unit, show_mass, testflag, check_conservation, ultra, n_in)
class(phs_point_set_t), intent(in) :: phs_point_set
integer, intent(in) :: i_phs
integer, intent(in), optional :: unit
logical, intent(in), optional :: show_mass
logical, intent(in), optional :: testflag, ultra
logical, intent(in), optional :: check_conservation
integer, intent(in), optional :: n_in
call phs_point_set%phs_point(i_phs)%write (unit, show_mass, testflag, &
check_conservation, ultra, n_in)
end subroutine phs_point_set_write_phs_point
@ %def phs_point_set_write_phs_point
@
<<PHS fks: phs point set: TBP>>=
procedure :: final => phs_point_set_final
<<PHS fks: sub interfaces>>=
module subroutine phs_point_set_final (phs_point_set)
class(phs_point_set_t), intent(inout) :: phs_point_set
end subroutine phs_point_set_final
<<PHS fks: procedures>>=
module subroutine phs_point_set_final (phs_point_set)
class(phs_point_set_t), intent(inout) :: phs_point_set
integer :: i
deallocate (phs_point_set%phs_point)
phs_point_set%initialized = .false.
end subroutine phs_point_set_final
@ %def phs_point_set_final
@
<<PHS fks: public>>=
public :: real_jacobian_t
<<PHS fks: types>>=
type :: real_jacobian_t
real(default), dimension(4) :: jac = 1._default
end type real_jacobian_t
@ %def real_jacobian_t
@
<<PHS fks: public>>=
public :: real_kinematics_t
<<PHS fks: types>>=
type :: real_kinematics_t
logical :: supply_xi_max = .true.
real(default) :: xi_tilde
real(default) :: phi
real(default), dimension(:), allocatable :: xi_max, y
real(default) :: xi_mismatch, y_mismatch
type(real_jacobian_t), dimension(:), allocatable :: jac
real(default) :: jac_mismatch
type(phs_point_set_t) :: p_born_cms
type(phs_point_set_t) :: p_born_lab
type(phs_point_set_t) :: p_real_cms
type(phs_point_set_t) :: p_real_lab
type(phs_point_set_t) :: p_born_onshell
type(phs_point_set_t), dimension(2) :: p_real_onshell
integer, dimension(:), allocatable :: alr_to_i_phs
real(default), dimension(3) :: x_rad
real(default), dimension(:), allocatable :: jac_rand
real(default), dimension(:), allocatable :: y_soft
real(default) :: cms_energy2
type(vector4_t), dimension(:), allocatable :: xi_ref_momenta
contains
<<PHS fks: real kinematics: TBP>>
end type real_kinematics_t
@ %def real_kinematics_t
@
<<PHS fks: real kinematics: TBP>>=
procedure :: init => real_kinematics_init
<<PHS fks: sub interfaces>>=
module subroutine real_kinematics_init (r, n_tot, n_phs, n_alr, n_contr)
class(real_kinematics_t), intent(inout) :: r
integer, intent(in) :: n_tot, n_phs, n_alr, n_contr
end subroutine real_kinematics_init
<<PHS fks: procedures>>=
module subroutine real_kinematics_init (r, n_tot, n_phs, n_alr, n_contr)
class(real_kinematics_t), intent(inout) :: r
integer, intent(in) :: n_tot, n_phs, n_alr, n_contr
allocate (r%xi_max (n_phs))
allocate (r%y (n_phs))
allocate (r%y_soft (n_phs))
call r%p_born_cms%init (n_tot - 1, 1)
call r%p_born_lab%init (n_tot - 1, 1)
call r%p_real_cms%init (n_tot, n_phs)
call r%p_real_lab%init (n_tot, n_phs)
allocate (r%jac (n_phs), r%jac_rand (n_phs))
allocate (r%alr_to_i_phs (n_alr))
allocate (r%xi_ref_momenta (n_contr))
r%alr_to_i_phs = 0
r%xi_tilde = zero; r%xi_mismatch = zero
r%xi_max = zero
r%y = zero; r%y_mismatch = zero
r%y_soft = zero
r%phi = zero
r%cms_energy2 = zero
r%xi_ref_momenta = vector4_null
r%jac_mismatch = one
r%jac_rand = one
end subroutine real_kinematics_init
@ %def real_kinematics_init
@
<<PHS fks: real kinematics: TBP>>=
procedure :: init_onshell => real_kinematics_init_onshell
<<PHS fks: sub interfaces>>=
module subroutine real_kinematics_init_onshell (r, n_tot, n_phs)
class(real_kinematics_t), intent(inout) :: r
integer, intent(in) :: n_tot, n_phs
end subroutine real_kinematics_init_onshell
<<PHS fks: procedures>>=
module subroutine real_kinematics_init_onshell (r, n_tot, n_phs)
class(real_kinematics_t), intent(inout) :: r
integer, intent(in) :: n_tot, n_phs
call r%p_born_onshell%init (n_tot - 1, 1)
call r%p_real_onshell(1)%init (n_tot, n_phs)
call r%p_real_onshell(2)%init (n_tot, n_phs)
end subroutine real_kinematics_init_onshell
@ %def real_kinematics_init_onshell
@
<<PHS fks: real kinematics: TBP>>=
procedure :: write => real_kinematics_write
<<PHS fks: sub interfaces>>=
module subroutine real_kinematics_write (r, unit)
class(real_kinematics_t), intent(in) :: r
integer, intent(in), optional :: unit
end subroutine real_kinematics_write
<<PHS fks: procedures>>=
module subroutine real_kinematics_write (r, unit)
class(real_kinematics_t), intent(in) :: r
integer, intent(in), optional :: unit
integer :: u, i
u = given_output_unit (unit); if (u < 0) return
write (u,"(A)") "Real kinematics: "
write (u,"(A," // FMT_17 // ",1X)") "xi_tilde: ", r%xi_tilde
write (u,"(A," // FMT_17 // ",1X)") "phi: ", r%phi
do i = 1, size (r%xi_max)
write (u,"(A,I1,1X)") "i_phs: ", i
write (u,"(A," // FMT_17 // ",1X)") "xi_max: ", r%xi_max(i)
write (u,"(A," // FMT_17 // ",1X)") "y: ", r%y(i)
write (u,"(A," // FMT_17 // ",1X)") "jac_rand: ", r%jac_rand(i)
write (u,"(A," // FMT_17 // ",1X)") "y_soft: ", r%y_soft(i)
end do
write (u, "(A)") "Born Momenta: "
write (u, "(A)") "CMS: "
call r%p_born_cms%write (unit = u)
write (u, "(A)") "Lab: "
call r%p_born_lab%write (unit = u)
write (u, "(A)") "Real Momenta: "
write (u, "(A)") "CMS: "
call r%p_real_cms%write (unit = u)
write (u, "(A)") "Lab: "
call r%p_real_lab%write (unit = u)
end subroutine real_kinematics_write
@ %def real_kinematics_write
@ The boost to the center-of-mass system only has a reasonable meaning
above the threshold. Below the threshold, we do not apply boost at all, so
that the top quarks stay in the rest frame. However, with top quarks exactly
at rest, problems arise in the matrix elements (e.g. in the computation
of angles). Therefore, we apply a boost which is not exactly 1, but has a
tiny value differing from that.
<<PHS fks: public>>=
public :: get_boost_for_threshold_projection
<<PHS fks: sub interfaces>>=
module function get_boost_for_threshold_projection &
(p, sqrts, mtop) result (L)
type(lorentz_transformation_t) :: L
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(in) :: sqrts, mtop
end function get_boost_for_threshold_projection
<<PHS fks: procedures>>=
module function get_boost_for_threshold_projection &
(p, sqrts, mtop) result (L)
type(lorentz_transformation_t) :: L
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(in) :: sqrts, mtop
type(vector4_t) :: p_tmp
type(vector3_t) :: dir
real(default) :: scale_factor, arg
p_tmp = p(THR_POS_WP) + p(THR_POS_B)
arg = sqrts**2 - four * mtop**2
if (arg > zero) then
scale_factor = sqrt (arg) / two
else
scale_factor = tiny_07*1000
end if
dir = scale_factor * create_unit_vector (p_tmp)
p_tmp = [sqrts / two, dir%p]
L = boost (p_tmp, mtop)
end function get_boost_for_threshold_projection
@ %def get_boost_for_threshold_projection
-@ This routine recomputes the value of $\phi$ used to generate the real phase space.
-<<PHS fks: procedures>>=
- function get_generation_phi (p_born, p_real, emitter, i_gluon) result (phi)
- real(default) :: phi
- type(vector4_t), intent(in), dimension(:) :: p_born, p_real
- integer, intent(in) :: emitter, i_gluon
- type(vector4_t) :: p1, p2, pp
- type(lorentz_transformation_t) :: rot_to_gluon, rot_to_z
- type(vector3_t) :: dir, z
- real(default) :: cpsi
- pp = p_real(emitter) + p_real(i_gluon)
- cpsi = (space_part_norm (pp)**2 - space_part_norm (p_real(emitter))**2 &
- + space_part_norm (p_real(i_gluon))**2) / &
- (two * space_part_norm (pp) * space_part_norm (p_real(i_gluon)))
- dir = create_orthogonal (space_part (p_born(emitter)))
- rot_to_gluon = rotation (cpsi, sqrt (one - cpsi**2), dir)
- pp = rot_to_gluon * p_born(emitter)
- z%p = [0._default, 0._default, 1._default]
- rot_to_z = rotation_to_2nd &
- (space_part (p_born(emitter)) / space_part_norm (p_born(emitter)), z)
- p1 = rot_to_z * pp / space_part_norm (pp)
- p2 = rot_to_z * p_real(i_gluon)
- phi = azimuthal_distance (p1, p2)
- if (phi < zero) phi = twopi - abs(phi)
- end function get_generation_phi
-
-@ %def get_generation_phi
@
<<PHS fks: real kinematics: TBP>>=
procedure :: apply_threshold_projection_real => &
real_kinematics_apply_threshold_projection_real
<<PHS fks: sub interfaces>>=
module subroutine real_kinematics_apply_threshold_projection_real &
(r, i_phs, mtop, L_to_cms, invert)
class(real_kinematics_t), intent(inout) :: r
integer, intent(in) :: i_phs
real(default), intent(in) :: mtop
type(lorentz_transformation_t), intent(in), dimension(:) :: L_to_cms
logical, intent(in) :: invert
end subroutine real_kinematics_apply_threshold_projection_real
<<PHS fks: procedures>>=
module subroutine real_kinematics_apply_threshold_projection_real &
(r, i_phs, mtop, L_to_cms, invert)
class(real_kinematics_t), intent(inout) :: r
integer, intent(in) :: i_phs
real(default), intent(in) :: mtop
type(lorentz_transformation_t), intent(in), dimension(:) :: L_to_cms
logical, intent(in) :: invert
integer :: leg, other_leg
type(vector4_t), dimension(:), allocatable :: p_real
type(vector4_t), dimension(:), allocatable :: p_real_onshell
type(vector4_t), dimension(4) :: k_tmp
type(vector4_t), dimension(4) :: k_decay_onshell_real
type(vector4_t), dimension(3) :: k_decay_onshell_born
do leg = 1, 2
other_leg = 3 - leg
p_real = r%p_real_cms%phs_point(i_phs)
allocate (p_real_onshell (size (p_real)))
p_real_onshell(1:2) = p_real(1:2)
k_tmp(1) = p_real(7)
k_tmp(2) = p_real(ass_quark(leg))
k_tmp(3) = p_real(ass_boson(leg))
k_tmp(4) = [mtop, zero, zero, zero]
call generate_on_shell_decay_threshold (k_tmp(1:3), &
k_tmp(4), k_decay_onshell_real (2:4))
k_decay_onshell_real (1) = k_tmp(4)
k_tmp(1) = p_real(ass_quark(other_leg))
k_tmp(2) = p_real(ass_boson(other_leg))
k_decay_onshell_born = create_two_particle_decay (mtop**2, k_tmp(1), k_tmp(2))
p_real_onshell(THR_POS_GLUON) = L_to_cms(leg) * k_decay_onshell_real (2)
p_real_onshell(ass_quark(leg)) = L_to_cms(leg) * k_decay_onshell_real(3)
p_real_onshell(ass_boson(leg)) = L_to_cms(leg) * k_decay_onshell_real(4)
p_real_onshell(ass_quark(other_leg)) = L_to_cms(leg) * k_decay_onshell_born (2)
p_real_onshell(ass_boson(other_leg)) = L_to_cms(leg) * k_decay_onshell_born (3)
if (invert) then
call vector4_invert_direction (p_real_onshell (ass_quark(other_leg)))
call vector4_invert_direction (p_real_onshell (ass_boson(other_leg)))
end if
r%p_real_onshell(leg)%phs_point(i_phs) = p_real_onshell
deallocate (p_real_onshell)
end do
end subroutine real_kinematics_apply_threshold_projection_real
@ %def real_kinematics_apply_threshold_projection_real
@
<<PHS fks: public>>=
public :: threshold_projection_born
<<PHS fks: sub interfaces>>=
module subroutine threshold_projection_born &
(mtop, L_to_cms, p_in, p_onshell)
real(default), intent(in) :: mtop
type(lorentz_transformation_t), intent(in) :: L_to_cms
type(vector4_t), intent(in), dimension(:) :: p_in
type(vector4_t), intent(out), dimension(:) :: p_onshell
end subroutine threshold_projection_born
<<PHS fks: procedures>>=
module subroutine threshold_projection_born &
(mtop, L_to_cms, p_in, p_onshell)
real(default), intent(in) :: mtop
type(lorentz_transformation_t), intent(in) :: L_to_cms
type(vector4_t), intent(in), dimension(:) :: p_in
type(vector4_t), intent(out), dimension(:) :: p_onshell
type(vector4_t), dimension(3) :: k_decay_onshell
type(vector4_t) :: p_tmp_1, p_tmp_2
type(lorentz_transformation_t) :: L_to_cms_inv
p_onshell(1:2) = p_in(1:2)
L_to_cms_inv = inverse (L_to_cms)
p_tmp_1 = L_to_cms_inv * p_in(THR_POS_B)
p_tmp_2 = L_to_cms_inv * p_in(THR_POS_WP)
k_decay_onshell = create_two_particle_decay (mtop**2, &
p_tmp_1, p_tmp_2)
p_onshell([THR_POS_B, THR_POS_WP]) = k_decay_onshell([2, 3])
p_tmp_1 = L_to_cms * p_in(THR_POS_BBAR)
p_tmp_2 = L_to_cms * p_in(THR_POS_WM)
k_decay_onshell = create_two_particle_decay (mtop**2, &
p_tmp_1, p_tmp_2)
p_onshell([THR_POS_BBAR, THR_POS_WM]) = k_decay_onshell([2, 3])
p_onshell([THR_POS_WP, THR_POS_B]) = L_to_cms * p_onshell([THR_POS_WP, THR_POS_B])
p_onshell([THR_POS_WM, THR_POS_BBAR]) = L_to_cms_inv * p_onshell([THR_POS_WM, THR_POS_BBAR])
end subroutine threshold_projection_born
@ %def threshold_projection_born
@ This routine computes the bounds of the Dalitz region for massive emitters.
The corresponding derivation can be found in [[1202.0465]], App. A.
It is also used for the POWHEG matching so the routine is public.
The input parameter [[m2]] corresponds to the squared mass of the emitter.
<<PHS fks: public>>=
public :: compute_dalitz_bounds
<<PHS fks: sub interfaces>>=
pure module subroutine compute_dalitz_bounds &
(q0, m2, mrec2, z1, z2, k0_rec_max)
real(default), intent(in) :: q0, m2, mrec2
real(default), intent(out) :: z1, z2, k0_rec_max
end subroutine compute_dalitz_bounds
<<PHS fks: procedures>>=
pure module subroutine compute_dalitz_bounds &
(q0, m2, mrec2, z1, z2, k0_rec_max)
real(default), intent(in) :: q0, m2, mrec2
real(default), intent(out) :: z1, z2, k0_rec_max
k0_rec_max = (q0**2 - m2 + mrec2) / (two * q0)
z1 = (k0_rec_max + sqrt(k0_rec_max**2 - mrec2)) / q0
z2 = (k0_rec_max - sqrt(k0_rec_max**2 - mrec2)) / q0
end subroutine compute_dalitz_bounds
@ %def compute_dalitz_bounds
@ Compute the [[kt2]] of a given emitter
<<PHS fks: real kinematics: TBP>>=
procedure :: kt2 => real_kinematics_kt2
<<PHS fks: sub interfaces>>=
module function real_kinematics_kt2 &
(real_kinematics, i_phs, emitter, kt2_type, xi, y) result (kt2)
real(default) :: kt2
class(real_kinematics_t), intent(in) :: real_kinematics
integer, intent(in) :: emitter, i_phs, kt2_type
real(default), intent(in), optional :: xi, y
end function real_kinematics_kt2
<<PHS fks: procedures>>=
module function real_kinematics_kt2 &
(real_kinematics, i_phs, emitter, kt2_type, xi, y) result (kt2)
real(default) :: kt2
class(real_kinematics_t), intent(in) :: real_kinematics
integer, intent(in) :: emitter, i_phs, kt2_type
real(default), intent(in), optional :: xi, y
real(default) :: xii, yy
real(default) :: q, E_em, z, z1, z2, m2, mrec2, k0_rec_max
type(vector4_t) :: p_emitter
if (present (y)) then
yy = y
else
yy = real_kinematics%y (i_phs)
end if
if (present (xi)) then
xii = xi
else
xii = real_kinematics%xi_tilde * real_kinematics%xi_max (i_phs)
end if
select case (kt2_type)
case (UBF_FSR_SIMPLE)
kt2 = real_kinematics%cms_energy2 / two * xii**2 * (1 - yy)
case (UBF_FSR_MASSIVE)
q = sqrt (real_kinematics%cms_energy2)
p_emitter = real_kinematics%p_born_cms%phs_point(1)%select (emitter)
mrec2 = (q - p_emitter%p(0))**2 - sum (p_emitter%p(1:3)**2)
m2 = p_emitter**2
E_em = energy (p_emitter)
call compute_dalitz_bounds (q, m2, mrec2, z1, z2, k0_rec_max)
z = z2 - (z2 - z1) * (one + yy) / two
kt2 = xii**2 * q**3 * (one - z) / &
(two * E_em - z * xii * q)
case (UBF_FSR_MASSLESS_RECOIL)
kt2 = real_kinematics%cms_energy2 / two * xii**2 * (1 - yy**2) / two
case (UBF_ISR)
kt2 = real_kinematics%cms_energy2 / four * xii**2 * (1 - yy**2) / (1 - xii)
case default
kt2 = zero
call msg_bug ("kt2_type must be set to a known value")
end select
end function real_kinematics_kt2
@ %def real_kinematics_kt2
@ These are the possible values for [[upper_bound_func_type]] and will be
used to decide which UBF object is allocated and which $K_T$ scale for the
matching is computed.
<<PHS fks: parameters>>=
integer, parameter, public :: UBF_FSR_SIMPLE = 1
integer, parameter, public :: UBF_FSR_MASSIVE = 2
integer, parameter, public :: UBF_FSR_MASSLESS_RECOIL = 3
integer, parameter, public :: UBF_ISR = 4
@ %def UBF_FSR_SIMPLE UBF_FSR_MASSIVE UBF_FSR_MASSLESS_RECOIL UBF_ISR
@
<<PHS fks: real kinematics: TBP>>=
procedure :: final => real_kinematics_final
<<PHS fks: sub interfaces>>=
module subroutine real_kinematics_final (real_kin)
class(real_kinematics_t), intent(inout) :: real_kin
end subroutine real_kinematics_final
<<PHS fks: procedures>>=
module subroutine real_kinematics_final (real_kin)
class(real_kinematics_t), intent(inout) :: real_kin
if (allocated (real_kin%xi_max)) deallocate (real_kin%xi_max)
if (allocated (real_kin%y)) deallocate (real_kin%y)
if (allocated (real_kin%alr_to_i_phs)) deallocate (real_kin%alr_to_i_phs)
if (allocated (real_kin%jac_rand)) deallocate (real_kin%jac_rand)
if (allocated (real_kin%y_soft)) deallocate (real_kin%y_soft)
if (allocated (real_kin%xi_ref_momenta)) &
deallocate (real_kin%xi_ref_momenta)
call real_kin%p_born_cms%final (); call real_kin%p_born_lab%final ()
call real_kin%p_real_cms%final (); call real_kin%p_real_lab%final ()
end subroutine real_kinematics_final
@ %def real_kinematics_final
@
<<PHS fks: parameters>>=
integer, parameter, public :: I_XI = 1
integer, parameter, public :: I_Y = 2
integer, parameter, public :: I_PHI = 3
integer, parameter, public :: PHS_MODE_UNDEFINED = 0
integer, parameter, public :: PHS_MODE_ADDITIONAL_PARTICLE = 1
integer, parameter, public :: PHS_MODE_COLLINEAR_REMNANT = 2
@ %def parameters
@
<<PHS fks: public>>=
public :: phs_fks_config_t
<<PHS fks: types>>=
type, extends (phs_wood_config_t) :: phs_fks_config_t
integer :: mode = PHS_MODE_UNDEFINED
character(32) :: md5sum_born_config
logical :: born_2_to_1 = .false.
logical :: make_dalitz_plot = .false.
contains
<<PHS fks: fks config: TBP>>
end type phs_fks_config_t
@ %def phs_fks_config_t
@
<<PHS fks: fks config: TBP>>=
procedure :: clear_phase_space => fks_config_clear_phase_space
<<PHS fks: sub interfaces>>=
module subroutine fks_config_clear_phase_space (phs_config)
class(phs_fks_config_t), intent(inout) :: phs_config
end subroutine fks_config_clear_phase_space
<<PHS fks: procedures>>=
module subroutine fks_config_clear_phase_space (phs_config)
class(phs_fks_config_t), intent(inout) :: phs_config
end subroutine fks_config_clear_phase_space
@ %def fks_config_clear_phase_space
@
<<PHS fks: fks config: TBP>>=
procedure :: write => phs_fks_config_write
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_config_write (object, unit, include_id)
class(phs_fks_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
end subroutine phs_fks_config_write
<<PHS fks: procedures>>=
module subroutine phs_fks_config_write (object, unit, include_id)
class(phs_fks_config_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: include_id
integer :: u
u = given_output_unit (unit)
call object%phs_wood_config_t%write (u)
write (u, "(3x,A,I0)") "NLO mode = ", object%mode
write (u, "(3x,A,L1)") "2->1 proc = ", object%born_2_to_1
write (u, "(3x,A,L1)") "Dalitz = ", object%make_dalitz_plot
write (u, "(A,A)") "Extra Born md5sum: ", object%md5sum_born_config
end subroutine phs_fks_config_write
@ %def phs_fks_config_write
@
<<PHS fks: fks config: TBP>>=
procedure :: set_mode => phs_fks_config_set_mode
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_config_set_mode (phs_config, mode)
class(phs_fks_config_t), intent(inout) :: phs_config
integer, intent(in) :: mode
end subroutine phs_fks_config_set_mode
<<PHS fks: procedures>>=
module subroutine phs_fks_config_set_mode (phs_config, mode)
class(phs_fks_config_t), intent(inout) :: phs_config
integer, intent(in) :: mode
select case (mode)
case (NLO_REAL, NLO_MISMATCH)
phs_config%mode = PHS_MODE_ADDITIONAL_PARTICLE
case (NLO_DGLAP)
phs_config%mode = PHS_MODE_COLLINEAR_REMNANT
end select
end subroutine phs_fks_config_set_mode
@ %def phs_fks_config_set_mod
@
<<PHS fks: fks config: TBP>>=
procedure :: configure => phs_fks_config_configure
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_fks_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
end subroutine phs_fks_config_configure
<<PHS fks: procedures>>=
module subroutine phs_fks_config_configure (phs_config, sqrts, &
sqrts_fixed, lab_is_cm, azimuthal_dependence, rebuild, &
ignore_mismatch, nlo_type, subdir)
class(phs_fks_config_t), intent(inout) :: phs_config
real(default), intent(in) :: sqrts
logical, intent(in), optional :: sqrts_fixed
logical, intent(in), optional :: lab_is_cm
logical, intent(in), optional :: azimuthal_dependence
logical, intent(in), optional :: rebuild
logical, intent(in), optional :: ignore_mismatch
integer, intent(in), optional :: nlo_type
type(string_t), intent(in), optional :: subdir
if (present (nlo_type)) phs_config%nlo_type = nlo_type
if (.not. phs_config%is_combined_integration) then
select case (phs_config%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
phs_config%n_par = phs_config%n_par + 3
if (phs_config%nlo_type == NLO_REAL .and. phs_config%n_out == 2) then
phs_config%born_2_to_1 = .true.
end if
case (PHS_MODE_COLLINEAR_REMNANT)
phs_config%n_par = phs_config%n_par + 1
end select
end if
call phs_config%compute_md5sum ()
end subroutine phs_fks_config_configure
@ %def phs_fks_config_configure
@
<<PHS fks: fks config: TBP>>=
procedure :: startup_message => phs_fks_config_startup_message
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_config_startup_message (phs_config, unit)
class(phs_fks_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
end subroutine phs_fks_config_startup_message
<<PHS fks: procedures>>=
module subroutine phs_fks_config_startup_message (phs_config, unit)
class(phs_fks_config_t), intent(in) :: phs_config
integer, intent(in), optional :: unit
call phs_config%phs_wood_config_t%startup_message (unit)
end subroutine phs_fks_config_startup_message
@ %def phs_fks_config_startup_message
@ Gfortran 7/8/9 bug, has to remain in the main module:
<<PHS fks: fks config: TBP>>=
procedure, nopass :: allocate_instance => phs_fks_config_allocate_instance
<<PHS fks: main procedures>>=
subroutine phs_fks_config_allocate_instance (phs)
class(phs_t), intent(inout), pointer :: phs
allocate (phs_fks_t :: phs)
end subroutine phs_fks_config_allocate_instance
@ %def phs_fks_config_allocate_instance
@ If the phase space is generated from file, but we want to have resonance
histories, we must force the cascade sets to be generated. However, it must
be assured that Born flavors are used for this.
<<PHS fks: fks config: TBP>>=
procedure :: generate_phase_space_extra => &
phs_fks_config_generate_phase_space_extra
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_config_generate_phase_space_extra (phs_config)
class(phs_fks_config_t), intent(inout) :: phs_config
end subroutine phs_fks_config_generate_phase_space_extra
<<PHS fks: procedures>>=
module subroutine phs_fks_config_generate_phase_space_extra (phs_config)
class(phs_fks_config_t), intent(inout) :: phs_config
integer :: off_shell, extra_off_shell
type(flavor_t), dimension(:,:), allocatable :: flv_born
integer :: i, j
integer :: n_state, n_flv_born
integer :: unit_fds
logical :: valid
type(string_t) :: file_name
logical :: file_exists
if (phs_config%use_cascades2) then
allocate (phs_config%feyngraph_set)
else
allocate (phs_config%cascade_set)
end if
n_flv_born = size (phs_config%flv, 1) - 1
n_state = size (phs_config%flv, 2)
allocate (flv_born (n_flv_born, n_state))
do i = 1, n_flv_born
do j = 1, n_state
flv_born(i, j) = phs_config%flv(i, j)
end do
end do
if (phs_config%use_cascades2) then
file_name = char (phs_config%id) // ".fds"
inquire (file=char (file_name), exist=file_exists)
if (.not. file_exists) call msg_fatal &
("The O'Mega input file " // char (file_name) // &
" does not exist. " // "Please make sure that the " // &
"variable ?omega_write_phs_output has been set correctly.")
unit_fds = free_unit ()
open (unit=unit_fds, file=char(file_name), status='old', action='read')
end if
off_shell = phs_config%par%off_shell
do extra_off_shell = 0, max (n_flv_born - 2, 0)
phs_config%par%off_shell = off_shell + extra_off_shell
if (phs_config%use_cascades2) then
call feyngraph_set_generate (phs_config%feyngraph_set, &
phs_config%model, phs_config%n_in, phs_config%n_out - 1, &
flv_born, phs_config%par, phs_config%fatal_beam_decay, unit_fds, &
phs_config%vis_channels)
if (feyngraph_set_is_valid (phs_config%feyngraph_set)) exit
else
call cascade_set_generate (phs_config%cascade_set, &
phs_config%model, phs_config%n_in, phs_config%n_out - 1, &
flv_born, phs_config%par, phs_config%fatal_beam_decay)
if (cascade_set_is_valid (phs_config%cascade_set)) exit
end if
end do
if (phs_config%use_cascades2) then
close (unit_fds)
valid = feyngraph_set_is_valid (phs_config%feyngraph_set)
else
valid = cascade_set_is_valid (phs_config%cascade_set)
end if
if (.not. valid) &
call msg_fatal ("Resonance extraction: Phase space generation failed")
end subroutine phs_fks_config_generate_phase_space_extra
@ %def phs_fks_config_generate_phase_space_extra
@
<<PHS fks: fks config: TBP>>=
procedure :: set_born_config => phs_fks_config_set_born_config
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_config_set_born_config (phs_config, phs_cfg_born)
class(phs_fks_config_t), intent(inout) :: phs_config
type(phs_wood_config_t), intent(in), target :: phs_cfg_born
end subroutine phs_fks_config_set_born_config
<<PHS fks: procedures>>=
module subroutine phs_fks_config_set_born_config (phs_config, phs_cfg_born)
class(phs_fks_config_t), intent(inout) :: phs_config
type(phs_wood_config_t), intent(in), target :: phs_cfg_born
if (debug_on) &
call msg_debug (D_PHASESPACE, "phs_fks_config_set_born_config")
phs_config%forest = phs_cfg_born%forest
phs_config%n_channel = phs_cfg_born%n_channel
allocate (phs_config%channel (phs_config%n_channel))
phs_config%channel = phs_cfg_born%channel
phs_config%n_par = phs_cfg_born%n_par
phs_config%n_state = phs_cfg_born%n_state
phs_config%sqrts = phs_cfg_born%sqrts
phs_config%par = phs_cfg_born%par
phs_config%sqrts_fixed = phs_cfg_born%sqrts_fixed
phs_config%azimuthal_dependence = phs_cfg_born%azimuthal_dependence
phs_config%provides_chains = phs_cfg_born%provides_chains
phs_config%lab_is_cm = phs_cfg_born%lab_is_cm
phs_config%vis_channels = phs_cfg_born%vis_channels
phs_config%provides_equivalences = phs_cfg_born%provides_equivalences
allocate (phs_config%chain (size (phs_cfg_born%chain)))
phs_config%chain = phs_cfg_born%chain
phs_config%model => phs_cfg_born%model
phs_config%use_cascades2 = phs_cfg_born%use_cascades2
if (allocated (phs_cfg_born%cascade_set)) then
allocate (phs_config%cascade_set)
phs_config%cascade_set = phs_cfg_born%cascade_set
end if
if (allocated (phs_cfg_born%feyngraph_set)) then
allocate (phs_config%feyngraph_set)
phs_config%feyngraph_set = phs_cfg_born%feyngraph_set
end if
phs_config%md5sum_born_config = phs_cfg_born%md5sum_phs_config
end subroutine phs_fks_config_set_born_config
@ %def phs_fks_config_set_born_config
@
<<PHS fks: fks config: TBP>>=
procedure :: get_resonance_histories => &
phs_fks_config_get_resonance_histories
<<PHS fks: sub interfaces>>=
module function phs_fks_config_get_resonance_histories &
(phs_config) result (resonance_histories)
type(resonance_history_t), dimension(:), allocatable :: &
resonance_histories
class(phs_fks_config_t), intent(inout) :: phs_config
end function phs_fks_config_get_resonance_histories
<<PHS fks: procedures>>=
module function phs_fks_config_get_resonance_histories &
(phs_config) result (resonance_histories)
type(resonance_history_t), dimension(:), allocatable :: resonance_histories
class(phs_fks_config_t), intent(inout) :: phs_config
if (allocated (phs_config%cascade_set)) then
call cascade_set_get_resonance_histories (phs_config%cascade_set, &
n_filter = 2, res_hists = resonance_histories)
else if (allocated (phs_config%feyngraph_set)) then
call feyngraph_set_get_resonance_histories (phs_config%feyngraph_set, &
n_filter = 2, res_hists = resonance_histories)
else
if (debug_on) call msg_debug (D_PHASESPACE, "Have to rebuild phase space for resonance histories")
call phs_config%generate_phase_space_extra ()
if (phs_config%use_cascades2) then
call feyngraph_set_get_resonance_histories &
(phs_config%feyngraph_set, n_filter = 2, res_hists = resonance_histories)
else
call cascade_set_get_resonance_histories &
(phs_config%cascade_set, n_filter = 2, res_hists = resonance_histories)
end if
end if
end function phs_fks_config_get_resonance_histories
@ %def phs_fks_config_get_resonance_histories
@
<<PHS fks: public>>=
public :: dalitz_plot_t
<<PHS fks: types>>=
type :: dalitz_plot_t
integer :: unit = -1
type(string_t) :: filename
logical :: active = .false.
logical :: inverse = .false.
contains
<<PHS fks: dalitz plot: TBP>>
end type dalitz_plot_t
@ %def dalitz_plot_t
@
<<PHS fks: dalitz plot: TBP>>=
procedure :: init => dalitz_plot_init
<<PHS fks: sub interfaces>>=
module subroutine dalitz_plot_init (plot, unit, filename, inverse)
class(dalitz_plot_t), intent(inout) :: plot
integer, intent(in) :: unit
type(string_t), intent(in) :: filename
logical, intent(in) :: inverse
end subroutine dalitz_plot_init
<<PHS fks: procedures>>=
module subroutine dalitz_plot_init (plot, unit, filename, inverse)
class(dalitz_plot_t), intent(inout) :: plot
integer, intent(in) :: unit
type(string_t), intent(in) :: filename
logical, intent(in) :: inverse
plot%active = .true.
plot%unit = unit
plot%inverse = inverse
open (plot%unit, file = char (filename), action = "write")
end subroutine dalitz_plot_init
@ %def daltiz_plot_init
@
<<PHS fks: dalitz plot: TBP>>=
procedure :: write_header => dalitz_plot_write_header
<<PHS fks: sub interfaces>>=
module subroutine dalitz_plot_write_header (plot)
class(dalitz_plot_t), intent(in) :: plot
end subroutine dalitz_plot_write_header
<<PHS fks: procedures>>=
module subroutine dalitz_plot_write_header (plot)
class(dalitz_plot_t), intent(in) :: plot
write (plot%unit, "(A36)") "### Dalitz plot generated by WHIZARD"
if (plot%inverse) then
write (plot%unit, "(A10,1x,A4)") "### k0_n+1", "k0_n"
else
write (plot%unit, "(A8,1x,A6)") "### k0_n", "k0_n+1"
end if
end subroutine dalitz_plot_write_header
@ %def dalitz_plot_write_header
@
<<PHS fks: dalitz plot: TBP>>=
procedure :: register => dalitz_plot_register
<<PHS fks: sub interfaces>>=
module subroutine dalitz_plot_register (plot, k0_n, k0_np1)
class(dalitz_plot_t), intent(in) :: plot
real(default), intent(in) :: k0_n, k0_np1
end subroutine dalitz_plot_register
<<PHS fks: procedures>>=
module subroutine dalitz_plot_register (plot, k0_n, k0_np1)
class(dalitz_plot_t), intent(in) :: plot
real(default), intent(in) :: k0_n, k0_np1
if (plot%inverse) then
write (plot%unit, "(F8.4,1X,F8.4)") k0_np1, k0_n
else
write (plot%unit, "(F8.4,1X,F8.4)") k0_np1, k0_n
end if
end subroutine dalitz_plot_register
@ %def dalitz_plot_register
@
<<PHS fks: dalitz plot: TBP>>=
procedure :: final => dalitz_plot_final
<<PHS fks: sub interfaces>>=
module subroutine dalitz_plot_final (plot)
class(dalitz_plot_t), intent(inout) :: plot
end subroutine dalitz_plot_final
<<PHS fks: procedures>>=
module subroutine dalitz_plot_final (plot)
class(dalitz_plot_t), intent(inout) :: plot
logical :: opened
plot%active = .false.
plot%inverse = .false.
if (plot%unit >= 0) then
inquire (unit = plot%unit, opened = opened)
if (opened) close (plot%unit)
end if
plot%filename = var_str ('')
plot%unit = -1
end subroutine dalitz_plot_final
@ %def dalitz_plot_final
@
<<PHS fks: parameters>>=
integer, parameter, public :: GEN_REAL_PHASE_SPACE = 1
integer, parameter, public :: GEN_SOFT_MISMATCH = 2
integer, parameter, public :: GEN_SOFT_LIMIT_TEST = 3
integer, parameter, public :: GEN_COLL_LIMIT_TEST = 4
integer, parameter, public :: GEN_ANTI_COLL_LIMIT_TEST = 5
integer, parameter, public :: GEN_SOFT_COLL_LIMIT_TEST = 6
integer, parameter, public :: GEN_SOFT_ANTI_COLL_LIMIT_TEST = 7
integer, parameter, public :: SQRTS_FIXED = 1
integer, parameter, public :: SQRTS_VAR = 2
real(default), parameter :: xi_tilde_test_soft = 0.00001_default
real(default), parameter :: xi_tilde_test_coll = 0.5_default
real(default), parameter :: y_test_soft = 0.5_default
real(default), parameter :: y_test_coll = 0.9999999_default
!!! for testing EW singularities: y_test_coll = 0.99999999_default
@
@ Very soft or collinear phase-space points can become a problem for
matrix elements providers, as some scalar products cannot be evaluated
properly. Here, a nonsensical result can spoil the whole integration.
We therefore check the scalar products appearing to be below a certain
tolerance.\\
Naturally, this happens very rarely but for some processes,
setting [[?test_coll_limit = true]] and/or [[?test_soft_limit = true]]
leads to all phase space points beeing discarded by this routine.
<<PHS fks: public>>=
public :: check_scalar_products
<<PHS fks: sub interfaces>>=
module function check_scalar_products (p) result (valid)
logical :: valid
type(vector4_t), intent(in), dimension(:) :: p
end function check_scalar_products
<<PHS fks: procedures>>=
module function check_scalar_products (p) result (valid)
logical :: valid
type(vector4_t), intent(in), dimension(:) :: p
real(default), parameter :: tolerance = 1E-7_default
!!! for testing EW singularities: tolerance = 5E-9_default
integer :: i, j
valid = .true.
do i = 1, size (p)
do j = i, size (p)
if (i /= j) then
if (abs(p(i) * p(j)) < tolerance) then
valid = .false.
exit
end if
end if
end do
end do
end function check_scalar_products
@ %def check_scalar_products
@ [[xi_min]] should be set to a non-zero value in order to avoid
phase-space points with [[p_real(emitter) = 0]].
<<PHS fks: public>>=
public :: phs_fks_generator_t
<<PHS fks: types>>=
type :: phs_fks_generator_t
integer, dimension(:), allocatable :: emitters
type(real_kinematics_t), pointer :: real_kinematics => null()
type(isr_kinematics_t), pointer :: isr_kinematics => null()
integer :: n_in
real(default) :: xi_min
real(default) :: y_max
real(default) :: sqrts
real(default) :: E_gluon
real(default) :: mrec2
real(default), dimension(:), allocatable :: m2
logical :: massive_phsp = .false.
logical, dimension(:), allocatable :: is_massive
logical :: singular_jacobian = .false.
integer :: i_fsr_first = -1
type(resonance_contributors_t), dimension(:), allocatable :: resonance_contributors !!! Put somewhere else?
integer :: mode = GEN_REAL_PHASE_SPACE
contains
<<PHS fks: phs fks generator: TBP>>
end type phs_fks_generator_t
@ %def phs_fks_generator_t
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: connect_kinematics => phs_fks_generator_connect_kinematics
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_connect_kinematics &
(generator, isr_kinematics, real_kinematics, massive_phsp)
class(phs_fks_generator_t), intent(inout) :: generator
type(isr_kinematics_t), intent(in), pointer :: isr_kinematics
type(real_kinematics_t), intent(in), pointer :: real_kinematics
logical, intent(in) :: massive_phsp
end subroutine phs_fks_generator_connect_kinematics
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_connect_kinematics &
(generator, isr_kinematics, real_kinematics, massive_phsp)
class(phs_fks_generator_t), intent(inout) :: generator
type(isr_kinematics_t), intent(in), pointer :: isr_kinematics
type(real_kinematics_t), intent(in), pointer :: real_kinematics
logical, intent(in) :: massive_phsp
generator%real_kinematics => real_kinematics
generator%isr_kinematics => isr_kinematics
generator%massive_phsp = massive_phsp
end subroutine phs_fks_generator_connect_kinematics
@ %def phs_fks_generator_connect_kinematics
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_isr_kinematics => &
phs_fks_generator_compute_isr_kinematics
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_isr_kinematics &
(generator, r, p_in)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r
type(vector4_t), dimension(2), intent(in), optional :: p_in
end subroutine phs_fks_generator_compute_isr_kinematics
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_isr_kinematics &
(generator, r, p_in)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r
type(vector4_t), dimension(2), intent(in), optional :: p_in
integer :: em
type(vector4_t), dimension(2) :: p
if (present (p_in)) then
p = p_in
else
p = generator%real_kinematics%p_born_lab%phs_point(1)%select ([1,2])
end if
associate (isr_kinematics => generator%isr_kinematics)
do em = 1, 2
isr_kinematics%x(em) = p(em)%p(0) / isr_kinematics%beam_energy(em)
isr_kinematics%z(em) = one - (one - isr_kinematics%x(em)) * r
isr_kinematics%jacobian(em) = one - isr_kinematics%x(em)
end do
isr_kinematics%sqrts_born = (p(1) + p(2))**1
end associate
end subroutine phs_fks_generator_compute_isr_kinematics
@ %def phs_fks_generator_compute_isr_kinematics
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: final => phs_fks_generator_final
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_final (generator)
class(phs_fks_generator_t), intent(inout) :: generator
end subroutine phs_fks_generator_final
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_final (generator)
class(phs_fks_generator_t), intent(inout) :: generator
if (allocated (generator%emitters)) deallocate (generator%emitters)
if (associated (generator%real_kinematics)) &
nullify (generator%real_kinematics)
if (associated (generator%isr_kinematics)) &
nullify (generator%isr_kinematics)
if (allocated (generator%m2)) deallocate (generator%m2)
generator%massive_phsp = .false.
if (allocated (generator%is_massive)) deallocate (generator%is_massive)
generator%singular_jacobian = .false.
generator%i_fsr_first = -1
if (allocated (generator%resonance_contributors)) &
deallocate (generator%resonance_contributors)
generator%mode = GEN_REAL_PHASE_SPACE
end subroutine phs_fks_generator_final
@ %def phs_fks_generator_final
@ A resonance phase space is uniquely specified via the resonance contributors and the
corresponding emitters. The [[phs_identifier]] type also checks whether
the given contributor-emitter configuration has already been evaluated to
avoid duplicate computations.
<<PHS fks: public>>=
public :: phs_identifier_t
<<PHS fks: types>>=
type :: phs_identifier_t
integer, dimension(:), allocatable :: contributors
integer :: emitter = -1
logical :: evaluated = .false.
contains
<<PHS fks: phs identifier: TBP>>
end type phs_identifier_t
@ %def phs_identifier_t
@
<<PHS fks: phs identifier: TBP>>=
generic :: init => init_from_emitter, init_from_emitter_and_contributors
procedure :: init_from_emitter => phs_identifier_init_from_emitter
procedure :: init_from_emitter_and_contributors &
=> phs_identifier_init_from_emitter_and_contributors
<<PHS fks: sub interfaces>>=
module subroutine phs_identifier_init_from_emitter (phs_id, emitter)
class(phs_identifier_t), intent(out) :: phs_id
integer, intent(in) :: emitter
end subroutine phs_identifier_init_from_emitter
module subroutine phs_identifier_init_from_emitter_and_contributors &
(phs_id, emitter, contributors)
class(phs_identifier_t), intent(out) :: phs_id
integer, intent(in) :: emitter
integer, intent(in), dimension(:) :: contributors
end subroutine phs_identifier_init_from_emitter_and_contributors
<<PHS fks: procedures>>=
module subroutine phs_identifier_init_from_emitter (phs_id, emitter)
class(phs_identifier_t), intent(out) :: phs_id
integer, intent(in) :: emitter
phs_id%emitter = emitter
end subroutine phs_identifier_init_from_emitter
module subroutine phs_identifier_init_from_emitter_and_contributors &
(phs_id, emitter, contributors)
class(phs_identifier_t), intent(out) :: phs_id
integer, intent(in) :: emitter
integer, intent(in), dimension(:) :: contributors
allocate (phs_id%contributors (size (contributors)))
phs_id%contributors = contributors
phs_id%emitter = emitter
end subroutine phs_identifier_init_from_emitter_and_contributors
@ %def phs_identifier_init_from_emitter
@ %def phs_identifier_init_from_emitter_and_contributors
@
<<PHS fks: phs identifier: TBP>>=
procedure :: check => phs_identifier_check
<<PHS fks: sub interfaces>>=
module function phs_identifier_check &
(phs_id, emitter, contributors) result (check)
logical :: check
class(phs_identifier_t), intent(in) :: phs_id
integer, intent(in) :: emitter
integer, intent(in), dimension(:), optional :: contributors
end function phs_identifier_check
<<PHS fks: procedures>>=
module function phs_identifier_check &
(phs_id, emitter, contributors) result (check)
logical :: check
class(phs_identifier_t), intent(in) :: phs_id
integer, intent(in) :: emitter
integer, intent(in), dimension(:), optional :: contributors
check = phs_id%emitter == emitter
if (present (contributors)) then
if (.not. allocated (phs_id%contributors)) &
call msg_fatal ("Phs identifier: contributors not allocated!")
check = check .and. all (phs_id%contributors == contributors)
end if
end function phs_identifier_check
@ %def phs_identifier_check
@
<<PHS fks: phs identifier: TBP>>=
procedure :: write => phs_identifier_write
<<PHS fks: sub interfaces>>=
module subroutine phs_identifier_write (phs_id, unit)
class(phs_identifier_t), intent(in) :: phs_id
integer, intent(in), optional :: unit
end subroutine phs_identifier_write
<<PHS fks: procedures>>=
module subroutine phs_identifier_write (phs_id, unit)
class(phs_identifier_t), intent(in) :: phs_id
integer, intent(in), optional :: unit
integer :: u, i
u = given_output_unit (unit); if (u < 0) return
write (u, '(A)') 'phs_identifier: '
write (u, '(A,1X,I1)') 'Emitter: ', phs_id%emitter
if (allocated (phs_id%contributors)) then
write (u, '(A)', advance = 'no') 'Resonance contributors: '
do i = 1, size (phs_id%contributors)
write (u, '(I1,1X)', advance = 'no') phs_id%contributors(i)
end do
else
write (u, '(A)') 'No Contributors allocated'
end if
end subroutine phs_identifier_write
@ %def phs_identifier_write
@
<<PHS fks: public>>=
public :: check_for_phs_identifier
<<PHS fks: sub interfaces>>=
module subroutine check_for_phs_identifier &
(phs_id, n_in, emitter, contributors, phs_exist, i_phs)
type(phs_identifier_t), intent(in), dimension(:) :: phs_id
integer, intent(in) :: n_in, emitter
integer, intent(in), dimension(:), optional :: contributors
logical, intent(out) :: phs_exist
integer, intent(out) :: i_phs
end subroutine check_for_phs_identifier
<<PHS fks: procedures>>=
module subroutine check_for_phs_identifier &
(phs_id, n_in, emitter, contributors, phs_exist, i_phs)
type(phs_identifier_t), intent(in), dimension(:) :: phs_id
integer, intent(in) :: n_in, emitter
integer, intent(in), dimension(:), optional :: contributors
logical, intent(out) :: phs_exist
integer, intent(out) :: i_phs
integer :: i
phs_exist = .false.
i_phs = -1
do i = 1, size (phs_id)
if (phs_id(i)%emitter < 0) then
i_phs = i
exit
end if
phs_exist = phs_id(i)%emitter == emitter
if (present (contributors)) &
phs_exist = phs_exist .and. &
all (phs_id(i)%contributors == contributors)
if (phs_exist) then
i_phs = i
exit
end if
end do
end subroutine check_for_phs_identifier
@ %def check_for_phs_identifier
@
@ The fks phase space type contains the wood phase space and
separately the in- and outcoming momenta for the real process and the
corresponding Born momenta. Additionally, there are the variables
$\xi$,$\xi_{max}$, $y$ and $\phi$ which are used to create the real
phase space, as well as the jacobian and its corresponding soft and
collinear limit. Lastly, the array \texttt{ch\_to\_em} connects each
channel with an emitter.
<<PHS fks: public>>=
public :: phs_fks_t
<<PHS fks: types>>=
type, extends (phs_wood_t) :: phs_fks_t
integer :: mode = PHS_MODE_UNDEFINED
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: q_born
type(vector4_t), dimension(:), allocatable :: p_real
type(vector4_t), dimension(:), allocatable :: q_real
type(vector4_t), dimension(:), allocatable :: p_born_tot
type(phs_fks_generator_t) :: generator
real(default) :: r_isr
type(phs_identifier_t), dimension(:), allocatable :: phs_identifiers
contains
<<PHS fks: phs fks: TBP>>
end type phs_fks_t
@ %def phs_fks_t
@
<<PHS fks: interfaces>>=
interface compute_beta
module procedure compute_beta_massless
module procedure compute_beta_massive
end interface
interface get_xi_max_fsr
module procedure get_xi_max_fsr_massless
module procedure get_xi_max_fsr_massive
end interface
@ %def interfaces
@
<<PHS fks: phs fks: TBP>>=
procedure :: write => phs_fks_write
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_write (object, unit, verbose)
class(phs_fks_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
end subroutine phs_fks_write
<<PHS fks: procedures>>=
module subroutine phs_fks_write (object, unit, verbose)
class(phs_fks_t), intent(in) :: object
integer, intent(in), optional :: unit
logical, intent(in), optional :: verbose
integer :: u, i, n_id
u = given_output_unit (unit)
call object%base_write ()
n_id = size (object%phs_identifiers)
if (n_id == 0) then
write (u, "(A)") "No phs identifiers allocated! "
else
do i = 1, n_id
call object%phs_identifiers(i)%write (u)
end do
end if
end subroutine phs_fks_write
@ %def phs_fks_write
@ Initializer for the phase space. Calls the initialization of the
corresponding Born phase space, sets up the
channel-emitter-association and allocates space for the momenta.
<<PHS fks: phs fks: TBP>>=
procedure :: init => phs_fks_init
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_init (phs, phs_config)
class(phs_fks_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
end subroutine phs_fks_init
<<PHS fks: procedures>>=
module subroutine phs_fks_init (phs, phs_config)
class(phs_fks_t), intent(out) :: phs
class(phs_config_t), intent(in), target :: phs_config
call phs%base_init (phs_config)
select type (phs_config)
type is (phs_fks_config_t)
phs%config => phs_config
phs%forest = phs_config%forest
end select
select type (phs)
type is (phs_fks_t)
select type (phs_config)
type is (phs_fks_config_t)
phs%mode = phs_config%mode
end select
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
phs%n_r_born = phs%config%n_par - 3
case (PHS_MODE_COLLINEAR_REMNANT)
phs%n_r_born = phs%config%n_par - 1
end select
end select
end subroutine phs_fks_init
@ %def phs_fks_init
@ For real components of $2\to 1$ NLO processes we have to recompute the
flux factor as this has to be the one of the underlying Born.
<<PHS fks: phs fks: TBP>>=
procedure :: compute_flux => phs_fks_compute_flux
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_flux (phs)
class(phs_fks_t), intent(inout) :: phs
end subroutine phs_fks_compute_flux
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_flux (phs)
class(phs_fks_t), intent(inout) :: phs
call phs%compute_base_flux ()
select type (config => phs%config)
type is (phs_fks_config_t)
if (config%born_2_to_1) then
phs%flux = conv * twopi &
/ (2 * config%sqrts ** 2 * phs%m_out(1) ** 2)
end if
end select
end subroutine phs_fks_compute_flux
@ %def phs_fks_compute_flux
@
<<PHS fks: phs fks: TBP>>=
procedure :: allocate_momenta => phs_fks_allocate_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_allocate_momenta (phs, phs_config, data_is_born)
class(phs_fks_t), intent(inout) :: phs
class(phs_config_t), intent(in) :: phs_config
logical, intent(in) :: data_is_born
end subroutine phs_fks_allocate_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_allocate_momenta (phs, phs_config, data_is_born)
class(phs_fks_t), intent(inout) :: phs
class(phs_config_t), intent(in) :: phs_config
logical, intent(in) :: data_is_born
integer :: n_out_born
allocate (phs%p_born (phs_config%n_in))
allocate (phs%p_real (phs_config%n_in))
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
if (data_is_born) then
n_out_born = phs_config%n_out
else
n_out_born = phs_config%n_out - 1
end if
allocate (phs%q_born (n_out_born))
allocate (phs%q_real (n_out_born + 1))
allocate (phs%p_born_tot (phs_config%n_in + n_out_born))
end select
end subroutine phs_fks_allocate_momenta
@ %def phs_fks_allocate_momenta
@ Evaluate selected channel. First, the subroutine calls the
evaluation procedure of the underlying Born phase space, using $n_r -
3$ random numbers. Then, the remaining three random numbers are used
to create $\xi$, $y$ and $\phi$, from which the real momenta are
calculated from the Born momenta.
<<PHS fks: phs fks: TBP>>=
procedure :: evaluate_selected_channel => phs_fks_evaluate_selected_channel
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_evaluate_selected_channel (phs, c_in, r_in)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
end subroutine phs_fks_evaluate_selected_channel
<<PHS fks: procedures>>=
module subroutine phs_fks_evaluate_selected_channel (phs, c_in, r_in)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: c_in
real(default), intent(in), dimension(:) :: r_in
integer :: n_in
call phs%phs_wood_t%evaluate_selected_channel (c_in, r_in)
phs%r(:,c_in) = r_in
phs%q_defined = phs%phs_wood_t%q_defined
if (.not. phs%q_defined) return
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
n_in = phs%config%n_in
phs%p_born = phs%phs_wood_t%p
phs%q_born = phs%phs_wood_t%q
phs%p_born_tot (1: n_in) = phs%p_born
phs%p_born_tot (n_in + 1 :) = phs%q_born
call phs%set_reference_frames (.true.)
call phs%set_isr_kinematics (.true.)
case (PHS_MODE_COLLINEAR_REMNANT)
call phs%compute_isr_kinematics (r_in(phs%n_r_born + 1))
phs%r_isr = r_in(phs%n_r_born + 1)
end select
end subroutine phs_fks_evaluate_selected_channel
@ %def phs_fks_evaluate_selected_channel
@
<<PHS fks: phs fks: TBP>>=
procedure :: evaluate_other_channels => phs_fks_evaluate_other_channels
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_evaluate_other_channels (phs, c_in)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: c_in
end subroutine phs_fks_evaluate_other_channels
<<PHS fks: procedures>>=
module subroutine phs_fks_evaluate_other_channels (phs, c_in)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: c_in
call phs%phs_wood_t%evaluate_other_channels (c_in)
phs%r_defined = .true.
end subroutine phs_fks_evaluate_other_channels
@ %def phs_fks_evaluate_other_channels
@
<<PHS fks: phs fks: TBP>>=
procedure :: get_mcpar => phs_fks_get_mcpar
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_get_mcpar (phs, c, r)
class(phs_fks_t), intent(in) :: phs
integer, intent(in) :: c
real(default), dimension(:), intent(out) :: r
end subroutine phs_fks_get_mcpar
<<PHS fks: procedures>>=
module subroutine phs_fks_get_mcpar (phs, c, r)
class(phs_fks_t), intent(in) :: phs
integer, intent(in) :: c
real(default), dimension(:), intent(out) :: r
r(1 : phs%n_r_born) = phs%r(1 : phs%n_r_born,c)
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
r(phs%n_r_born + 1 :) = phs%r_real
case (PHS_MODE_COLLINEAR_REMNANT)
r(phs%n_r_born + 1 :) = phs%r_isr
end select
end subroutine phs_fks_get_mcpar
@ %def phs_fks_get_mcpar
@
<<PHS fks: phs fks: TBP>>=
procedure :: set_beam_energy => phs_fks_set_beam_energy
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_set_beam_energy (phs)
class(phs_fks_t), intent(inout) :: phs
end subroutine phs_fks_set_beam_energy
<<PHS fks: procedures>>=
module subroutine phs_fks_set_beam_energy (phs)
class(phs_fks_t), intent(inout) :: phs
call phs%generator%set_sqrts_hat (phs%config%sqrts)
end subroutine phs_fks_set_beam_energy
@ %def phs_fks_set_beam_energy
@
<<PHS fks: phs fks: TBP>>=
procedure :: set_emitters => phs_fks_set_emitters
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_set_emitters (phs, emitters)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in), dimension(:), allocatable :: emitters
end subroutine phs_fks_set_emitters
<<PHS fks: procedures>>=
module subroutine phs_fks_set_emitters (phs, emitters)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in), dimension(:), allocatable :: emitters
call phs%generator%set_emitters (emitters)
end subroutine phs_fks_set_emitters
@ %def phs_fks_set_emitters
@
<<PHS fks: phs fks: TBP>>=
procedure :: set_momenta => phs_fks_set_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_set_momenta (phs, p)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(in), dimension(:) :: p
end subroutine phs_fks_set_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_set_momenta (phs, p)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(in), dimension(:) :: p
integer :: n_in, n_tot_born
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
n_in = phs%config%n_in; n_tot_born = phs%config%n_tot - 1
phs%p_born = p(1 : n_in)
phs%q_born = p(n_in + 1 : n_tot_born)
phs%p_born_tot = p
end select
end subroutine phs_fks_set_momenta
@ %def phs_fks_set_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: setup_masses => phs_fks_setup_masses
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_setup_masses (phs, n_tot)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: n_tot
end subroutine phs_fks_setup_masses
<<PHS fks: procedures>>=
module subroutine phs_fks_setup_masses (phs, n_tot)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: n_tot
call phs%generator%setup_masses (n_tot)
end subroutine phs_fks_setup_masses
@ %def phs_fks_setup_masses
@
<<PHS fks: phs fks: TBP>>=
procedure :: get_born_momenta => phs_fks_get_born_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_get_born_momenta (phs, p)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(out), dimension(:) :: p
end subroutine phs_fks_get_born_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_get_born_momenta (phs, p)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(out), dimension(:) :: p
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
p(1 : phs%config%n_in) = phs%p_born
p(phs%config%n_in + 1 :) = phs%q_born
case (PHS_MODE_COLLINEAR_REMNANT)
p(1:phs%config%n_in) = phs%phs_wood_t%p
p(phs%config%n_in + 1 : ) = phs%phs_wood_t%q
end select
if (.not. phs%config%lab_is_cm) p = phs%lt_cm_to_lab * p
end subroutine phs_fks_get_born_momenta
@ %def phs_fks_get_born_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: get_outgoing_momenta => phs_fks_get_outgoing_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_get_outgoing_momenta (phs, q)
class(phs_fks_t), intent(in) :: phs
type(vector4_t), intent(out), dimension(:) :: q
end subroutine phs_fks_get_outgoing_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_get_outgoing_momenta (phs, q)
class(phs_fks_t), intent(in) :: phs
type(vector4_t), intent(out), dimension(:) :: q
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
q = phs%q_real
case (PHS_MODE_COLLINEAR_REMNANT)
q = phs%phs_wood_t%q
end select
end subroutine phs_fks_get_outgoing_momenta
@ %def phs_fks_get_outgoing_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: get_incoming_momenta => phs_fks_get_incoming_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_get_incoming_momenta (phs, p)
class(phs_fks_t), intent(in) :: phs
type(vector4_t), intent(inout), dimension(:), allocatable :: p
end subroutine phs_fks_get_incoming_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_get_incoming_momenta (phs, p)
class(phs_fks_t), intent(in) :: phs
type(vector4_t), intent(inout), dimension(:), allocatable :: p
p = phs%p_real
end subroutine phs_fks_get_incoming_momenta
@ %def phs_fks_get_incoming_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: set_isr_kinematics => phs_fks_set_isr_kinematics
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_set_isr_kinematics (phs, requires_boost)
class(phs_fks_t), intent(inout) :: phs
logical, intent(in) :: requires_boost
end subroutine phs_fks_set_isr_kinematics
<<PHS fks: procedures>>=
module subroutine phs_fks_set_isr_kinematics (phs, requires_boost)
class(phs_fks_t), intent(inout) :: phs
logical, intent(in) :: requires_boost
type(vector4_t), dimension(2) :: p
if (phs%generator%isr_kinematics%isr_mode == SQRTS_VAR) then
if (requires_boost) then
p = phs%lt_cm_to_lab &
* phs%generator%real_kinematics%p_born_cms%phs_point(1)%select ([1,2])
else
p = phs%generator%real_kinematics%p_born_lab%phs_point(1)%select ([1,2])
end if
call phs%generator%set_isr_kinematics (p)
end if
end subroutine phs_fks_set_isr_kinematics
@ %def phs_fks_set_isr_kinematics
@
<<PHS fks: phs fks: TBP>>=
procedure :: generate_radiation_variables => &
phs_fks_generate_radiation_variables
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generate_radiation_variables &
(phs, r_in, threshold)
class(phs_fks_t), intent(inout) :: phs
real(default), intent(in), dimension(:) :: r_in
logical, intent(in) :: threshold
end subroutine phs_fks_generate_radiation_variables
<<PHS fks: procedures>>=
module subroutine phs_fks_generate_radiation_variables &
(phs, r_in, threshold)
class(phs_fks_t), intent(inout) :: phs
real(default), intent(in), dimension(:) :: r_in
logical, intent(in) :: threshold
type(vector4_t), dimension(:), allocatable :: p_born
if (size (r_in) /= 3) call msg_fatal &
("Real kinematics need to be generated using three random numbers!")
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
allocate (p_born (size (phs%p_born_tot)))
if (threshold) then
p_born = phs%get_onshell_projected_momenta ()
else
p_born = phs%p_born_tot
if (.not. phs%lab_is_cm ()) &
p_born = inverse (phs%lt_cm_to_lab) * p_born
end if
call phs%generator%generate_radiation_variables &
(r_in, p_born, phs%phs_identifiers, threshold)
phs%r_real = r_in
end select
end subroutine phs_fks_generate_radiation_variables
@ %def phs_fks_generate_radiation_variables
@
<<PHS fks: phs fks: TBP>>=
procedure :: compute_xi_ref_momenta => phs_fks_compute_xi_ref_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_xi_ref_momenta (phs, p_in, contributors)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(in), dimension(:), optional :: p_in
type(resonance_contributors_t), intent(in), dimension(:), optional :: &
contributors
end subroutine phs_fks_compute_xi_ref_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_xi_ref_momenta (phs, p_in, contributors)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(in), dimension(:), optional :: p_in
type(resonance_contributors_t), intent(in), dimension(:), optional :: &
contributors
if (phs%mode == PHS_MODE_ADDITIONAL_PARTICLE) then
if (present (p_in)) then
call phs%generator%compute_xi_ref_momenta (p_in, contributors)
else
call phs%generator%compute_xi_ref_momenta &
(phs%p_born_tot, contributors)
end if
end if
end subroutine phs_fks_compute_xi_ref_momenta
@ %def phs_fks_compute_xi_ref_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: compute_xi_ref_momenta_threshold => &
phs_fks_compute_xi_ref_momenta_threshold
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_xi_ref_momenta_threshold (phs)
class(phs_fks_t), intent(inout) :: phs
end subroutine phs_fks_compute_xi_ref_momenta_threshold
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_xi_ref_momenta_threshold (phs)
class(phs_fks_t), intent(inout) :: phs
select case (phs%mode)
case (PHS_MODE_ADDITIONAL_PARTICLE)
call phs%generator%compute_xi_ref_momenta_threshold &
(phs%get_onshell_projected_momenta ())
end select
end subroutine phs_fks_compute_xi_ref_momenta_threshold
@ %def phs_fks_compute_xi_ref_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: compute_cms_energy => phs_fks_compute_cms_energy
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_cms_energy (phs)
class(phs_fks_t), intent(inout) :: phs
end subroutine phs_fks_compute_cms_energy
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_cms_energy (phs)
class(phs_fks_t), intent(inout) :: phs
if (phs%mode == PHS_MODE_ADDITIONAL_PARTICLE) &
call phs%generator%compute_cms_energy (phs%p_born_tot)
end subroutine phs_fks_compute_cms_energy
@ %def phs_fks_compute_cms_energy
@ When initial-state radiation is involved, either due to beamstrahlung or
QCD/QED corrections, it is important to have access to both the phase
space points in the center-of-mass and lab frame.
<<PHS fks: phs fks: TBP>>=
procedure :: set_reference_frames => phs_fks_set_reference_frames
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_set_reference_frames (phs, is_cms)
class(phs_fks_t), intent(inout) :: phs
logical, intent(in) :: is_cms
end subroutine phs_fks_set_reference_frames
<<PHS fks: procedures>>=
module subroutine phs_fks_set_reference_frames (phs, is_cms)
class(phs_fks_t), intent(inout) :: phs
logical, intent(in) :: is_cms
associate (real_kinematics => phs%generator%real_kinematics)
if (phs%config%lab_is_cm) then
real_kinematics%p_born_cms%phs_point(1) = phs%p_born_tot
real_kinematics%p_born_lab%phs_point(1) = phs%p_born_tot
else
if (is_cms) then
real_kinematics%p_born_cms%phs_point(1) &
= phs%p_born_tot
real_kinematics%p_born_lab%phs_point(1) &
= phs%lt_cm_to_lab * phs%p_born_tot
else
real_kinematics%p_born_cms%phs_point(1) &
= inverse (phs%lt_cm_to_lab) * phs%p_born_tot
real_kinematics%p_born_lab%phs_point(1) &
= phs%p_born_tot
end if
end if
end associate
end subroutine phs_fks_set_reference_frames
@ %def phs_fks_set_reference_frames
@
<<PHS fks: phs fks: TBP>>=
procedure :: i_phs_is_isr => phs_fks_i_phs_is_isr
<<PHS fks: sub interfaces>>=
module function phs_fks_i_phs_is_isr (phs, i_phs) result (is_isr)
logical :: is_isr
class(phs_fks_t), intent(in) :: phs
integer, intent(in) :: i_phs
end function phs_fks_i_phs_is_isr
<<PHS fks: procedures>>=
module function phs_fks_i_phs_is_isr (phs, i_phs) result (is_isr)
logical :: is_isr
class(phs_fks_t), intent(in) :: phs
integer, intent(in) :: i_phs
is_isr = phs%phs_identifiers(i_phs)%emitter <= phs%generator%n_in
end function phs_fks_i_phs_is_isr
@ %def phs_fks_i_phs_is_isr
@
\subsection{Creation of the real phase space - FSR}
At this point, the Born phase space has been generated, as well as the
three random variables $\xi$, $y$ and $\phi$. The question is how the
real phase space is generated for a final-state emission
configuration. We work with two different sets of momenta, the Born
configuration $\Bigl\{ \bar{k}_{\oplus}, \bar{k}_{\ominus}, \bar{k}_{1}, ...,
\bar{k}_{n} \Bigr\}$ and the real configuration $\Bigl\{ k_{\oplus},
k_{\ominus}, k_1,..., k_n, k_{n+1} \Bigr\}$. We define the momentum of
the emitter to be on the $n$-th position and the momentum of the
radiated particle to be at position $n+1$. The magnitude of the
spatial component of k is denoted by $\underline{k}$.
For final-state emissions, it is $\bar{k}_\oplus = k_\oplus$ and
$\bar{k}_\ominus = k_\ominus$. Thus, the center-of-mass systems
coincide and it is
\begin{equation}
q = \sum_{i=1}^n \bar{k}_i = \sum_{i=1}^{n+1} k_i,
\end{equation}
with $\vec{q} = 0$ and $q^2 = \left(q^0\right)^2$.
We want to construct the real phase space from the Born phase space
using three random numbers. They are defined as follows:
\begin{itemize}
\item $\xi = \frac{2k_{n+1}^0}{\sqrt{s}} \in [0, \xi_{max}]$, where
$k_{n+1}$ denotes the four-momentum of the radiated particle.
\item $y = \cos\theta = \frac{\vec{k}_n \cdot
\vec{k}_{n+1}}{\underline{k}_n \underline{k}_{n+1}}$ is the
splitting angle.
\item The angle between tho two splitting particles in the transversal
plane, $phi \in [0,2\pi]$.
\end{itemize}
Further, $k_{rec} = \sum_{i=1}^{n-1} k_i$ denotes the sum of all
recoiling momenta.
<<PHS fks: phs fks generator: TBP>>=
generic :: generate_fsr => generate_fsr_default, generate_fsr_resonances
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_fsr_default => phs_fks_generator_generate_fsr_default
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_fsr_default &
(generator, emitter, i_phs, &
p_born, p_real, xi_y_phi, no_jacobians)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
end subroutine phs_fks_generator_generate_fsr_default
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_fsr_default &
(generator, emitter, i_phs, &
p_born, p_real, xi_y_phi, no_jacobians)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
real(default) :: q0
call generator%generate_fsr_in (p_born, p_real)
q0 = sum (p_born(1:generator%n_in))**1
generator%i_fsr_first = generator%n_in + 1
call generator%generate_fsr_out (emitter, i_phs, p_born, p_real, q0, &
xi_y_phi = xi_y_phi, no_jacobians = no_jacobians)
if (debug_active (D_PHASESPACE)) then
call vector4_check_momentum_conservation (p_real, generator%n_in, &
rel_smallness = 1000 * tiny_07, abs_smallness = tiny_07)
end if
end subroutine phs_fks_generator_generate_fsr_default
@ %def phs_fks_generator_generate_fsr
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_fsr_resonances => &
phs_fks_generator_generate_fsr_resonances
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_fsr_resonances (generator, &
emitter, i_phs, i_con, p_born, p_real, xi_y_phi, no_jacobians)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
integer, intent(in) :: i_con
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(inout), dimension(:) :: p_real
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
end subroutine phs_fks_generator_generate_fsr_resonances
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_fsr_resonances (generator, &
emitter, i_phs, i_con, p_born, p_real, xi_y_phi, no_jacobians)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
integer, intent(in) :: i_con
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(inout), dimension(:) :: p_real
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
integer, dimension(:), allocatable :: resonance_list
integer, dimension(size(p_born)) :: inv_resonance_list
type(vector4_t), dimension(:), allocatable :: p_tmp_born
type(vector4_t), dimension(:), allocatable :: p_tmp_real
type(vector4_t) :: p_resonance
real(default) :: q0
integer :: i, j, nlegborn, nlegreal
integer :: i_emitter
type(lorentz_transformation_t) :: boost_to_resonance
integer :: n_resonant_particles
if (debug_on) call msg_debug2 (D_PHASESPACE, "phs_fks_generator_generate_fsr_resonances")
nlegborn = size (p_born); nlegreal = nlegborn + 1
allocate (resonance_list (size (generator%resonance_contributors(i_con)%c)))
resonance_list = generator%resonance_contributors(i_con)%c
n_resonant_particles = size (resonance_list)
if (.not. any (resonance_list == emitter)) then
call msg_fatal ("Emitter must be included in the resonance list!")
else
do i = 1, n_resonant_particles
if (resonance_list (i) == emitter) i_emitter = i
end do
end if
inv_resonance_list = &
create_inverse_resonance_list (nlegborn, resonance_list)
allocate (p_tmp_born (n_resonant_particles))
allocate (p_tmp_real (n_resonant_particles + 1))
p_tmp_born = vector4_null
p_tmp_real = vector4_null
j = 1
do i = 1, n_resonant_particles
p_tmp_born(j) = p_born(resonance_list(i))
j = j + 1
end do
call generator%generate_fsr_in (p_born, p_real)
p_resonance = generator%real_kinematics%xi_ref_momenta(i_con)
q0 = p_resonance**1
boost_to_resonance = inverse (boost (p_resonance, q0))
p_tmp_born = boost_to_resonance * p_tmp_born
generator%i_fsr_first = 1
call generator%generate_fsr_out (emitter, i_phs, p_tmp_born, p_tmp_real, &
q0, i_emitter, xi_y_phi)
p_tmp_real = inverse (boost_to_resonance) * p_tmp_real
do i = generator%n_in + 1, nlegborn
if (any (resonance_list == i)) then
p_real(i) = p_tmp_real(inv_resonance_list (i))
else
p_real(i) = p_born (i)
end if
end do
p_real(nlegreal) = p_tmp_real (n_resonant_particles + 1)
if (debug_active (D_PHASESPACE)) then
call vector4_check_momentum_conservation (p_real, generator%n_in, &
rel_smallness = 1000 * tiny_07, abs_smallness = tiny_07)
end if
contains
function create_inverse_resonance_list (nlegborn, resonance_list) &
result (inv_resonance_list)
integer, intent(in) :: nlegborn
integer, intent(in), dimension(:) :: resonance_list
integer, dimension(nlegborn) :: inv_resonance_list
integer :: i, j
inv_resonance_list = 0
j = 1
do i = 1, nlegborn
if (any (i == resonance_list)) then
inv_resonance_list (i) = j
j = j + 1
end if
end do
end function create_inverse_resonance_list
function boosted_energy () result (E)
real(default) :: E
type(vector4_t) :: p_boost
p_boost = boost_to_resonance * p_resonance
E = p_boost%p(0)
end function boosted_energy
end subroutine phs_fks_generator_generate_fsr_resonances
@ %def phs_fks_generator_generate_fsr_resonances
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_fsr_threshold => &
phs_fks_generator_generate_fsr_threshold
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_fsr_threshold (generator, &
emitter, i_phs, p_born, p_real, xi_y_phi)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(inout), dimension(:) :: p_real
real(default), intent(in), dimension(3), optional :: xi_y_phi
end subroutine phs_fks_generator_generate_fsr_threshold
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_fsr_threshold (generator, &
emitter, i_phs, p_born, p_real, xi_y_phi)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(inout), dimension(:) :: p_real
real(default), intent(in), dimension(3), optional :: xi_y_phi
type(vector4_t), dimension(2) :: p_tmp_born
type(vector4_t), dimension(3) :: p_tmp_real
integer :: nlegborn, nlegreal
type(vector4_t) :: p_top
real(default) :: q0
type(lorentz_transformation_t) :: boost_to_top
integer :: leg, other_leg
real(default) :: sqrts, mtop
if (debug_on) call msg_debug2 &
(D_PHASESPACE, "phs_fks_generator_generate_fsr_resonances")
nlegborn = size (p_born); nlegreal = nlegborn + 1
leg = thr_leg(emitter); other_leg = 3 - leg
p_tmp_born(1) = p_born (ass_boson(leg))
p_tmp_born(2) = p_born (ass_quark(leg))
call generator%generate_fsr_in (p_born, p_real)
p_top = generator%real_kinematics%xi_ref_momenta(leg)
q0 = p_top**1
sqrts = two * p_born(1)%p(0)
mtop = m1s_to_mpole (sqrts)
if (sqrts**2 - four * mtop**2 > zero) then
boost_to_top = inverse (boost (p_top, q0))
else
boost_to_top = identity
end if
p_tmp_born = boost_to_top * p_tmp_born
generator%i_fsr_first = 1
call generator%generate_fsr_out (emitter, i_phs, p_tmp_born, &
p_tmp_real, q0, 2, xi_y_phi)
p_tmp_real = inverse (boost_to_top) * p_tmp_real
p_real(ass_boson(leg)) = p_tmp_real(1)
p_real(ass_quark(leg)) = p_tmp_real(2)
p_real(ass_boson(other_leg)) = p_born(ass_boson(other_leg))
p_real(ass_quark(other_leg)) = p_born(ass_quark(other_leg))
p_real(THR_POS_GLUON) = p_tmp_real(3)
end subroutine phs_fks_generator_generate_fsr_threshold
@ %def phs_fks_generator_generate_fsr_threshold
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_fsr_in => phs_fks_generator_generate_fsr_in
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_fsr_in &
(generator, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generator_generate_fsr_in
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_fsr_in &
(generator, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
integer :: i
do i = 1, generator%n_in
p_real(i) = p_born(i)
end do
end subroutine phs_fks_generator_generate_fsr_in
@ %def phs_fks_generator_generate_fsr_in
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_fsr_out => phs_fks_generator_generate_fsr_out
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_fsr_out (generator, &
emitter, i_phs, p_born, p_real, q0, p_emitter_index, &
xi_y_phi, no_jacobians)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(inout), dimension(:) :: p_real
real(default), intent(in) :: q0
integer, intent(in), optional :: p_emitter_index
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
end subroutine phs_fks_generator_generate_fsr_out
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_fsr_out (generator, &
emitter, i_phs, p_born, p_real, q0, p_emitter_index, &
xi_y_phi, no_jacobians)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(inout), dimension(:) :: p_real
real(default), intent(in) :: q0
integer, intent(in), optional :: p_emitter_index
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
real(default) :: xi, y, phi
integer :: nlegborn, nlegreal
real(default) :: uk_np1, uk_n
real(default) :: uk_rec, k_rec0
type(vector3_t) :: k_n_born, k
real(default) :: uk_n_born, uk, k2, k0_n
real(default) :: cpsi, beta
type(vector3_t) :: vec, vec_orth
type(lorentz_transformation_t) :: rot
integer :: i, p_em
logical :: compute_jac
p_em = emitter; if (present (p_emitter_index)) p_em = p_emitter_index
compute_jac = .true.
if (present (no_jacobians)) compute_jac = .not. no_jacobians
if (generator%i_fsr_first < 0) &
call msg_fatal ("FSR generator is called for outgoing particles but "&
&"i_fsr_first is not set!")
if (present (xi_y_phi)) then
xi = xi_y_phi(I_XI)
y = xi_y_phi(I_Y)
phi = xi_y_phi(I_PHI)
else
associate (rad_var => generator%real_kinematics)
xi = rad_var%xi_tilde
if (rad_var%supply_xi_max) xi = xi * rad_var%xi_max(i_phs)
y = rad_var%y(i_phs)
phi = rad_var%phi
end associate
end if
nlegborn = size (p_born)
nlegreal = nlegborn + 1
generator%E_gluon = q0 * xi / two
uk_np1 = generator%E_gluon
k_n_born = p_born(p_em)%p(1:3)
uk_n_born = k_n_born**1
generator%mrec2 = (q0 - p_born(p_em)%p(0))**2 &
- space_part_norm(p_born(p_em))**2
if (generator%is_massive(emitter)) then
call generator%compute_emitter_kinematics (y, emitter, &
i_phs, q0, k0_n, uk_n, uk, compute_jac)
else
call generator%compute_emitter_kinematics (y, q0, uk_n, uk)
generator%real_kinematics%y_soft(i_phs) = y
k0_n = uk_n
end if
if (debug_on) call msg_debug2 &
(D_PHASESPACE, "phs_fks_generator_generate_fsr_out")
call debug_input_values ()
vec = uk_n / uk_n_born * k_n_born
vec_orth = create_orthogonal (vec)
p_real(p_em)%p(0) = k0_n
p_real(p_em)%p(1:3) = vec%p(1:3)
cpsi = (uk_n**2 + uk**2 - uk_np1**2) / (two * uk_n * uk)
!!! This is to catch the case where cpsi = 1, but numerically
!!! turns out to be slightly larger than 1.
call check_cpsi_bound (cpsi)
rot = rotation (cpsi, - sqrt (one - cpsi**2), vec_orth)
p_real(p_em) = rot * p_real(p_em)
vec = uk_np1 / uk_n_born * k_n_born
vec_orth = create_orthogonal (vec)
p_real(nlegreal)%p(0) = uk_np1
p_real(nlegreal)%p(1:3) = vec%p(1:3)
cpsi = (uk_np1**2 + uk**2 - uk_n**2) / (two * uk_np1 * uk)
call check_cpsi_bound (cpsi)
rot = rotation (cpsi, sqrt (one - cpsi**2), vec_orth)
p_real(nlegreal) = rot * p_real(nlegreal)
call construct_recoiling_momenta ()
if (compute_jac) call compute_jacobians ()
contains
<<PHS fks: generator generate fsr out procedures>>
end subroutine phs_fks_generator_generate_fsr_out
@ %def phs_fks_generator_generate_fsr_out
@
<<PHS fks: generator generate fsr out procedures>>=
subroutine debug_input_values ()
if (debug2_active (D_PHASESPACE)) then
call generator%write ()
print *, 'emitter = ', emitter
print *, 'p_born:'
call vector4_write_set (p_born)
print *, 'p_real:'
call vector4_write_set (p_real)
print *, 'q0 = ', q0
if (present(p_emitter_index)) then
print *, 'p_emitter_index = ', p_emitter_index
else
print *, 'p_emitter_index not given'
end if
end if
end subroutine debug_input_values
<<PHS fks: generator generate fsr out procedures>>=
subroutine check_cpsi_bound (cpsi)
real(default), intent(inout) :: cpsi
if (cpsi > one) then
cpsi = one
else if (cpsi < -one) then
cpsi = - one
end if
end subroutine check_cpsi_bound
@ Construction of the recoiling momenta. The reshuffling of momenta
must not change the invariant mass of the recoiling system, which
means $k_{\rm{rec}}^2 = \bar{k_{\rm{rec}}}^2$. Therefore, the momenta
are related by a boost, $\bar{k}_i = \Lambda k_i$. The boost parameter
is
\begin{equation*}
\beta = \frac{q^2 - (k_{\rm{rec}}^0 +
\underline{k}_{\rm{rec}})^2}{q^2 + (k_{\rm{rec}}^0 +
\underline{k}_{\rm{rec}})^2}
\end{equation*}
<<PHS fks: generator generate fsr out procedures>>=
subroutine construct_recoiling_momenta ()
type(lorentz_transformation_t) :: lambda
k_rec0 = q0 - p_real(p_em)%p(0) - p_real(nlegreal)%p(0)
if (k_rec0**2 > generator%mrec2) then
uk_rec = sqrt (k_rec0**2 - generator%mrec2)
else
uk_rec = 0
end if
if (generator%is_massive(emitter)) then
beta = compute_beta (q0**2, k_rec0, uk_rec, &
p_born(p_em)%p(0), uk_n_born)
else
beta = compute_beta (q0**2, k_rec0, uk_rec)
end if
k = p_real(p_em)%p(1:3) + p_real(nlegreal)%p(1:3)
vec%p(1:3) = one / uk * k%p(1:3)
lambda = boost (beta / sqrt(one - beta**2), vec)
do i = generator%i_fsr_first, nlegborn
if (i /= p_em) then
p_real(i) = lambda * p_born(i)
end if
end do
vec%p(1:3) = p_born(p_em)%p(1:3) / uk_n_born
rot = rotation (cos(phi), sin(phi), vec)
p_real(nlegreal) = rot * p_real(nlegreal)
p_real(p_em) = rot * p_real(p_em)
end subroutine construct_recoiling_momenta
@ The factor $\frac{q^2}{(4\pi)^3}$ is not included here since it is
supplied during phase space generation. Also, we already divide by
$\xi$.
<<PHS fks: generator generate fsr out procedures>>=
subroutine compute_jacobians ()
associate (jac => generator%real_kinematics%jac(i_phs))
if (generator%is_massive(emitter)) then
jac%jac(1) = jac%jac(1) * four / q0 / uk_n_born / xi
else
k2 = two * uk_n * uk_np1* (one - y)
jac%jac(1) = uk_n**2 / uk_n_born / (uk_n - k2 / (two * q0))
end if
jac%jac(2) = one
jac%jac(3) = one - xi / two * q0 / uk_n_born
end associate
end subroutine compute_jacobians
@ %def compute_jacobians
@
<<PHS fks: phs fks: TBP>>=
procedure :: generate_fsr_in => phs_fks_generate_fsr_in
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generate_fsr_in (phs)
class(phs_fks_t), intent(inout) :: phs
end subroutine phs_fks_generate_fsr_in
<<PHS fks: procedures>>=
module subroutine phs_fks_generate_fsr_in (phs)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), dimension(:), allocatable :: p
p = phs%generator%real_kinematics%p_born_lab%get_momenta &
(1, phs%generator%n_in)
end subroutine phs_fks_generate_fsr_in
@ %def phs_fks_generate_fsr_in
@
<<PHS fks: phs fks: TBP>>=
procedure :: generate_fsr => phs_fks_generate_fsr
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generate_fsr (phs, emitter, i_phs, p_real, &
i_con, xi_y_phi, no_jacobians)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(out), dimension(:) :: p_real
integer, intent(in), optional :: i_con
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
end subroutine phs_fks_generate_fsr
<<PHS fks: procedures>>=
module subroutine phs_fks_generate_fsr (phs, emitter, i_phs, p_real, &
i_con, xi_y_phi, no_jacobians)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(out), dimension(:) :: p_real
integer, intent(in), optional :: i_con
real(default), intent(in), dimension(3), optional :: xi_y_phi
logical, intent(in), optional :: no_jacobians
type(vector4_t), dimension(:), allocatable :: p
associate (generator => phs%generator)
p = generator%real_kinematics%p_born_cms%phs_point(1)
generator%real_kinematics%supply_xi_max = .true.
if (present (i_con)) then
call generator%generate_fsr (emitter, i_phs, i_con, p, p_real, &
xi_y_phi, no_jacobians)
else
call generator%generate_fsr (emitter, i_phs, p, p_real, &
xi_y_phi, no_jacobians)
end if
generator%real_kinematics%p_real_cms%phs_point(i_phs) = p_real
if (.not. phs%config%lab_is_cm) p_real = phs%lt_cm_to_lab * p_real
generator%real_kinematics%p_real_lab%phs_point(i_phs) = p_real
end associate
end subroutine phs_fks_generate_fsr
@ %def phs_fks_generate_fsr
@
<<PHS fks: phs fks: TBP>>=
procedure :: get_onshell_projected_momenta => &
phs_fks_get_onshell_projected_momenta
<<PHS fks: sub interfaces>>=
pure module function phs_fks_get_onshell_projected_momenta (phs) result (p)
type(vector4_t), dimension(:), allocatable :: p
class(phs_fks_t), intent(in) :: phs
end function phs_fks_get_onshell_projected_momenta
<<PHS fks: procedures>>=
pure module function phs_fks_get_onshell_projected_momenta (phs) result (p)
type(vector4_t), dimension(:), allocatable :: p
class(phs_fks_t), intent(in) :: phs
p = phs%generator%real_kinematics%p_born_onshell%phs_point(1)
end function phs_fks_get_onshell_projected_momenta
@ %def phs_fks_get_onshell_projected_momenta
@
<<PHS fks: phs fks: TBP>>=
procedure :: generate_fsr_threshold => phs_fks_generate_fsr_threshold
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generate_fsr_threshold &
(phs, emitter, i_phs, p_real)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(inout), dimension(:), optional :: p_real
end subroutine phs_fks_generate_fsr_threshold
<<PHS fks: procedures>>=
module subroutine phs_fks_generate_fsr_threshold &
(phs, emitter, i_phs, p_real)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(inout), dimension(:), optional :: p_real
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: pp
integer :: leg
associate (generator => phs%generator)
generator%real_kinematics%supply_xi_max = .true.
allocate (p_born (1 : generator%real_kinematics%p_born_cms%get_n_particles()))
p_born = generator%real_kinematics%p_born_onshell%get_momenta (1)
allocate (pp (size (p_born) + 1))
call generator%generate_fsr_threshold (emitter, i_phs, p_born, pp)
leg = thr_leg (emitter)
call generator%real_kinematics%p_real_onshell(leg)%set_momenta (i_phs, pp)
if (present (p_real)) p_real = pp
end associate
end subroutine phs_fks_generate_fsr_threshold
@ %def phs_fks_generate_fsr_threshold
@
<<PHS fks: phs fks: TBP>>=
generic :: compute_xi_max => &
compute_xi_max_internal, compute_xi_max_with_output
procedure :: compute_xi_max_internal => phs_fks_compute_xi_max_internal
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_xi_max_internal (phs, p, threshold)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(in), dimension(:) :: p
logical, intent(in) :: threshold
end subroutine phs_fks_compute_xi_max_internal
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_xi_max_internal (phs, p, threshold)
class(phs_fks_t), intent(inout) :: phs
type(vector4_t), intent(in), dimension(:) :: p
logical, intent(in) :: threshold
integer :: i_phs, i_con, emitter
do i_phs = 1, size (phs%phs_identifiers)
associate (phs_id => phs%phs_identifiers(i_phs), generator => phs%generator)
emitter = phs_id%emitter
if (threshold) then
call generator%compute_xi_max (emitter, i_phs, p, &
generator%real_kinematics%xi_max(i_phs), i_con = thr_leg(emitter))
else if (allocated (phs_id%contributors)) then
do i_con = 1, size (phs_id%contributors)
call generator%compute_xi_max (emitter, i_phs, p, &
generator%real_kinematics%xi_max(i_phs), i_con = 1)
end do
else
call generator%compute_xi_max (emitter, i_phs, p, &
generator%real_kinematics%xi_max(i_phs))
end if
end associate
end do
end subroutine phs_fks_compute_xi_max_internal
@ %def phs_fks_compute_xi_max
@
<<PHS fks: phs fks: TBP>>=
procedure :: compute_xi_max_with_output => phs_fks_compute_xi_max_with_output
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_xi_max_with_output &
(phs, emitter, i_phs, y, p, xi_max)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: i_phs, emitter
real(default), intent(in) :: y
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(out) :: xi_max
end subroutine phs_fks_compute_xi_max_with_output
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_xi_max_with_output &
(phs, emitter, i_phs, y, p, xi_max)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: i_phs, emitter
real(default), intent(in) :: y
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(out) :: xi_max
call phs%generator%compute_xi_max (emitter, i_phs, p, xi_max, y_in = y)
end subroutine phs_fks_compute_xi_max_with_output
@ %def phs_fks_compute_xi_max_with_output
@
<<PHS fks: phs fks generator: TBP>>=
generic :: compute_emitter_kinematics => &
compute_emitter_kinematics_massless, &
compute_emitter_kinematics_massive
procedure :: compute_emitter_kinematics_massless => &
phs_fks_generator_compute_emitter_kinematics_massless
procedure :: compute_emitter_kinematics_massive => &
phs_fks_generator_compute_emitter_kinematics_massive
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_emitter_kinematics_massless &
(generator, y, q0, uk_em, uk)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: y, q0
real(default), intent(out) :: uk_em, uk
end subroutine phs_fks_generator_compute_emitter_kinematics_massless
module subroutine phs_fks_generator_compute_emitter_kinematics_massive &
(generator, y, em, i_phs, q0, k0_em, uk_em, uk, compute_jac)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: y
integer, intent(in) :: em, i_phs
real(default), intent(in) :: q0
real(default), intent(inout) :: k0_em, uk_em, uk
logical, intent(in) :: compute_jac
end subroutine phs_fks_generator_compute_emitter_kinematics_massive
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_emitter_kinematics_massless &
(generator, y, q0, uk_em, uk)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: y, q0
real(default), intent(out) :: uk_em, uk
real(default) :: k0_np1, q2
k0_np1 = generator%E_gluon
q2 = q0**2
uk_em = (q2 - generator%mrec2 - two * q0 * k0_np1) / &
(two * (q0 - k0_np1 * (one - y)))
uk = sqrt (uk_em**2 + k0_np1**2 + two * uk_em * k0_np1 * y)
end subroutine phs_fks_generator_compute_emitter_kinematics_massless
module subroutine phs_fks_generator_compute_emitter_kinematics_massive &
(generator, y, em, i_phs, q0, k0_em, uk_em, uk, compute_jac)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: y
integer, intent(in) :: em, i_phs
real(default), intent(in) :: q0
real(default), intent(inout) :: k0_em, uk_em, uk
logical, intent(in) :: compute_jac
real(default) :: k0_np1, q2, mrec2, m2
real(default) :: k0_rec_max, k0_em_max, k0_rec, uk_rec
real(default) :: z, z1, z2
k0_np1 = generator%E_gluon
q2 = q0**2
mrec2 = generator%mrec2
m2 = generator%m2(em)
k0_rec_max = (q2 - m2 + mrec2) / (two * q0)
k0_em_max = (q2 + m2 - mrec2) /(two * q0)
z1 = (k0_rec_max + sqrt (k0_rec_max**2 - mrec2)) / q0
z2 = (k0_rec_max - sqrt (k0_rec_max**2 - mrec2)) / q0
z = z2 - (z2 - z1) * (one + y) / two
k0_em = k0_em_max - k0_np1 * z
k0_rec = q0 - k0_np1 - k0_em
uk_em = sqrt(k0_em**2 - m2)
uk_rec = sqrt(k0_rec**2 - mrec2)
uk = uk_rec
if (compute_jac) &
generator%real_kinematics%jac(i_phs)%jac = q0 * (z1 - z2) / four * k0_np1
generator%real_kinematics%y_soft(i_phs) = &
(two * q2 * z - q2 - mrec2 + m2) / (sqrt(k0_em_max**2 - m2) * q0) / two
end subroutine phs_fks_generator_compute_emitter_kinematics_massive
@ %def phs_fks_generator_compute_emitter_kinematics
@
<<PHS fks: procedures>>=
function recompute_xi_max (q0, mrec2, m2, y) result (xi_max)
real(default) :: xi_max
real(default), intent(in) :: q0, mrec2, m2, y
real(default) :: q2, k0_np1_max, k0_rec_max
real(default) :: z1, z2, z
q2 = q0**2
k0_rec_max = (q2 - m2 + mrec2) / (two * q0)
z1 = (k0_rec_max + sqrt (k0_rec_max**2 - mrec2)) / q0
z2 = (k0_rec_max - sqrt (k0_rec_max**2 - mrec2)) / q0
z = z2 - (z2 - z1) * (one + y) / 2
k0_np1_max = - (q2 * z**2 - two * q0 * k0_rec_max * z + mrec2) / (two * q0 * z * (one - z))
xi_max = two * k0_np1_max / q0
end function recompute_xi_max
@ %def recompute_xi_max
@
<<PHS fks: procedures>>=
function compute_beta_massless (q2, k0_rec, uk_rec) result (beta)
real(default), intent(in) :: q2, k0_rec, uk_rec
real(default) :: beta
beta = (q2 - (k0_rec + uk_rec)**2) / (q2 + (k0_rec + uk_rec)**2)
end function compute_beta_massless
function compute_beta_massive (q2, k0_rec, uk_rec, &
k0_em_born, uk_em_born) result (beta)
real(default), intent(in) :: q2, k0_rec, uk_rec
real(default), intent(in) :: k0_em_born, uk_em_born
real(default) :: beta
real(default) :: k0_rec_born, uk_rec_born, alpha
k0_rec_born = sqrt(q2) - k0_em_born
uk_rec_born = uk_em_born
alpha = (k0_rec + uk_rec) / (k0_rec_born + uk_rec_born)
beta = (one - alpha**2) / (one + alpha**2)
end function compute_beta_massive
@ %def compute_beta
@ The momentum of the radiated particle is computed according to
\begin{equation}
\label{eq:phs_fks:compute_k_n}
\underline{k}_n = \frac{q^2 - M_{\rm{rec}}^2 -
2q^0\underline{k}_{n+1}}{2(q^0 - \underline{k}_{n+1}(1-y))},
\end{equation}
with $k = k_n + k_{n+1}$ and $M_{\rm{rec}}^2 = k_{\rm{rec}}^2 =
\left(q-k\right)^2$. Because of $\boldsymbol{\bar{k}}_n \parallel
\boldsymbol{k}_n + \boldsymbol{k}_{n+1}$ we find $M_{\rm{rec}}^2 =
\left(q-\bar{k}_n\right)^2$.
Equation \ref{eq:phs_fks:compute_k_n} follows from the fact that
$\left(\boldsymbol{k} - \boldsymbol{k}_n\right)^2 =
\boldsymbol{k}_{n+1}^2$, which is equivalent to $\boldsymbol{k}_n
\cdot \boldsymbol{k} = \frac{1}{2} \left(\underline{k}_n^2 +
\underline{k}^2 - \underline{k}_{n+1}^2\right)$.\\
$\boldsymbol{k}_n$ and $\boldsymbol{k}_{n+1}$ are obtained by first
setting up vectors parallel to $\boldsymbol{\bar{k}}_n$,
\begin{equation*}
\boldsymbol{k}_n' = \underline{k}_n
\frac{\bar{\pmb{k}}_n}{\underline{\bar{k}}_n}, \quad \pmb{k}_{n+1}'
= \underline{k}_{n+1}\frac{\bar{\pmb{k}}_n}{\underline{\bar{k}}_n},
\end{equation*}
and then rotating these vectors by an amount of $\cos\psi_n =
\frac{\boldsymbol{k}_n\cdot\pmb{k}}{\underline{k}_n \underline{k}}$.
@ The emitted particle cannot have more momentum than the emitter has
in the Born phase space. Thus, there is an upper bound for $\xi$,
determined by the condition $k_{n+1}^0 = \underline{\bar{k}}_n$, which
is equal to
\begin{equation*}
\xi_{\rm{max}} = \frac{2}{\underline{\bar{k}}_n}{q^0}.
\end{equation*}
<<PHS fks: procedures>>=
pure function get_xi_max_fsr_massless (p_born, q0, emitter) result (xi_max)
type(vector4_t), intent(in), dimension(:) :: p_born
real(default), intent(in) :: q0
integer, intent(in) :: emitter
real(default) :: xi_max
real(default) :: uk_n_born
uk_n_born = space_part_norm (p_born(emitter))
xi_max = two * uk_n_born / q0
end function get_xi_max_fsr_massless
@ %def get_xi_max_fsr_massless
@ The computation of $\xi_{\rm{max}}$ for massive emitters is described
in arXiv:1202.0465. Let's recapitulate it here.
We consider the Dalitz-domain created by $k_{n+1}^0$, $k_n^0$ and
$k_{\rm{rec}}^0$ and introduce the parameterization
\begin{equation*}
k_n^0 = \bar{k}_n^0 - zk_{n+1}^0
\end{equation*}
Then, for each value of $z$, there exists a maximum value of
$\underline{k}_{n+1}$ from which $\xi_{\rm{max}}$ can be extracted via
$\xi_{\rm{max}} = 2k_{n+1}^0/q$. It is determined by the condition
\begin{equation*}
\underline{k}_{n+1} \pm \underline{k}_n \pm \underline{k}_{\rm{rec}} = 0.
\end{equation*}
This can be manipulated to yield
\begin{equation*}
\left(\underline{k}_{n+1}^2 + \underline{k}_n^2 -
\underline{k}_{\rm{rec}}^2\right)^2 =
4\underline{k}^2_{n+1}\underline{k}_n^2.
\end{equation*}
Here we can use $\underline{k}_n^2 = \left(k_n^0\right)^2 - m^2$ and
$\underline{k}_{\rm{rec}}^2 = \left(q - k_n^0 - k_{n+1}^0\right)^2 -
M_{\rm{rec}}^2$, as well as the above parameterization of $k_n^0$, to
obtain
\begin{equation*}
4\underline{k}_{n+1}^2\left(2\underline{k}_{n+1}qz(1-z) +
q^2z^2 - 2q\bar{k}_{\rm{rec}}^0z + M_{\rm{rec}}^2\right) = 0.
\end{equation*}
Solving for $k_{n+1}^0$ gives
\begin{equation}
k_{n+1}^0 = \frac{2q\bar{k}^0_{\rm{rec}}z - q^2z^2 - M_{\rm{rec}}^2}{2qz(1-z)}.
\label{XiMaxMassive}
\end{equation}
It is still open how to compute $z$. For this, consider that the
right-hand-side of equation (\ref{XiMaxMassive}) vanishes for
\begin{equation*}
z_{1,2} = \left(\bar{k}_{\rm{rec}}^0 \pm
\sqrt{\left(\bar{k}_{\rm{rec}}^0\right)^2 - M_{\rm{rec}}^2}\right)/q,
\end{equation*}
which corresponds to the borders of the Dalitz-region where the gluon
momentum vanishes. Thus we define
\begin{equation*}
z = z_2 - \frac{1}{2} (z_2 - z_1)(1+y).
\end{equation*}
<<PHS fks: procedures>>=
pure function get_xi_max_fsr_massive (p_born, q0, emitter, m2, y) result (xi_max)
real(default) :: xi_max
type(vector4_t), intent(in), dimension(:) :: p_born
real(default), intent(in) :: q0
integer, intent(in) :: emitter
real(default), intent(in) :: m2, y
real(default) :: mrec2
real(default) :: k0_rec_max
real(default) :: z, z1, z2
real(default) :: k0_np1_max
associate (p => p_born(emitter)%p)
mrec2 = (q0 - p(0))**2 - p(1)**2 - p(2)**2 - p(3)**2
end associate
call compute_dalitz_bounds (q0, m2, mrec2, z1, z2, k0_rec_max)
z = z2 - (z2 - z1) * (one + y) / two
k0_np1_max = - (q0**2 * z**2 - two * q0 * k0_rec_max * z + mrec2) &
/ (two * q0 * z * (one - z))
xi_max = two * k0_np1_max / q0
end function get_xi_max_fsr_massive
@ %def get_xi_max_fsr_massive
@
<<PHS fks: parameters>>=
integer, parameter, public :: I_PLUS = 1
integer, parameter, public :: I_MINUS = 2
@ %def parameters
@ Computes $\xi_{\text{max}}$ in the case of ISR as documented in eq. \ref{eqn:xi_max_isr}.
It is also used for the POWHEG matching.
<<PHS fks: public>>=
public :: get_xi_max_isr
<<PHS fks: sub interfaces>>=
module function get_xi_max_isr (xb, y) result (xi_max)
real(default) :: xi_max
real(default), dimension(2), intent(in) :: xb
real(default), intent(in) :: y
end function get_xi_max_isr
<<PHS fks: procedures>>=
module function get_xi_max_isr (xb, y) result (xi_max)
real(default) :: xi_max
real(default), dimension(2), intent(in) :: xb
real(default), intent(in) :: y
xi_max = one - max (xi_max_isr_plus (xb(I_PLUS), y), xi_max_isr_minus (xb(I_MINUS), y))
contains
function xi_max_isr_plus (x, y)
real(default) :: xi_max_isr_plus
real(default), intent(in) :: x, y
real(default) :: deno
deno = sqrt ((one + x**2)**2 * (one - y)**2 + 16 * y * x**2) + (one - y) * (1 - x**2)
xi_max_isr_plus = two * (one + y) * x**2 / deno
end function xi_max_isr_plus
function xi_max_isr_minus (x, y)
real(default) :: xi_max_isr_minus
real(default), intent(in) :: x, y
real(default) :: deno
deno = sqrt ((one + x**2)**2 * (one + y)**2 - 16 * y * x**2) + (one + y) * (1 - x**2)
xi_max_isr_minus = two * (one - y) * x**2 / deno
end function xi_max_isr_minus
end function get_xi_max_isr
@ %def get_xi_max_isr
@
<<PHS fks: procedures>>=
recursive function get_xi_max_isr_decay (p) result (xi_max)
real(default) :: xi_max
type(vector4_t), dimension(:), intent(in) :: p
integer :: n_tot
type(vector4_t), dimension(:), allocatable :: p_dec_new
n_tot = size (p)
if (n_tot == 3) then
xi_max = xi_max_one_to_two (p(1), p(2), p(3))
else
allocate (p_dec_new (n_tot - 1))
p_dec_new(1) = sum (p (3 : ))
p_dec_new(2 : n_tot - 1) = p (3 : n_tot)
xi_max = min (xi_max_one_to_two (p(1), p(2), sum(p(3 : ))), &
get_xi_max_isr_decay (p_dec_new))
end if
contains
function xi_max_one_to_two (p_in, p_out1, p_out2) result (xi_max)
real(default) :: xi_max
type(vector4_t), intent(in) :: p_in, p_out1, p_out2
real(default) :: m_in, m_out1, m_out2
m_in = p_in**1
m_out1 = p_out1**1; m_out2 = p_out2**1
xi_max = one - (m_out1 + m_out2)**2 / m_in**2
end function xi_max_one_to_two
end function get_xi_max_isr_decay
@ %def get_xi_max_isr_decay
@
\subsection{Creation of the real phase space - ISR}
<<PHS fks: phs fks: TBP>>=
procedure :: generate_isr => phs_fks_generate_isr
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generate_isr (phs, i_phs, p_real)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: i_phs
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generate_isr
<<PHS fks: procedures>>=
module subroutine phs_fks_generate_isr (phs, i_phs, p_real)
class(phs_fks_t), intent(inout) :: phs
integer, intent(in) :: i_phs
type(vector4_t), intent(out), dimension(:) :: p_real
type(vector4_t) :: p0, p1
type(lorentz_transformation_t) :: lt
real(default) :: sqrts_hat
type(vector4_t), dimension(:), allocatable :: p_work
associate (generator => phs%generator)
select case (generator%n_in)
case (1)
p_work = generator%real_kinematics%p_born_cms%phs_point(1)
call generator%generate_isr_fixed_beam_energy (i_phs, p_work, p_real)
phs%config%lab_is_cm = .true.
case (2)
select case (generator%isr_kinematics%isr_mode)
case (SQRTS_FIXED)
p_work = generator%real_kinematics%p_born_cms%phs_point(1)
call generator%generate_isr_fixed_beam_energy (i_phs, p_work, p_real)
case (SQRTS_VAR)
p_work = generator%real_kinematics%p_born_lab%phs_point(1)
call generator%generate_isr (i_phs, p_work, p_real)
end select
end select
generator%real_kinematics%p_real_lab%phs_point(i_phs) = p_real
if (.not. phs%config%lab_is_cm) then
sqrts_hat = (p_real(1) + p_real(2))**1
p0 = p_real(1) + p_real(2)
lt = boost (p0, sqrts_hat)
p1 = inverse(lt) * p_real(1)
lt = lt * rotation_to_2nd (3, space_part (p1))
phs%generator%real_kinematics%p_real_cms%phs_point(i_phs) &
= inverse (lt) * p_real
else
phs%generator%real_kinematics%p_real_cms%phs_point(i_phs) &
= p_real
end if
end associate
end subroutine phs_fks_generate_isr
@ %def phs_fks_generate_isr
@ The real phase space for an inital-state emission involved in a decay
process is generated by first setting the gluon momentum like in the
scattering case by using its angular coordinates $y$ and $\phi$ and then
adjusting the gluon energy with $\xi$. The emitter momentum is kept
identical to the Born case, i.e. $p_{\rm{in}} = \bar{p}_{\rm{in}}$, so
that after the emission it has momentum $p_{\rm{virt}} = p_{\rm{in}} -
p_{\rm{g}}$ and invariant mass $m^2 = p_{\rm{virt}}^2$. Note that the
final state momenta have to remain on-shell, so that $p_1^2 =
\bar{p}_1^2 = m_1^2$ and $p_2^2 = \bar{p}_2^2 = m_2^2$. Let $\Lambda$ be
the boost from into the rest frame of the emitter after emission, i.e.
$\Lambda p_{\rm{virt}} = \left(m, 0, 0, 0\right)$. In this reference
frame, the spatial components of the final-state momenta sum up to zero,
and their magnitude is
\begin{equation*}
p = \frac{\sqrt {\lambda (m^2, m_1^2, m_2^2)}}{2m},
\end{equation*}
a fact already used in the evaluation of the phase space trees of
[[phs_forest]]. Obviously, from this, the final-state energies can be
deferred via $E_i^2 = m_i^2 - p^2$. In the next step, the $p_{1,2}$ are
set up as vectors $(E,0,0,\pm p)$ along the z-axis and then rotated
about the same azimuthal and polar angles as in the Born system.
Finally, the momenta are boosted out of the rest frame by multiplying
with $\Lambda$.
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_isr_fixed_beam_energy => &
phs_fks_generator_generate_isr_fixed_beam_energy
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_isr_fixed_beam_energy &
(generator, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generator_generate_isr_fixed_beam_energy
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_isr_fixed_beam_energy &
(generator, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
real(default) :: xi_max, xi, y, phi
integer :: nlegborn, nlegreal, i
real(default) :: k0_np1
real(default) :: msq_in
type(vector4_t) :: p_virt
real(default) :: jac_real
associate (rad_var => generator%real_kinematics)
xi_max = rad_var%xi_max(i_phs)
xi = rad_var%xi_tilde * xi_max
y = rad_var%y(i_phs)
phi = rad_var%phi
rad_var%y_soft(i_phs) = y
end associate
nlegborn = size (p_born)
nlegreal = nlegborn + 1
msq_in = sum (p_born(1:generator%n_in))**2
generator%real_kinematics%jac(i_phs)%jac = one
p_real(1) = p_born(1)
if (generator%n_in > 1) p_real(2) = p_born(2)
k0_np1 = zero
do i = 1, generator%n_in
k0_np1 = k0_np1 + p_real(i)%p(0) * xi / two
end do
p_real(nlegreal)%p(0) = k0_np1
p_real(nlegreal)%p(1) = k0_np1 * sqrt(one - y**2) * sin(phi)
p_real(nlegreal)%p(2) = k0_np1 * sqrt(one - y**2) * cos(phi)
p_real(nlegreal)%p(3) = k0_np1 * y
p_virt = sum (p_real(1:generator%n_in)) - p_real(nlegreal)
jac_real = one
call generate_on_shell_decay (p_virt, &
p_born(generator%n_in + 1 : nlegborn), &
p_real(generator%n_in + 1 : nlegreal - 1), 1, msq_in, jac_real)
associate (jac => generator%real_kinematics%jac(i_phs))
jac%jac(1) = jac_real
jac%jac(2) = one
end associate
end subroutine phs_fks_generator_generate_isr_fixed_beam_energy
@ %def phs_fks_generator_generate_isr_fixed_beam_energy
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_isr_factorized => &
phs_fks_generator_generate_isr_factorized
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_isr_factorized &
(generator, i_phs, emitter, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs, emitter
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generator_generate_isr_factorized
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_isr_factorized &
(generator, i_phs, emitter, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs, emitter
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
type(vector4_t), dimension(3) :: p_tmp_born
type(vector4_t), dimension(4) :: p_tmp_real
type(vector4_t) :: p_top
type(lorentz_transformation_t) :: boost_to_rest_frame
integer, parameter :: nlegreal = 7
!!! Factorized phase space so far only required for ee -> bwbw
p_tmp_born = vector4_null; p_tmp_real = vector4_null
p_real(1:2) = p_born(1:2)
if (emitter == THR_POS_B) then
p_top = p_born (THR_POS_WP) + p_born (THR_POS_B)
p_tmp_born(2) = p_born (THR_POS_WP)
p_tmp_born(3) = p_born (THR_POS_B)
else if (emitter == THR_POS_BBAR) then
p_top = p_born (THR_POS_WM) + p_born (THR_POS_BBAR)
p_tmp_born(2) = p_born (THR_POS_WM)
p_tmp_born(3) = p_born (THR_POS_BBAR)
else
call msg_fatal ("Threshold computation requires emitters to be at position 5 and 6 " // &
"Please check if your process specification fulfills this requirement.")
end if
p_tmp_born (1) = p_top
boost_to_rest_frame = inverse (boost (p_top, p_top**1))
p_tmp_born = boost_to_rest_frame * p_tmp_born
call generator%compute_xi_max_isr_factorized (i_phs, p_tmp_born)
call generator%generate_isr_fixed_beam_energy &
(i_phs, p_tmp_born, p_tmp_real)
p_tmp_real = inverse (boost_to_rest_frame) * p_tmp_real
if (emitter == THR_POS_B) then
p_real(THR_POS_WP) = p_tmp_real(2)
p_real(THR_POS_B) = p_tmp_real(3)
p_real(THR_POS_WM) = p_born(THR_POS_WM)
p_real(THR_POS_BBAR) = p_born(THR_POS_BBAR)
!!! Exception has been handled above
else
p_real(THR_POS_WM) = p_tmp_real(2)
p_real(THR_POS_BBAR) = p_tmp_real(3)
p_real(THR_POS_WP) = p_born(THR_POS_WP)
p_real(THR_POS_B) = p_born(THR_POS_B)
end if
p_real(nlegreal) = p_tmp_real(4)
end subroutine phs_fks_generator_generate_isr_factorized
@ %def phs_fks_generator_generate_isr_factorized
@ Construction of the real momenta [[p_real]] in case of ISR.
Follows the discussion in [0709.2092] sec. 5.1.
The sequence of Lorentz boosts required to construct [[p_real]] from
[[p_born]] is as follows:
\begin{enumerate}
\item[\labelitemii] We construct the IS momenta of [[p_real]] from
the Born momenta via rescaling:
[[p_real(1:2)]] $= \frac{x}{\overline{x}} \cdot$ [[p_born(1:2)]].
If the Born momenta are imported in the lab frame, these will define
the real lab frame.
\item[\labelitemii] We construct the momentum of the radiated
particle in the real CMS:
$k_{n+1} = \frac{s \xi}{2} \cdot (1, \sin(\theta) \sin(\phi),
\sin(\theta) \cos(\phi), \cos(\theta))$
\setcounter{enumi}{-1}
\item We first boost the momentum of the radiated particle from the
real CMS to the real lab frame determined from [[p_real(1:2)]].
\item We initialize the non-radiated real FS momenta by a
longitudinal boost of [[p_born]] to a system with zero rapidity,
i.e. zero longitudinal momenum. This is $\mathbb{B}_L$.
\item We boost these momenta in a transverse direction to compensate
the transverse momentum of the radiation. This is
$\mathbb{B}_T$. Note: we switched $\mathbb{B}_T$ and
$\mathbb{B}^{-1}_T$ in Eq. (5.16) and their definition
w.r.t. [0709.2092].
\item We restore longitudinal momentum conservation by applying the
inverse boost of $\mathbb{B}_L$ to all non-radiated real FS momenta.
\end{enumerate}
This way, all components of [[p_real]] are constructed in the real Lab frame.
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_isr => phs_fks_generator_generate_isr
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_isr &
(generator, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs
type(vector4_t), intent(in) , dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generator_generate_isr
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_isr &
(generator, i_phs, p_born, p_real)
!!! Important: Import Born momenta in the lab frame
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs
type(vector4_t), intent(in) , dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
real(default) :: xi_max, xi_tilde, xi, y, phi
integer :: nlegborn, nlegreal
real(default) :: sqrts_real
real(default) :: k0_np1
type(lorentz_transformation_t) :: &
lambda_transv, lambda_longit, lambda_longit_inv
real(default) :: x_plus, x_minus, xb_plus, xb_minus
real(default) :: onemy, onepy
integer :: i
real(default) :: xi_plus, xi_minus
real(default) :: beta_gamma
type(vector3_t) :: beta_vec
associate (rad_var => generator%real_kinematics)
xi_max = rad_var%xi_max(i_phs)
xi_tilde = rad_var%xi_tilde
xi = xi_tilde * xi_max
y = rad_var%y(i_phs)
onemy = one - y; onepy = one + y
phi = rad_var%phi
rad_var%y_soft(i_phs) = y
end associate
nlegborn = size (p_born)
nlegreal = nlegborn + 1
generator%isr_kinematics%sqrts_born = (p_born(1) + p_born(2))**1
!!! Initial state real momenta
xb_plus = generator%isr_kinematics%x(I_PLUS)
xb_minus = generator%isr_kinematics%x(I_MINUS)
x_plus = xb_plus / sqrt(one - xi) * sqrt ((two - xi * onemy) / &
(two - xi * onepy))
x_minus = xb_minus / sqrt(one - xi) * sqrt ((two - xi * onepy) / &
(two - xi * onemy))
xi_plus = xi_tilde * (one - xb_plus)
xi_minus = xi_tilde * (one - xb_minus)
p_real(I_PLUS) = x_plus / xb_plus * p_born(I_PLUS)
p_real(I_MINUS) = x_minus / xb_minus * p_born(I_MINUS)
!!! Fraction of momentum fractions in a collinear splitting
generator%isr_kinematics%z(I_PLUS) = (one - xi_plus)
generator%isr_kinematics%z(I_MINUS) = (one - xi_minus)
!!! Create radiation momentum in the real CMS
sqrts_real = generator%isr_kinematics%sqrts_born / sqrt (one - xi)
k0_np1 = sqrts_real * xi / two
p_real(nlegreal)%p(0) = k0_np1
p_real(nlegreal)%p(1) = k0_np1 * sqrt (one - y**2) * sin(phi)
p_real(nlegreal)%p(2) = k0_np1 * sqrt (one - y**2) * cos(phi)
p_real(nlegreal)%p(3) = k0_np1 * y
!!! Boosts the radiation from real CMS to the real LAB frame
call get_boost_parameters (p_real, beta_gamma, beta_vec)
lambda_longit = create_longitudinal_boost &
(beta_gamma, beta_vec, inverse = .true.)
p_real(nlegreal) = lambda_longit * p_real(nlegreal)
call get_boost_parameters (p_born, beta_gamma, beta_vec)
lambda_longit = create_longitudinal_boost &
(beta_gamma, beta_vec, inverse = .false.)
forall (i = 3 : nlegborn) p_real(i) = lambda_longit * p_born(i)
lambda_transv = create_transversal_boost (p_real(nlegreal), xi, sqrts_real)
forall (i = 3 : nlegborn) p_real(i) = lambda_transv * p_real(i)
lambda_longit_inv = create_longitudinal_boost &
(beta_gamma, beta_vec, inverse = .true.)
forall (i = 3 : nlegborn) p_real(i) = lambda_longit_inv * p_real(i)
!!! Compute Jacobians
associate (jac => generator%real_kinematics%jac(i_phs))
!!! Additional 1 / (1 - xi) factor because in the real jacobian,
!!! there is s_real in the numerator
!!! We also have to adapt the flux factor, which is 1/(2s_real) for
!!! the real component
!!! The reweighting factor is s_born / s_real, cancelling the
!!! (1-xi) factor from above
jac%jac(1) = one / (one - xi)
jac%jac(2) = one
jac%jac(3) = one / (one - xi_plus)**2
jac%jac(4) = one / (one - xi_minus)**2
end associate
contains
subroutine get_boost_parameters (p, beta_gamma, beta_vec)
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(out) :: beta_gamma
type(vector3_t), intent(out) :: beta_vec
beta_vec = (p(1)%p(1:3) + p(2)%p(1:3)) / (p(1)%p(0) + p(2)%p(0))
beta_gamma = beta_vec**1 / sqrt (one - beta_vec**2)
beta_vec = beta_vec / beta_vec**1
end subroutine get_boost_parameters
function create_longitudinal_boost &
(beta_gamma, beta_vec, inverse) result (lambda)
real(default), intent(in) :: beta_gamma
type(vector3_t), intent(in) :: beta_vec
logical, intent(in) :: inverse
type(lorentz_transformation_t) :: lambda
if (inverse) then
lambda = boost (beta_gamma, beta_vec)
else
lambda = boost (-beta_gamma, beta_vec)
end if
end function create_longitudinal_boost
function create_transversal_boost (p_rad, xi, sqrts_real) result (lambda)
type(vector4_t), intent(in) :: p_rad
real(default), intent(in) :: xi, sqrts_real
type(lorentz_transformation_t) :: lambda
type(vector3_t) :: vec_transverse
real(default) :: pt2, beta, beta_gamma
pt2 = transverse_part (p_rad)**2
beta = one / sqrt (one + sqrts_real**2 * (one - xi) / pt2)
beta_gamma = beta / sqrt (one - beta**2)
vec_transverse%p(1:2) = p_rad%p(1:2)
vec_transverse%p(3) = zero
vec_transverse = normalize (vec_transverse)
lambda = boost (-beta_gamma, vec_transverse)
end function create_transversal_boost
end subroutine phs_fks_generator_generate_isr
@ %def phs_fks_generator_generate_isr
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: set_sqrts_hat => phs_fks_generator_set_sqrts_hat
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_set_sqrts_hat (generator, sqrts)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: sqrts
end subroutine phs_fks_generator_set_sqrts_hat
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_set_sqrts_hat (generator, sqrts)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: sqrts
generator%sqrts = sqrts
end subroutine phs_fks_generator_set_sqrts_hat
@ %def phs_fks_generator_set_sqrts_hat
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: set_emitters => phs_fks_generator_set_emitters
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_set_emitters (generator, emitters)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in), dimension(:), allocatable :: emitters
end subroutine phs_fks_generator_set_emitters
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_set_emitters (generator, emitters)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in), dimension(:), allocatable :: emitters
allocate (generator%emitters (size (emitters)))
generator%emitters = emitters
end subroutine phs_fks_generator_set_emitters
@ %def phs_fks_generator_set_emitters
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: setup_masses => phs_fks_generator_setup_masses
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_setup_masses (generator, n_tot)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: n_tot
end subroutine phs_fks_generator_setup_masses
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_setup_masses (generator, n_tot)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: n_tot
if (.not. allocated (generator%m2)) then
allocate (generator%is_massive (n_tot))
allocate (generator%m2 (n_tot))
generator%is_massive = .false.
generator%m2 = zero
end if
end subroutine phs_fks_generator_setup_masses
@ %def phs_fks_generator_setup_masses
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: set_xi_and_y_bounds => phs_fks_generator_set_xi_and_y_bounds
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_set_xi_and_y_bounds &
(generator, fks_xi_min, fks_y_max)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in), optional :: fks_xi_min, fks_y_max
end subroutine phs_fks_generator_set_xi_and_y_bounds
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_set_xi_and_y_bounds &
(generator, fks_xi_min, fks_y_max)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in), optional :: fks_xi_min, fks_y_max
real(default) :: xi_min, y_max
xi_min = zero; y_max = one
if (present (fks_xi_min)) xi_min = fks_xi_min
if (present (fks_y_max)) y_max = fks_y_max
generator%xi_min = min (one, max (xi_min, tiny_07))
generator%y_max = min (abs (y_max), one)
end subroutine phs_fks_generator_set_xi_and_y_bounds
@ %def phs_fks_generator_set_xi_and_y_bounds
@ Sets [[x]] in the [[isr_kinematics]] of the generator.
<<PHS fks: phs fks generator: TBP>>=
procedure :: set_isr_kinematics => phs_fks_generator_set_isr_kinematics
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_set_isr_kinematics (generator, p)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), dimension(2), intent(in) :: p
end subroutine phs_fks_generator_set_isr_kinematics
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_set_isr_kinematics (generator, p)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), dimension(2), intent(in) :: p
if (allocated (generator%isr_kinematics%beam_energy)) then
select case (size (generator%isr_kinematics%beam_energy))
case (1)
generator%isr_kinematics%x = p%p(0) / &
generator%isr_kinematics%beam_energy(1)
case (2)
generator%isr_kinematics%x = p%p(0) / &
generator%isr_kinematics%beam_energy
end select
else
generator%isr_kinematics%x = 0
end if
end subroutine phs_fks_generator_set_isr_kinematics
@ %def phs_fks_generator_set_isr_kinematics
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_radiation_variables => &
phs_fks_generator_generate_radiation_variables
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_radiation_variables &
(generator, r_in, p_born, phs_identifiers, threshold)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in), dimension(:) :: r_in
type(vector4_t), intent(in), dimension(:) :: p_born
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
logical, intent(in), optional :: threshold
end subroutine phs_fks_generator_generate_radiation_variables
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_radiation_variables &
(generator, r_in, p_born, phs_identifiers, threshold)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in), dimension(:) :: r_in
type(vector4_t), intent(in), dimension(:) :: p_born
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
logical, intent(in), optional :: threshold
associate (rad_var => generator%real_kinematics)
rad_var%phi = r_in (I_PHI) * twopi
select case (generator%mode)
case (GEN_REAL_PHASE_SPACE)
rad_var%jac_rand = twopi
call generator%compute_y_real_phs (r_in(I_Y), p_born, phs_identifiers, &
rad_var%jac_rand, rad_var%y, threshold)
case (GEN_SOFT_MISMATCH)
rad_var%jac_mismatch = twopi
call generator%compute_y_mismatch (r_in(I_Y), rad_var%jac_mismatch, &
rad_var%y_mismatch, rad_var%y_soft)
case default
call generator%compute_y_test (rad_var%y)
end select
call generator%compute_xi_tilde (r_in(I_XI))
call generator%set_masses (p_born, phs_identifiers)
end associate
end subroutine phs_fks_generator_generate_radiation_variables
@ %def phs_fks_generator_generate_radiation_variables
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_xi_ref_momenta => &
phs_fks_generator_compute_xi_ref_momenta
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_xi_ref_momenta &
(generator, p_born, resonance_contributors)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
type(resonance_contributors_t), intent(in), dimension(:), optional &
:: resonance_contributors
end subroutine phs_fks_generator_compute_xi_ref_momenta
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_xi_ref_momenta &
(generator, p_born, resonance_contributors)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
type(resonance_contributors_t), intent(in), dimension(:), optional &
:: resonance_contributors
integer :: i_con, n_contributors
if (present (resonance_contributors)) then
n_contributors = size (resonance_contributors)
if (.not. allocated (generator%resonance_contributors)) &
allocate (generator%resonance_contributors (n_contributors))
do i_con = 1, n_contributors
generator%real_kinematics%xi_ref_momenta(i_con) = &
get_resonance_momentum (p_born, resonance_contributors(i_con)%c)
generator%resonance_contributors(i_con) = &
resonance_contributors(i_con)
end do
else
generator%real_kinematics%xi_ref_momenta(1) = &
sum (p_born(1:generator%n_in))
end if
end subroutine phs_fks_generator_compute_xi_ref_momenta
@ %def phs_fks_generator_compute_xi_ref_momenta
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_xi_ref_momenta_threshold &
=> phs_fks_generator_compute_xi_ref_momenta_threshold
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_xi_ref_momenta_threshold &
(generator, p_born)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
end subroutine phs_fks_generator_compute_xi_ref_momenta_threshold
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_xi_ref_momenta_threshold &
(generator, p_born)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
generator%real_kinematics%xi_ref_momenta(1) = &
p_born(THR_POS_WP) + p_born(THR_POS_B)
generator%real_kinematics%xi_ref_momenta(2) = &
p_born(THR_POS_WM) + p_born(THR_POS_BBAR)
end subroutine phs_fks_generator_compute_xi_ref_momenta_threshold
@ %def phs_fks_generator_compute_xi_ref_momenta_threshold
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_cms_energy => phs_fks_generator_compute_cms_energy
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_cms_energy (generator, p_born)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
end subroutine phs_fks_generator_compute_cms_energy
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_cms_energy (generator, p_born)
class(phs_fks_generator_t), intent(inout) :: generator
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t) :: p_sum
p_sum = sum (p_born (1 : generator%n_in))
generator%real_kinematics%cms_energy2 = p_sum**2
end subroutine phs_fks_generator_compute_cms_energy
@ %def phs_fks_generator_compute_cms_energy
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_xi_max => phs_fks_generator_compute_xi_max
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_xi_max (generator, emitter, &
i_phs, p, xi_max, i_con, y_in)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs, emitter
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(out) :: xi_max
integer, intent(in), optional :: i_con
real(default), intent(in), optional :: y_in
end subroutine phs_fks_generator_compute_xi_max
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_xi_max (generator, emitter, &
i_phs, p, xi_max, i_con, y_in)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs, emitter
type(vector4_t), intent(in), dimension(:) :: p
real(default), intent(out) :: xi_max
integer, intent(in), optional :: i_con
real(default), intent(in), optional :: y_in
real(default) :: q0
type(vector4_t), dimension(:), allocatable :: pp, pp_decay
type(vector4_t) :: p_res
type(lorentz_transformation_t) :: L_to_resonance
real(default) :: y
if (.not. any (generator%emitters == emitter)) return
allocate (pp (size (p)))
associate (rad_var => generator%real_kinematics)
if (present (i_con)) then
q0 = rad_var%xi_ref_momenta(i_con)**1
else
q0 = energy (sum (p(1:generator%n_in)))
end if
if (present (y_in)) then
y = y_in
else
y = rad_var%y(i_phs)
end if
if (present (i_con)) then
p_res = rad_var%xi_ref_momenta(i_con)
L_to_resonance = inverse (boost (p_res, q0))
pp = L_to_resonance * p
else
pp = p
end if
if (emitter <= generator%n_in) then
select case (generator%isr_kinematics%isr_mode)
case (SQRTS_FIXED)
if (generator%n_in > 1) then
allocate (pp_decay (size (pp) - 1))
else
allocate (pp_decay (size (pp)))
end if
pp_decay (1) = sum (pp(1:generator%n_in))
pp_decay (2 : ) = pp (generator%n_in + 1 : )
xi_max = get_xi_max_isr_decay (pp_decay)
deallocate (pp_decay)
case (SQRTS_VAR)
xi_max = get_xi_max_isr (generator%isr_kinematics%x, y)
end select
else
if (generator%is_massive(emitter)) then
xi_max = get_xi_max_fsr (pp, q0, emitter, generator%m2(emitter), y)
else
xi_max = get_xi_max_fsr (pp, q0, emitter)
end if
end if
deallocate (pp)
end associate
end subroutine phs_fks_generator_compute_xi_max
@ %def phs_fks_generator_compute_xi_max
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_xi_max_isr_factorized &
=> phs_fks_generator_compute_xi_max_isr_factorized
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_xi_max_isr_factorized &
(generator, i_phs, p)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p
end subroutine phs_fks_generator_compute_xi_max_isr_factorized
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_xi_max_isr_factorized &
(generator, i_phs, p)
class(phs_fks_generator_t), intent(inout) :: generator
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p
generator%real_kinematics%xi_max(i_phs) = get_xi_max_isr_decay (p)
end subroutine phs_fks_generator_compute_xi_max_isr_factorized
@ %def phs_fks_generator_compute_xi_max_isr_factorized
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: set_masses => phs_fks_generator_set_masses
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_set_masses &
(generator, p, phs_identifiers)
class(phs_fks_generator_t), intent(inout) :: generator
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
type(vector4_t), intent(in), dimension(:) :: p
end subroutine phs_fks_generator_set_masses
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_set_masses &
(generator, p, phs_identifiers)
class(phs_fks_generator_t), intent(inout) :: generator
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
type(vector4_t), intent(in), dimension(:) :: p
integer :: emitter, i_phs
do i_phs = 1, size (phs_identifiers)
emitter = phs_identifiers(i_phs)%emitter
if (any (generator%emitters == emitter) .and. emitter > 0) then
if (generator%is_massive (emitter) .and. emitter > generator%n_in) &
generator%m2(emitter) = p(emitter)**2
end if
end do
end subroutine phs_fks_generator_set_masses
@ %def phs_fhs_generator_set_masses
@
<<PHS fks: public>>=
public :: compute_y_from_emitter
<<PHS fks: sub interfaces>>=
module subroutine compute_y_from_emitter (r_y, p, n_in, emitter, &
massive, y_max, jac_rand, y, contributors, threshold)
real(default), intent(in) :: r_y
type(vector4_t), intent(in), dimension(:) :: p
integer, intent(in) :: n_in
integer, intent(in) :: emitter
logical, intent(in) :: massive
real(default), intent(in) :: y_max
real(default), intent(inout) :: jac_rand
real(default), intent(out) :: y
integer, intent(in), dimension(:), allocatable, optional :: contributors
logical, intent(in), optional :: threshold
end subroutine compute_y_from_emitter
<<PHS fks: procedures>>=
module subroutine compute_y_from_emitter (r_y, p, n_in, emitter, &
massive, y_max, jac_rand, y, contributors, threshold)
real(default), intent(in) :: r_y
type(vector4_t), intent(in), dimension(:) :: p
integer, intent(in) :: n_in
integer, intent(in) :: emitter
logical, intent(in) :: massive
real(default), intent(in) :: y_max
real(default), intent(inout) :: jac_rand
real(default), intent(out) :: y
integer, intent(in), dimension(:), allocatable, optional :: contributors
logical, intent(in), optional :: threshold
logical :: thr, resonance
type(vector4_t) :: p_res, p_em
real(default) :: q0
type(lorentz_transformation_t) :: boost_to_resonance
integer :: i
real(default) :: beta, one_m_beta, one_p_beta
thr = .false.; if (present (threshold)) thr = threshold
p_res = vector4_null
if (present (contributors)) then
resonance = allocated (contributors)
else
resonance = .false.
end if
if (massive) then
if (resonance) then
do i = 1, size (contributors)
p_res = p_res + p(contributors(i))
end do
else if (thr) then
p_res = p(ass_boson(thr_leg(emitter))) + p(ass_quark(thr_leg(emitter)))
else
p_res = sum (p(1:n_in))
end if
q0 = p_res**1
boost_to_resonance = inverse (boost (p_res, q0))
p_em = boost_to_resonance * p(emitter)
beta = beta_emitter (q0, p_em)
one_m_beta = one - beta
one_p_beta = one + beta
y = one / beta * (one - one_p_beta * &
exp ( - r_y * log(one_p_beta / one_m_beta)))
jac_rand = jac_rand * &
(one - beta * y) * log(one_p_beta / one_m_beta) / beta
else
y = (one - two * r_y) * y_max
jac_rand = jac_rand * 3 * (one - y**2) * y_max
y = 1.5_default * (y - y**3 / 3)
end if
end subroutine compute_y_from_emitter
@ %def compute_y_from_emitter
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_y_real_phs => phs_fks_generator_compute_y_real_phs
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_y_real_phs &
(generator, r_y, p, phs_identifiers, &
jac_rand, y, threshold)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r_y
type(vector4_t), intent(in), dimension(:) :: p
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
real(default), intent(inout), dimension(:) :: jac_rand
real(default), intent(out), dimension(:) :: y
logical, intent(in), optional :: threshold
end subroutine phs_fks_generator_compute_y_real_phs
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_y_real_phs &
(generator, r_y, p, phs_identifiers, &
jac_rand, y, threshold)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r_y
type(vector4_t), intent(in), dimension(:) :: p
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
real(default), intent(inout), dimension(:) :: jac_rand
real(default), intent(out), dimension(:) :: y
logical, intent(in), optional :: threshold
- real(default) :: beta, one_p_beta, one_m_beta
- type(lorentz_transformation_t) :: boost_to_resonance
- real(default) :: q0
- type(vector4_t) :: p_res, p_em
integer :: i, i_phs, emitter
logical :: thr
logical :: construct_massive_fsr
construct_massive_fsr = .false.
thr = .false.; if (present (threshold)) thr = threshold
do i_phs = 1, size (phs_identifiers)
emitter = phs_identifiers(i_phs)%emitter
!!! We need this additional check because of decay phase spaces
!!! t -> bW has a massive emitter at position 1, which should
!!! not be treated here.
construct_massive_fsr = emitter > generator%n_in
if (construct_massive_fsr) construct_massive_fsr = &
construct_massive_fsr .and. generator%is_massive (emitter)
call compute_y_from_emitter (r_y, p, generator%n_in, &
emitter, construct_massive_fsr, &
generator%y_max, jac_rand(i_phs), y(i_phs), &
phs_identifiers(i_phs)%contributors, threshold)
end do
end subroutine phs_fks_generator_compute_y_real_phs
@ %def phs_fks_generator_compute_y_real_phs
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_y_mismatch => phs_fks_generator_compute_y_mismatch
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_y_mismatch &
(generator, r_y, jac_rand, y, y_soft)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r_y
real(default), intent(inout) :: jac_rand
real(default), intent(out) :: y
real(default), intent(out), dimension(:) :: y_soft
end subroutine phs_fks_generator_compute_y_mismatch
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_y_mismatch &
(generator, r_y, jac_rand, y, y_soft)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r_y
real(default), intent(inout) :: jac_rand
real(default), intent(out) :: y
real(default), intent(out), dimension(:) :: y_soft
y = (one - two * r_y) * generator%y_max
jac_rand = jac_rand * 3 * (one - y**2) * generator%y_max
y = 1.5_default * (y - y**3 / 3)
y_soft = y
end subroutine phs_fks_generator_compute_y_mismatch
@ %def phs_fks_generator_compute_y_mismatch
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_y_test => phs_fks_generator_compute_y_test
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_compute_y_test (generator, y)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(out), dimension(:):: y
end subroutine phs_fks_generator_compute_y_test
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_compute_y_test (generator, y)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(out), dimension(:):: y
select case (generator%mode)
case (GEN_SOFT_LIMIT_TEST)
y = y_test_soft
case (GEN_COLL_LIMIT_TEST)
y = y_test_coll
case (GEN_ANTI_COLL_LIMIT_TEST)
y = - y_test_coll
case (GEN_SOFT_COLL_LIMIT_TEST)
y = y_test_coll
case (GEN_SOFT_ANTI_COLL_LIMIT_TEST)
y = - y_test_coll
end select
end subroutine phs_fks_generator_compute_y_test
@ %def phs_fks_generator_compute_y_test
@
<<PHS fks: public>>=
public :: beta_emitter
<<PHS fks: sub interfaces>>=
pure module function beta_emitter (q0, p) result (beta)
real(default), intent(in) :: q0
type(vector4_t), intent(in) :: p
real(default) :: beta
end function beta_emitter
<<PHS fks: procedures>>=
pure module function beta_emitter (q0, p) result (beta)
real(default), intent(in) :: q0
type(vector4_t), intent(in) :: p
real(default) :: beta
real(default) :: m2, mrec2, k0_max
m2 = p**2
mrec2 = (q0 - p%p(0))**2 - p%p(1)**2 - p%p(2)**2 - p%p(3)**2
k0_max = (q0**2 - mrec2 + m2) / (two * q0)
beta = sqrt(one - m2 / k0_max**2)
end function beta_emitter
@ %def beta_emitter
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: compute_xi_tilde => phs_fks_generator_compute_xi_tilde
<<PHS fks: sub interfaces>>=
pure module subroutine phs_fks_generator_compute_xi_tilde (generator, r)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r
end subroutine phs_fks_generator_compute_xi_tilde
<<PHS fks: procedures>>=
pure module subroutine phs_fks_generator_compute_xi_tilde (generator, r)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: r
real(default) :: deno
associate (rad_var => generator%real_kinematics)
select case (generator%mode)
case (GEN_REAL_PHASE_SPACE)
if (generator%singular_jacobian) then
rad_var%xi_tilde = (one - generator%xi_min) - (one - r)**2 * &
(one - two * generator%xi_min)
rad_var%jac_rand = rad_var%jac_rand * two * (one - r) * &
(one - two * generator%xi_min)
else
rad_var%xi_tilde = generator%xi_min + r * (one - generator%xi_min)
rad_var%jac_rand = rad_var%jac_rand * (one - generator%xi_min)
end if
case (GEN_SOFT_MISMATCH)
deno = one - r
if (deno < tiny_13) deno = tiny_13
rad_var%xi_mismatch = generator%xi_min + r / deno
rad_var%jac_mismatch = rad_var%jac_mismatch / deno**2
case (GEN_SOFT_LIMIT_TEST)
rad_var%xi_tilde = r * two * xi_tilde_test_soft
rad_var%jac_rand = two * xi_tilde_test_soft
case (GEN_COLL_LIMIT_TEST)
rad_var%xi_tilde = xi_tilde_test_coll
rad_var%jac_rand = xi_tilde_test_coll
case (GEN_ANTI_COLL_LIMIT_TEST)
rad_var%xi_tilde = xi_tilde_test_coll
rad_var%jac_rand = xi_tilde_test_coll
case (GEN_SOFT_COLL_LIMIT_TEST)
rad_var%xi_tilde = r * two * xi_tilde_test_soft
rad_var%jac_rand = two * xi_tilde_test_soft
case (GEN_SOFT_ANTI_COLL_LIMIT_TEST)
rad_var%xi_tilde = r * two * xi_tilde_test_soft
rad_var%jac_rand = two * xi_tilde_test_soft
end select
end associate
end subroutine phs_fks_generator_compute_xi_tilde
@ %def phs_fks_generator_compute_xi_tilde
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: prepare_generation => phs_fks_generator_prepare_generation
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_prepare_generation (generator, &
r_in, i_phs, emitter, p_born, phs_identifiers, contributors, i_con)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), dimension(3), intent(in) :: r_in
integer, intent(in) :: i_phs, emitter
type(vector4_t), intent(in), dimension(:) :: p_born
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
type(resonance_contributors_t), intent(in), dimension(:), optional :: &
contributors
integer, intent(in), optional :: i_con
end subroutine phs_fks_generator_prepare_generation
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_prepare_generation (generator, &
r_in, i_phs, emitter, p_born, phs_identifiers, contributors, i_con)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), dimension(3), intent(in) :: r_in
integer, intent(in) :: i_phs, emitter
type(vector4_t), intent(in), dimension(:) :: p_born
type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers
type(resonance_contributors_t), intent(in), dimension(:), optional :: &
contributors
integer, intent(in), optional :: i_con
call generator%generate_radiation_variables (r_in, p_born, phs_identifiers)
call generator%compute_xi_ref_momenta &
(generator%real_kinematics%p_born_lab%phs_point(1)%get (), &
contributors)
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs), i_con = i_con)
end subroutine phs_fks_generator_prepare_generation
@ %def phs_fks_generator_prepare_generation
@ Get [[xi]] and [[y]] from an external routine (e.g. [[powheg]]) and
generate an FSR phase space. Note that the flag [[supply_xi_max]] is
set to [[.false.]] because it is assumed that the upper bound on [[xi]]
has already been taken into account during its generation.
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_fsr_from_xi_and_y => &
phs_fks_generator_generate_fsr_from_xi_and_y
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_fsr_from_xi_and_y &
(generator, xi, y, &
phi, emitter, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: xi, y, phi
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generator_generate_fsr_from_xi_and_y
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_fsr_from_xi_and_y &
(generator, xi, y, &
phi, emitter, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: xi, y, phi
integer, intent(in) :: emitter, i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
associate (rad_var => generator%real_kinematics)
rad_var%supply_xi_max = .false.
rad_var%xi_tilde = xi
rad_var%y(i_phs) = y
rad_var%phi = phi
end associate
call generator%set_sqrts_hat (p_born(1)%p(0) + p_born(2)%p(0))
call generator%generate_fsr (emitter, i_phs, p_born, p_real)
end subroutine phs_fks_generator_generate_fsr_from_xi_and_y
@ %def phs_fks_generator_generate_fsr_from_xi_and_y
@ Get [[xi]] and [[y]] from an external routine (e.g. [[powheg]]) and
generate the ISR phase space.
For this, we need to overwrite all variables of the real kinematics
dependent on $(\xi,y,\phi)$ that we need to compute [[p_real]].
From the [[isr_kinematics]] we just need [[sqme_born]] and [[x]].
Both do not depend on the real radiation so we can leave them as they are.
<<PHS fks: phs fks generator: TBP>>=
procedure :: generate_isr_from_xi_and_y => &
phs_fks_generator_generate_isr_from_xi_and_y
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_generate_isr_from_xi_and_y &
(generator, xi, xi_max, y, phi, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: xi, xi_max, y, phi
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
end subroutine phs_fks_generator_generate_isr_from_xi_and_y
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_generate_isr_from_xi_and_y &
(generator, xi, xi_max, y, phi, i_phs, p_born, p_real)
class(phs_fks_generator_t), intent(inout) :: generator
real(default), intent(in) :: xi, xi_max, y, phi
integer, intent(in) :: i_phs
type(vector4_t), intent(in), dimension(:) :: p_born
type(vector4_t), intent(out), dimension(:) :: p_real
associate (rad_var => generator%real_kinematics)
rad_var%xi_max(i_phs) = xi_max
rad_var%xi_tilde = xi / xi_max
rad_var%y(i_phs) = y
rad_var%phi = phi
end associate
call generator%generate_isr (i_phs, p_born, p_real)
end subroutine phs_fks_generator_generate_isr_from_xi_and_y
@ %def phs_fks_generator_generate_isr_from_xi_and_y
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: get_radiation_variables => &
phs_fks_generator_get_radiation_variables
<<PHS fks: sub interfaces>>=
pure module subroutine phs_fks_generator_get_radiation_variables &
(generator, i_phs, xi, y, phi)
class(phs_fks_generator_t), intent(in) :: generator
integer, intent(in) :: i_phs
real(default), intent(out) :: xi, y
real(default), intent(out), optional :: phi
end subroutine phs_fks_generator_get_radiation_variables
<<PHS fks: procedures>>=
pure module subroutine phs_fks_generator_get_radiation_variables &
(generator, i_phs, xi, y, phi)
class(phs_fks_generator_t), intent(in) :: generator
integer, intent(in) :: i_phs
real(default), intent(out) :: xi, y
real(default), intent(out), optional :: phi
associate (rad_var => generator%real_kinematics)
xi = rad_var%xi_max(i_phs) * rad_var%xi_tilde
y = rad_var%y(i_phs)
if (present (phi)) phi = rad_var%phi
end associate
end subroutine phs_fks_generator_get_radiation_variables
@ %def phs_fks_generator_get_radiation_variables
@
<<PHS fks: phs fks generator: TBP>>=
procedure :: write => phs_fks_generator_write
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_generator_write (generator, unit)
class(phs_fks_generator_t), intent(in) :: generator
integer, intent(in), optional :: unit
end subroutine phs_fks_generator_write
<<PHS fks: procedures>>=
module subroutine phs_fks_generator_write (generator, unit)
class(phs_fks_generator_t), intent(in) :: generator
integer, intent(in), optional :: unit
integer :: u
type(string_t) :: massive_phsp
u = given_output_unit (unit); if (u < 0) return
if (generator%massive_phsp) then
massive_phsp = " massive "
else
massive_phsp = " massless "
end if
write (u, "(A)") char ("This is a generator for a" &
// massive_phsp // "phase space")
if (associated (generator%real_kinematics)) then
call generator%real_kinematics%write ()
else
write (u, "(A)") "Warning: There are no real " // &
"kinematics associated with this generator"
end if
call write_separator (u)
write (u, "(A," // FMT_17 // ",1X)") "sqrts : ", generator%sqrts
write (u, "(A," // FMT_17 // ",1X)") "E_gluon : ", generator%E_gluon
write (u, "(A," // FMT_17 // ",1X)") "mrec2 : ", generator%mrec2
end subroutine phs_fks_generator_write
@ %def phs_fks_generator_write
@
<<PHS fks: phs fks: TBP>>=
procedure :: compute_isr_kinematics => phs_fks_compute_isr_kinematics
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_compute_isr_kinematics (phs, r)
class(phs_fks_t), intent(inout) :: phs
real(default), intent(in) :: r
end subroutine phs_fks_compute_isr_kinematics
<<PHS fks: procedures>>=
module subroutine phs_fks_compute_isr_kinematics (phs, r)
class(phs_fks_t), intent(inout) :: phs
real(default), intent(in) :: r
if (.not. phs%config%lab_is_cm) then
call phs%generator%compute_isr_kinematics &
(r, phs%lt_cm_to_lab * phs%phs_wood_t%p)
else
call phs%generator%compute_isr_kinematics (r, phs%phs_wood_t%p)
end if
end subroutine phs_fks_compute_isr_kinematics
@ %def phs_fks_compute_isr_kinematics
@
<<PHS fks: phs fks: TBP>>=
procedure :: final => phs_fks_final
<<PHS fks: sub interfaces>>=
module subroutine phs_fks_final (object)
class(phs_fks_t), intent(inout) :: object
end subroutine phs_fks_final
<<PHS fks: procedures>>=
module subroutine phs_fks_final (object)
class(phs_fks_t), intent(inout) :: object
call object%forest%final ()
call object%generator%final ()
end subroutine phs_fks_final
@ %def phs_fks_final
@
<<PHS fks: procedures>>=
subroutine filter_particles_from_resonances &
(res_hist, exclusion_list, &
model, res_hist_filtered)
type(resonance_history_t), intent(in), dimension(:) :: res_hist
type(string_t), intent(in), dimension(:) :: exclusion_list
type(model_t), intent(in) :: model
type(resonance_history_t), intent(out), dimension(:), allocatable :: &
res_hist_filtered
integer :: i_hist, i_flv, i_new, n_orig
logical, dimension(size (res_hist)) :: to_filter
type(flavor_t) :: flv
to_filter = .false.
n_orig = size (res_hist)
do i_flv = 1, size (exclusion_list)
call flv%init (exclusion_list (i_flv), model)
do i_hist = 1, size (res_hist)
if (res_hist(i_hist)%has_flavor (flv)) to_filter (i_hist) = .true.
end do
end do
allocate (res_hist_filtered (n_orig - count (to_filter)))
i_new = 1
do i_hist = 1, size (res_hist)
if (.not. to_filter (i_hist)) then
res_hist_filtered (i_new) = res_hist (i_hist)
i_new = i_new + 1
end if
end do
end subroutine filter_particles_from_resonances
@ %def filter_particles_from_resonances
@
<<PHS fks: procedures>>=
subroutine clean_resonance_histories &
(res_hist, n_in, flv, res_hist_clean, success)
type(resonance_history_t), intent(in), dimension(:) :: res_hist
integer, intent(in) :: n_in
integer, intent(in), dimension(:) :: flv
type(resonance_history_t), intent(out), dimension(:), allocatable :: &
res_hist_clean
logical, intent(out) :: success
integer :: i_hist
type(resonance_history_t), dimension(:), allocatable :: &
res_hist_colored, res_hist_contracted
if (debug_on) call msg_debug (D_SUBTRACTION, "resonance_mapping_init")
if (debug_active (D_SUBTRACTION)) then
call msg_debug (D_SUBTRACTION, "Original resonances:")
do i_hist = 1, size(res_hist)
call res_hist(i_hist)%write ()
end do
end if
call remove_uncolored_resonances ()
call contract_resonances (res_hist_colored, res_hist_contracted)
call remove_subresonances (res_hist_contracted, res_hist_clean)
!!! Here, we are still not sure whether we actually would rather use
!!! call remove_multiple_resonances (res_hist_contracted, res_hist_clean)
if (debug_active (D_SUBTRACTION)) then
call msg_debug (D_SUBTRACTION, "Resonances after removing uncolored and duplicates: ")
do i_hist = 1, size (res_hist_clean)
call res_hist_clean(i_hist)%write ()
end do
end if
if (size (res_hist_clean) == 0) then
call msg_warning ("No resonances found. Proceed in usual FKS mode.")
success = .false.
else
success = .true.
end if
contains
subroutine remove_uncolored_resonances ()
type(resonance_history_t), dimension(:), allocatable :: res_hist_tmp
integer :: n_hist, nleg_out, n_removed
integer :: i_res, i_hist
n_hist = size (res_hist)
nleg_out = size (flv) - n_in
allocate (res_hist_tmp (n_hist))
allocate (res_hist_colored (n_hist))
do i_hist = 1, n_hist
res_hist_tmp(i_hist) = res_hist(i_hist)
call res_hist_tmp(i_hist)%add_offset (n_in)
n_removed = 0
do i_res = 1, res_hist_tmp(i_hist)%n_resonances
associate (resonance => res_hist_tmp(i_hist)%resonances(i_res - n_removed))
if (.not. any (is_colored (flv (resonance%contributors%c))) &
.or. size (resonance%contributors%c) == nleg_out) then
call res_hist_tmp(i_hist)%remove_resonance (i_res - n_removed)
n_removed = n_removed + 1
end if
end associate
end do
if (allocated (res_hist_tmp(i_hist)%resonances)) then
if (any (res_hist_colored == res_hist_tmp(i_hist))) then
cycle
else
do i_res = 1, res_hist_tmp(i_hist)%n_resonances
associate (resonance => res_hist_tmp(i_hist)%resonances(i_res))
call res_hist_colored(i_hist)%add_resonance (resonance)
end associate
end do
end if
end if
end do
end subroutine remove_uncolored_resonances
subroutine contract_resonances (res_history_in, res_history_out)
type(resonance_history_t), intent(in), dimension(:) :: res_history_in
type(resonance_history_t), intent(out), dimension(:), allocatable :: res_history_out
logical, dimension(:), allocatable :: i_non_zero
integer :: n_hist_non_zero, n_hist
integer :: i_hist_new
n_hist = size (res_history_in); n_hist_non_zero = 0
allocate (i_non_zero (n_hist))
i_non_zero = .false.
do i_hist = 1, n_hist
if (res_history_in(i_hist)%n_resonances /= 0) then
n_hist_non_zero = n_hist_non_zero + 1
i_non_zero(i_hist) = .true.
end if
end do
allocate (res_history_out (n_hist_non_zero))
i_hist_new = 1
do i_hist = 1, n_hist
if (i_non_zero (i_hist)) then
res_history_out (i_hist_new) = res_history_in (i_hist)
i_hist_new = i_hist_new + 1
end if
end do
end subroutine contract_resonances
subroutine remove_subresonances (res_history_in, res_history_out)
type(resonance_history_t), intent(in), dimension(:) :: res_history_in
type(resonance_history_t), intent(out), dimension(:), allocatable :: res_history_out
logical, dimension(:), allocatable :: i_non_sub_res
integer :: n_hist, n_hist_non_sub_res
integer :: i_hist1, i_hist2
logical :: is_not_subres
n_hist = size (res_history_in); n_hist_non_sub_res = 0
allocate (i_non_sub_res (n_hist)); i_non_sub_res = .false.
do i_hist1 = 1, n_hist
is_not_subres = .true.
do i_hist2 = 1, n_hist
if (i_hist1 == i_hist2) cycle
is_not_subres = is_not_subres .and. &
.not.(res_history_in(i_hist2) .contains. res_history_in(i_hist1))
end do
if (is_not_subres) then
n_hist_non_sub_res = n_hist_non_sub_res + 1
i_non_sub_res (i_hist1) = .true.
end if
end do
allocate (res_history_out (n_hist_non_sub_res))
i_hist2 = 1
do i_hist1 = 1, n_hist
if (i_non_sub_res (i_hist1)) then
res_history_out (i_hist2) = res_history_in (i_hist1)
i_hist2 = i_hist2 + 1
end if
end do
end subroutine remove_subresonances
subroutine remove_multiple_resonances (res_history_in, res_history_out)
type(resonance_history_t), intent(in), dimension(:) :: res_history_in
type(resonance_history_t), intent(out), dimension(:), allocatable :: res_history_out
integer :: n_hist, n_hist_single
logical, dimension(:), allocatable :: i_hist_single
integer :: i_hist, j
n_hist = size (res_history_in)
n_hist_single = 0
allocate (i_hist_single (n_hist)); i_hist_single = .false.
do i_hist = 1, n_hist
if (res_history_in(i_hist)%n_resonances == 1) then
n_hist_single = n_hist_single + 1
i_hist_single(i_hist) = .true.
end if
end do
allocate (res_history_out (n_hist_single))
j = 1
do i_hist = 1, n_hist
if (i_hist_single(i_hist)) then
res_history_out(j) = res_history_in(i_hist)
j = j + 1
end if
end do
end subroutine remove_multiple_resonances
end subroutine clean_resonance_histories
@ %def clean_resonance_histories
@
<<PHS fks: public>>=
public :: get_filtered_resonance_histories
<<PHS fks: sub interfaces>>=
module subroutine get_filtered_resonance_histories &
(phs_config, n_in, flv_state, model, excluded_resonances, &
resonance_histories_filtered, success)
type(phs_fks_config_t), intent(inout) :: phs_config
integer, intent(in) :: n_in
integer, intent(in), dimension(:,:), allocatable :: flv_state
type(model_t), intent(in) :: model
type(string_t), intent(in), dimension(:), allocatable :: &
excluded_resonances
type(resonance_history_t), intent(out), dimension(:), &
allocatable :: resonance_histories_filtered
logical, intent(out) :: success
end subroutine get_filtered_resonance_histories
<<PHS fks: procedures>>=
module subroutine get_filtered_resonance_histories &
(phs_config, n_in, flv_state, model, excluded_resonances, &
resonance_histories_filtered, success)
type(phs_fks_config_t), intent(inout) :: phs_config
integer, intent(in) :: n_in
integer, intent(in), dimension(:,:), allocatable :: flv_state
type(model_t), intent(in) :: model
type(string_t), intent(in), dimension(:), allocatable :: &
excluded_resonances
type(resonance_history_t), intent(out), dimension(:), &
allocatable :: resonance_histories_filtered
logical, intent(out) :: success
type(resonance_history_t), dimension(:), allocatable :: resonance_histories
type(resonance_history_t), dimension(:), allocatable :: &
resonance_histories_clean!, resonance_histories_filtered
allocate (resonance_histories (size (phs_config%get_resonance_histories ())))
resonance_histories = phs_config%get_resonance_histories ()
call clean_resonance_histories (resonance_histories, &
n_in, flv_state (:,1), resonance_histories_clean, success)
if (success .and. allocated (excluded_resonances)) then
call filter_particles_from_resonances (resonance_histories_clean, &
excluded_resonances, model, resonance_histories_filtered)
else
allocate (resonance_histories_filtered (size (resonance_histories_clean)))
resonance_histories_filtered = resonance_histories_clean
end if
end subroutine get_filtered_resonance_histories
@ %def get_filtered_resonance_histories
@
\clearpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Unit tests}
Test module for FKS phase space, followed by the corresponding implementation module.
<<[[phs_fks_ut.f90]]>>=
<<File header>>
module phs_fks_ut
use unit_tests
use phs_fks_uti
<<Standard module head>>
<<PHS fks: public test>>
contains
<<PHS fks: test driver>>
end module phs_fks_ut
@ %def phs_fks_ut
@
<<[[phs_fks_uti.f90]]>>=
<<File header>>
module phs_fks_uti
<<Use kinds>>
use format_utils, only: write_separator, pac_fmt
use format_defs, only: FMT_15, FMT_19
use numeric_utils, only: nearly_equal
use constants, only: tiny_07, zero, one, two
use lorentz
use phs_points, only: assignment(=)
use physics_defs, only: THR_POS_B, THR_POS_BBAR, THR_POS_WP, THR_POS_WM, THR_POS_GLUON
use physics_defs, only: thr_leg
use resonances, only: resonance_contributors_t
use phs_fks
<<Standard module head>>
<<PHS fks: test declarations>>
contains
<<PHS fks: tests>>
end module phs_fks_uti
@ %def phs_fks_uti
@ API: driver for the unit tests below.
<<PHS fks: public test>>=
public :: phs_fks_generator_test
<<PHS fks: test driver>>=
subroutine phs_fks_generator_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
call test(phs_fks_generator_1, "phs_fks_generator_1", &
"Test the generation of FKS phase spaces", u, results)
call test(phs_fks_generator_2, "phs_fks_generator_2", &
"Test the generation of an ISR FKS phase space", u, results)
call test(phs_fks_generator_3, "phs_fks_generator_3", &
"Test the generation of a real phase space for decays", &
u, results)
call test(phs_fks_generator_4, "phs_fks_generator_4", &
"Test the generation of an FSR phase space with "&
&"conserved invariant resonance masses", u, results)
call test(phs_fks_generator_5, "phs_fks_generator_5", &
"Test on-shell projection of a Born phase space and the generation"&
&" of a real phase-space from that", u, results)
call test(phs_fks_generator_6, "phs_fks_generator_6", &
"Test the generation of a real phase space for 1 -> 3 decays", &
u, results)
call test(phs_fks_generator_7, "phs_fks_generator_7", &
"Test the generation of an ISR FKS phase space for fixed beam energy", &
u, results)
end subroutine phs_fks_generator_test
@ %def phs_fks_generator_test
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_1
<<PHS fks: tests>>=
subroutine phs_fks_generator_1 (u)
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: p_real
integer :: emitter, i_phs
real(default) :: x1, x2, x3
real(default), parameter :: sqrts = 250.0_default
type(phs_identifier_t), dimension(2) :: phs_identifiers
write (u, "(A)") "* Test output: phs_fks_generator_1"
write (u, "(A)") "* Purpose: Create massless fsr phase space"
write (u, "(A)")
allocate (p_born (4))
p_born(1)%p(0) = 125.0_default
p_born(1)%p(1:2) = 0.0_default
p_born(1)%p(3) = 125.0_default
p_born(2)%p(0) = 125.0_default
p_born(2)%p(1:2) = 0.0_default
p_born(2)%p(3) = -125.0_default
p_born(3)%p(0) = 125.0_default
p_born(3)%p(1) = -39.5618_default
p_born(3)%p(2) = -20.0791_default
p_born(3)%p(3) = -114.6957_default
p_born(4)%p(0) = 125.0_default
p_born(4)%p(1:3) = -p_born(3)%p(1:3)
allocate (generator%isr_kinematics)
generator%n_in = 2
generator%isr_kinematics%isr_mode = SQRTS_FIXED
call generator%set_xi_and_y_bounds ()
call generator%set_sqrts_hat (sqrts)
write (u, "(A)") "* Use four-particle phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
write (u, "(A)") "***********************"
write (u, "(A)")
x1 = 0.5_default; x2 = 0.25_default; x3 = 0.75_default
write (u, "(A)" ) "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,1X,A,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
allocate (generator%real_kinematics)
call generator%real_kinematics%init (4, 2, 2, 1)
allocate (generator%emitters (2))
generator%emitters(1) = 3; generator%emitters(2) = 4
allocate (generator%m2 (4))
generator%m2 = zero
allocate (generator%is_massive (4))
generator%is_massive(1:2) = .false.
generator%is_massive(3:4) = .true.
phs_identifiers(1)%emitter = 3
phs_identifiers(2)%emitter = 4
call generator%compute_xi_ref_momenta (p_born)
call generator%generate_radiation_variables ([x1,x2,x3], p_born, phs_identifiers)
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs))
end do
write (u, "(A)") &
"* With these, the following radiation variables have been produced:"
associate (rad_var => generator%real_kinematics)
write (u, "(A,F3.2)") "xi_tilde: ", rad_var%xi_tilde
write (u, "(A,F3.2)") "y: " , rad_var%y(1)
write (u, "(A,F3.2)") "phi: ", rad_var%phi
end associate
call write_separator (u)
write (u, "(A)") "Produce real momenta: "
i_phs = 1; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
allocate (p_real (5))
call generator%generate_fsr (emitter, i_phs, p_born, p_real)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
call write_separator (u)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_1"
end subroutine phs_fks_generator_1
@ %def phs_fks_generator_1
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_2
<<PHS fks: tests>>=
subroutine phs_fks_generator_2 (u)
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: p_real
integer :: emitter, i_phs
real(default) :: x1, x2, x3
real(default), parameter :: sqrts_hadronic = 250.0_default
type(phs_identifier_t), dimension(2) :: phs_identifiers
write (u, "(A)") "* Test output: phs_fks_generator_2"
write (u, "(A)") "* Purpose: Create massless ISR phase space"
write (u, "(A)")
allocate (p_born (4))
p_born(1)%p(0) = 114.661_default
p_born(1)%p(1:2) = 0.0_default
p_born(1)%p(3) = 114.661_default
p_born(2)%p(0) = 121.784_default
p_born(2)%p(1:2) = 0.0_default
p_born(2)%p(3) = -121.784_default
p_born(3)%p(0) = 115.148_default
p_born(3)%p(1) = -46.250_default
p_born(3)%p(2) = -37.711_default
p_born(3)%p(3) = 98.478_default
p_born(4)%p(0) = 121.296_default
p_born(4)%p(1:2) = -p_born(3)%p(1:2)
p_born(4)%p(3) = -105.601_default
phs_identifiers(1)%emitter = 1
phs_identifiers(2)%emitter = 2
allocate (generator%emitters (2))
allocate (generator%isr_kinematics)
generator%emitters(1) = 1; generator%emitters(2) = 2
generator%sqrts = sqrts_hadronic
allocate (generator%isr_kinematics%beam_energy(2))
generator%isr_kinematics%beam_energy = sqrts_hadronic / two
call generator%set_sqrts_hat (sqrts_hadronic)
call generator%set_isr_kinematics (p_born)
generator%n_in = 2
generator%isr_kinematics%isr_mode = SQRTS_VAR
call generator%set_xi_and_y_bounds ()
write (u, "(A)") "* Use four-particle phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
write (u, "(A)") "***********************"
write (u, "(A)")
x1=0.5_default; x2=0.25_default; x3=0.65_default
write (u, "(A)" ) "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,1X,A,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
allocate (generator%real_kinematics)
call generator%real_kinematics%init (4, 2, 2, 1)
call generator%real_kinematics%p_born_lab%set_momenta (1, p_born)
allocate (generator%m2 (2))
generator%m2(1) = 0._default; generator%m2(2) = 0._default
allocate (generator%is_massive (4))
generator%is_massive = .false.
call generator%generate_radiation_variables ([x1,x2,x3], p_born, phs_identifiers)
call generator%compute_xi_ref_momenta (p_born)
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs))
end do
write (u, "(A)") &
"* With these, the following radiation variables have been produced:"
associate (rad_var => generator%real_kinematics)
write (u, "(A,F3.2)") "xi_tilde: ", rad_var%xi_tilde
write (u, "(A,F3.2)") "y: " , rad_var%y(1)
write (u, "(A,F3.2)") "phi: ", rad_var%phi
end associate
write (u, "(A)") "Initial-state momentum fractions: "
associate (xb => generator%isr_kinematics%x)
write (u, "(A,F3.2)") "x_born_plus: ", xb(1)
write (u, "(A,F3.2)") "x_born_minus: ", xb(2)
end associate
call write_separator (u)
write (u, "(A)") "Produce real momenta: "
i_phs = 1; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
allocate (p_real(5))
call generator%generate_isr (i_phs, p_born, p_real)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
call write_separator (u)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_2"
end subroutine phs_fks_generator_2
@ %def phs_fks_generator_2
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_3
<<PHS fks: tests>>=
subroutine phs_fks_generator_3 (u)
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: p_real
real(default) :: x1, x2, x3
real(default) :: mB, mW, mT
integer :: i, emitter, i_phs
type(phs_identifier_t), dimension(2) :: phs_identifiers
write (u, "(A)") "* Test output: phs_fks_generator_3"
write (u, "(A)") "* Puropse: Create real phase space for particle decays"
write (u, "(A)")
allocate (p_born(3))
p_born(1)%p(0) = 172._default
p_born(1)%p(1) = 0._default
p_born(1)%p(2) = 0._default
p_born(1)%p(3) = 0._default
p_born(2)%p(0) = 104.72866679_default
p_born(2)%p(1) = 45.028053213_default
p_born(2)%p(2) = 29.450337581_default
p_born(2)%p(3) = -5.910229156_default
p_born(3)%p(0) = 67.271333209_default
p_born(3)%p(1:3) = -p_born(2)%p(1:3)
generator%n_in = 1
allocate (generator%isr_kinematics)
generator%isr_kinematics%isr_mode = SQRTS_FIXED
call generator%set_xi_and_y_bounds ()
mB = 4.2_default
mW = 80.376_default
mT = 172._default
generator%sqrts = mT
write (u, "(A)") "* Use three-particle phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
write (u, "(A)") "**********************"
write (u, "(A)")
x1 = 0.5_default; x2 = 0.25_default; x3 = 0.6_default
write (u, "(A)") "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,A,1X,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
allocate (generator%real_kinematics)
call generator%real_kinematics%init (3, 2, 2, 1)
call generator%real_kinematics%p_born_lab%set_momenta (1, p_born)
allocate (generator%emitters(2))
generator%emitters(1) = 1
generator%emitters(2) = 3
allocate (generator%m2 (3), generator%is_massive(3))
generator%m2(1) = mT**2
generator%m2(2) = mW**2
generator%m2(3) = mB**2
generator%is_massive = .true.
phs_identifiers(1)%emitter = 1
phs_identifiers(2)%emitter = 3
call generator%generate_radiation_variables ([x1,x2,x3], p_born, phs_identifiers)
call generator%compute_xi_ref_momenta (p_born)
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs))
end do
write (u, "(A)") &
"* With these, the following radiation variables have been produced: "
associate (rad_var => generator%real_kinematics)
write (u, "(A,F4.2)") "xi_tilde: ", rad_var%xi_tilde
do i = 1, 2
write (u, "(A,I1,A,F5.2)") "i: ", i, "y: " , rad_var%y(i)
end do
write (u, "(A,F4.2)") "phi: ", rad_var%phi
end associate
call write_separator (u)
write (u, "(A)") "Produce real momenta via initial-state emission: "
i_phs = 1; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
allocate (p_real (4))
call generator%generate_isr_fixed_beam_energy (i_phs, p_born, p_real)
call pacify (p_real, 1E-6_default)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
call write_separator(u)
write (u, "(A)") "Produce real momenta via final-state emisson: "
i_phs = 2; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
call generator%generate_fsr (emitter, i_phs, p_born, p_real)
call pacify (p_real, 1E-6_default)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_3"
end subroutine phs_fks_generator_3
@ %def phs_fks_generator_3
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_4
<<PHS fks: tests>>=
subroutine phs_fks_generator_4 (u)
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: p_real
integer, dimension(:), allocatable :: emitters
integer, dimension(:,:), allocatable :: resonance_lists
type(resonance_contributors_t), dimension(2) :: alr_contributors
real(default) :: x1, x2, x3
real(default), parameter :: sqrts = 250.0_default
integer, parameter :: nlegborn = 6
integer :: i_phs, i_con, emitter
real(default) :: m_inv_born, m_inv_real
character(len=7) :: fmt
type(phs_identifier_t), dimension(2) :: phs_identifiers
call pac_fmt (fmt, FMT_19, FMT_15, .true.)
write (u, "(A)") "* Test output: phs_fks_generator_4"
write (u, "(A)") "* Purpose: Create FSR phase space with fixed resonances"
write (u, "(A)")
allocate (p_born (nlegborn))
p_born(1)%p(0) = 250._default
p_born(1)%p(1) = 0._default
p_born(1)%p(2) = 0._default
p_born(1)%p(3) = 250._default
p_born(2)%p(0) = 250._default
p_born(2)%p(1) = 0._default
p_born(2)%p(2) = 0._default
p_born(2)%p(3) = -250._default
p_born(3)%p(0) = 145.91184486_default
p_born(3)%p(1) = 50.39727589_default
p_born(3)%p(2) = 86.74156041_default
p_born(3)%p(3) = -69.03608748_default
p_born(4)%p(0) = 208.1064784_default
p_born(4)%p(1) = -44.07610020_default
p_born(4)%p(2) = -186.34264578_default
p_born(4)%p(3) = 13.48038407_default
p_born(5)%p(0) = 26.25614471_default
p_born(5)%p(1) = -25.12258068_default
p_born(5)%p(2) = -1.09540228_default
p_born(5)%p(3) = -6.27703505_default
p_born(6)%p(0) = 119.72553196_default
p_born(6)%p(1) = 18.80140499_default
p_born(6)%p(2) = 100.69648766_default
p_born(6)%p(3) = 61.83273846_default
allocate (generator%isr_kinematics)
generator%n_in = 2
generator%isr_kinematics%isr_mode = SQRTS_FIXED
call generator%set_xi_and_y_bounds ()
call generator%set_sqrts_hat (sqrts)
write (u, "(A)") "* Test process: e+ e- -> W+ W- b b~"
write (u, "(A)") "* Resonance pairs: (3,5) and (4,6)"
write (u, "(A)") "* Use four-particle phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
write (u, "(A)") "******************************"
write (u, "(A)")
x1 = 0.5_default; x2 = 0.25_default; x3 = 0.75_default
write (u, "(A)") "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,1X,A,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
allocate (generator%real_kinematics)
call generator%real_kinematics%init (nlegborn, 2, 2, 2)
allocate (generator%emitters (2))
generator%emitters(1) = 5; generator%emitters(2) = 6
allocate (generator%m2 (nlegborn))
generator%m2 = p_born**2
allocate (generator%is_massive (nlegborn))
generator%is_massive (1:2) = .false.
generator%is_massive (3:6) = .true.
phs_identifiers(1)%emitter = 5
phs_identifiers(2)%emitter = 6
do i_phs = 1, 2
allocate (phs_identifiers(i_phs)%contributors (2))
end do
allocate (resonance_lists (2, 2))
resonance_lists (1,:) = [3,5]
resonance_lists (2,:) = [4,6]
!!! Here is obviously some redundance. Surely we can improve on this.
do i_phs = 1, 2
phs_identifiers(i_phs)%contributors = resonance_lists(i_phs,:)
end do
do i_con = 1, 2
allocate (alr_contributors(i_con)%c (size (resonance_lists(i_con,:))))
alr_contributors(i_con)%c = resonance_lists(i_con,:)
end do
call generator%generate_radiation_variables &
([x1, x2, x3], p_born, phs_identifiers)
allocate (p_real(nlegborn + 1))
call generator%compute_xi_ref_momenta (p_born, alr_contributors)
!!! Keep the distinction between i_phs and i_con because in general,
!!! they are not the same.
do i_phs = 1, 2
i_con = i_phs
emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1,1X,A,I1,A,I1,A)") &
"* Generate FSR phase space for emitter ", emitter, &
"and resonance pair (", resonance_lists (i_con, 1), ",", &
resonance_lists (i_con, 2), ")"
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs), i_con = i_con)
call generator%generate_fsr (emitter, i_phs, i_con, p_born, p_real)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
call write_separator(u)
write (u, "(A)") "* Check if resonance masses are conserved: "
m_inv_born = compute_resonance_mass (p_born, resonance_lists (i_con,:))
m_inv_real = compute_resonance_mass (p_real, resonance_lists (i_con,:), 7)
write (u, "(A,1X, " // fmt // ")") "m_inv_born = ", m_inv_born
write (u, "(A,1X, " // fmt // ")") "m_inv_real = ", m_inv_real
if (abs (m_inv_born - m_inv_real) < tiny_07) then
write (u, "(A)") " Success! "
else
write (u, "(A)") " Failure! "
end if
call write_separator(u)
call write_separator(u)
end do
deallocate (p_real)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_4"
end subroutine phs_fks_generator_4
@ %def phs_fks_generator_4
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_5
<<PHS fks: tests>>=
subroutine phs_fks_generator_5 (u)
use ttv_formfactors, only: init_parameters
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born, pb1
type(vector4_t), dimension(:), allocatable :: p_born_onshell, pb1_os
type(vector4_t), dimension(:), allocatable :: p_real
real(default) :: x1, x2, x3
real(default) :: mB, mW, mtop, mcheck
integer :: i, emitter, i_phs
type(phs_identifier_t), dimension(2) :: phs_identifiers
type(lorentz_transformation_t) :: L_to_cms
real(default), parameter :: sqrts = 360._default
real(default), parameter :: momentum_tolerance = 1E-10_default
real(default) :: mpole, gam_out
write (u, "(A)") "* Test output: phs_fks_generator_5"
write (u, "(A)") "* Puropse: Perform threshold on-shell projection of "
write (u, "(A)") "* Born momenta and create a real phase-space "
write (u, "(A)") "* point from those. "
write (u, "(A)")
allocate (p_born(6), p_born_onshell(6))
p_born(1)%p(0) = sqrts / two
p_born(1)%p(1:2) = zero
p_born(1)%p(3) = sqrts / two
p_born(2)%p(0) = sqrts / two
p_born(2)%p(1:2) = zero
p_born(2)%p(3) = -sqrts / two
p_born(3)%p(0) = 117.1179139230_default
p_born(3)%p(1) = 56.91215483880_default
p_born(3)%p(2) = -40.02386013017_default
p_born(3)%p(3) = -49.07634310496_default
p_born(4)%p(0) = 98.91904548743_default
p_born(4)%p(1) = 56.02241403836_default
p_born(4)%p(2) = -8.302977504723_default
p_born(4)%p(3) = -10.50293716131_default
p_born(5)%p(0) = 62.25884689208_default
p_born(5)%p(1) = -60.00786540278_default
p_born(5)%p(2) = 4.753602375910_default
p_born(5)%p(3) = 15.32916731546_default
p_born(6)%p(0) = 81.70419369751_default
p_born(6)%p(1) = -52.92670347439_default
p_born(6)%p(2) = 43.57323525898_default
p_born(6)%p(3) = 44.25011295081_default
generator%n_in = 2
allocate (generator%isr_kinematics)
generator%isr_kinematics%isr_mode = SQRTS_FIXED
call generator%set_xi_and_y_bounds ()
mB = 4.2_default
mW = 80.376_default
mtop = 172._default
generator%sqrts = sqrts
!!! Dummy-initialization of the threshold model because generate_fsr_threshold
!!! uses m1s_to_mpole to determine if it is above or below threshold.
call init_parameters (mpole, gam_out, mtop, one, one / 1.5_default, 125._default, &
0.47_default, 0.118_default, 91._default, 80._default, 4.2_default, &
one, one, one, one, zero, zero, zero, zero, zero, zero, .false., zero)
write (u, "(A)") "* Use four-particle phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
call vector4_check_momentum_conservation &
(p_born, 2, unit = u, abs_smallness = momentum_tolerance, verbose = .true.)
write (u, "(A)") "**********************"
write (u, "(A)")
allocate (generator%real_kinematics)
call generator%real_kinematics%init (7, 2, 2, 2)
call generator%real_kinematics%init_onshell (7, 2)
generator%real_kinematics%p_born_cms%phs_point(1) = p_born
write (u, "(A)") "Get boost projection system -> CMS: "
L_to_cms = get_boost_for_threshold_projection (p_born, sqrts, mtop)
call L_to_cms%write (u, testflag = .true., ultra = .true.)
write (u, "(A)") "**********************"
write (u, "(A)")
write (u, "(A)") "* Perform onshell-projection:"
pb1 = generator%real_kinematics%p_born_cms%phs_point(1)
call threshold_projection_born (mtop, L_to_cms, pb1, p_born_onshell)
generator%real_kinematics%p_born_onshell%phs_point(1) = p_born_onshell
call generator%real_kinematics%p_born_onshell%write &
(1, unit = u, testflag = .true., ultra = .true.)
pb1_os = generator%real_kinematics%p_born_onshell%phs_point(1)
call check_phsp (pb1_os, 0)
allocate (generator%emitters (2))
generator%emitters(1) = THR_POS_B; generator%emitters(2) = THR_POS_BBAR
allocate (generator%m2 (6), generator%is_massive(6))
generator%m2 = p_born**2
generator%is_massive (1:2) = .false.
generator%is_massive (3:6) = .true.
phs_identifiers(1)%emitter = THR_POS_B
phs_identifiers(2)%emitter = THR_POS_BBAR
x1 = 0.5_default; x2 = 0.25_default; x3 = 0.6_default
write (u, "(A)") "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,A,1X,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
call generator%generate_radiation_variables ([x1,x2,x3], p_born_onshell, phs_identifiers)
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
call generator%compute_xi_ref_momenta_threshold (p_born_onshell)
call generator%compute_xi_max (emitter, i_phs, p_born_onshell, &
generator%real_kinematics%xi_max(i_phs), i_con = thr_leg(emitter))
end do
write (u, "(A)") &
"* With these, the following radiation variables have been produced: "
associate (rad_var => generator%real_kinematics)
write (u, "(A,F4.2)") "xi_tilde: ", rad_var%xi_tilde
write (u, "(A)") "xi_max: "
write (u, "(2F5.2)") rad_var%xi_max(1), rad_var%xi_max(2)
write (u, "(A)") "y: "
write (u, "(2F5.2)") rad_var%y(1), rad_var%y(2)
write (u, "(A,F4.2)") "phi: ", rad_var%phi
end associate
call write_separator (u)
write (u, "(A)") "* Produce real momenta from on-shell phase space: "
allocate (p_real(7))
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
call generator%generate_fsr_threshold (emitter, i_phs, p_born_onshell, p_real)
call check_phsp (p_real, emitter)
end do
call write_separator(u)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_5"
contains
subroutine check_phsp (p, emitter)
type(vector4_t), intent(inout), dimension(:) :: p
integer, intent(in) :: emitter
type(vector4_t) :: pp
real(default) :: E_tot
logical :: check
write (u, "(A)") "* Check momentum conservation: "
call vector4_check_momentum_conservation &
(p, 2, unit = u, abs_smallness = momentum_tolerance, verbose = .true.)
write (u, "(A)") "* Check invariant masses: "
write (u, "(A)", advance = "no") "inv(W+, b, gl): "
pp = p(THR_POS_WP) + p(THR_POS_B)
if (emitter == THR_POS_B) pp = pp + p(THR_POS_GLUON)
if (nearly_equal (pp**1, mtop)) then
write (u, "(A)") "CHECK"
else
write (u, "(A,F7.3)") "FAIL: ", pp**1
end if
write (u, "(A)", advance = "no") "inv(W-, bbar): "
pp = p(THR_POS_WM) + p(THR_POS_BBAR)
if (emitter == THR_POS_BBAR) pp = pp + p(THR_POS_GLUON)
if (nearly_equal (pp**1, mtop)) then
write (u, "(A)") "CHECK"
else
write (u, "(A,F7.3)") "FAIL: ", pp**1
end if
write (u, "(A)") "* Sum of energies equal to sqrts?"
E_tot = sum(p(1:2)%p(0)); check = nearly_equal (E_tot, sqrts)
write (u, "(A,L1)") "Initial state: ", check
if (.not. check) write (u, "(A,F7.3)") "E_tot: ", E_tot
if (emitter > 0) then
E_tot = sum(p(3:7)%p(0))
else
E_tot = sum(p(3:6)%p(0))
end if
check = nearly_equal (E_tot, sqrts)
write (u, "(A,L1)") "Final state : ", check
if (.not. check) write (u, "(A,F7.3)") "E_tot: ", E_tot
call pacify (p, 1E-6_default)
call vector4_write_set (p, u, testflag = .true., ultra = .true.)
end subroutine check_phsp
end subroutine phs_fks_generator_5
@ %def phs_fks_generator_5
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_6
<<PHS fks: tests>>=
subroutine phs_fks_generator_6 (u)
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: p_real
real(default) :: x1, x2, x3
real(default) :: mB, mW, mT
integer :: i, emitter, i_phs
type(phs_identifier_t), dimension(2) :: phs_identifiers
write (u, "(A)") "* Test output: phs_fks_generator_6"
write (u, "(A)") "* Puropse: Create real phase space for particle decays"
write (u, "(A)")
allocate (p_born(4))
p_born(1)%p(0) = 173.1_default
p_born(1)%p(1) = zero
p_born(1)%p(2) = zero
p_born(1)%p(3) = zero
p_born(2)%p(0) = 68.17074462929_default
p_born(2)%p(1) = -37.32578717617_default
p_born(2)%p(2) = 30.99675959336_default
p_born(2)%p(3) = -47.70321718398_default
p_born(3)%p(0) = 65.26639312326_default
p_born(3)%p(1) = -1.362927648502_default
p_born(3)%p(2) = -33.25327150840_default
p_born(3)%p(3) = 56.14324922494_default
p_born(4)%p(0) = 39.66286224745_default
p_born(4)%p(1) = 38.68871482467_default
p_born(4)%p(2) = 2.256511915049_default
p_born(4)%p(3) = -8.440032040958_default
generator%n_in = 1
allocate (generator%isr_kinematics)
generator%isr_kinematics%isr_mode = SQRTS_FIXED
call generator%set_xi_and_y_bounds ()
mB = 4.2_default
mW = 80.376_default
mT = 173.1_default
generator%sqrts = mT
write (u, "(A)") "* Use four-particle phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
write (u, "(A)") "**********************"
write (u, "(A)")
x1=0.5_default; x2=0.25_default; x3=0.6_default
write (u, "(A)") "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,A,1X,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
allocate (generator%real_kinematics)
call generator%real_kinematics%init (3, 2, 2, 1)
call generator%real_kinematics%p_born_lab%set_momenta (1, p_born)
allocate (generator%emitters(2))
generator%emitters(1) = 1
generator%emitters(2) = 2
allocate (generator%m2 (4), generator%is_massive(4))
generator%m2(1) = mT**2
generator%m2(2) = mB**2
generator%m2(3) = zero
generator%m2(4) = zero
generator%is_massive(1:2) = .true.
generator%is_massive(3:4) = .false.
phs_identifiers(1)%emitter = 1
phs_identifiers(2)%emitter = 2
call generator%generate_radiation_variables ([x1,x2,x3], p_born, phs_identifiers)
call generator%compute_xi_ref_momenta (p_born)
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs))
end do
write (u, "(A)") &
"* With these, the following radiation variables have been produced: "
associate (rad_var => generator%real_kinematics)
write (u, "(A,F4.2)") "xi_tilde: ", rad_var%xi_tilde
do i = 1, 2
write (u, "(A,I1,A,F5.2)") "i: ", i, "y: " , rad_var%y(i)
end do
write (u, "(A,F4.2)") "phi: ", rad_var%phi
end associate
call write_separator (u)
write (u, "(A)") "Produce real momenta via initial-state emission: "
i_phs = 1; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
allocate (p_real(5))
call generator%generate_isr_fixed_beam_energy (i_phs, p_born, p_real)
call pacify (p_real, 1E-6_default)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
call write_separator(u)
write (u, "(A)") "Produce real momenta via final-state emisson: "
i_phs = 2; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
call generator%generate_fsr (emitter, i_phs, p_born, p_real)
call pacify (p_real, 1E-6_default)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_6"
end subroutine phs_fks_generator_6
@ %def phs_fks_generator_6
@
<<PHS fks: test declarations>>=
public :: phs_fks_generator_7
<<PHS fks: tests>>=
subroutine phs_fks_generator_7 (u)
integer, intent(in) :: u
type(phs_fks_generator_t) :: generator
type(vector4_t), dimension(:), allocatable :: p_born
type(vector4_t), dimension(:), allocatable :: p_real
real(default) :: x1, x2, x3
integer :: i, emitter, i_phs
type(phs_identifier_t), dimension(2) :: phs_identifiers
real(default), parameter :: sqrts = 1000.0_default
write (u, "(A)") "* Test output: phs_fks_generator_7"
write (u, "(A)") "* Puropse: Create real phase space for scattering ISR"
write (u, "(A)") "* keeping the beam energy fixed."
write (u, "(A)")
allocate (p_born(4))
p_born(1)%p(0) = 500._default
p_born(1)%p(1) = 0._default
p_born(1)%p(2) = 0._default
p_born(1)%p(3) = 500._default
p_born(2)%p(0) = 500._default
p_born(2)%p(1) = 0._default
p_born(2)%p(2) = 0._default
p_born(2)%p(3) = -500._default
p_born(3)%p(0) = 500._default
p_born(3)%p(1) = 11.275563070_default
p_born(3)%p(2) = -13.588797663_default
p_born(3)%p(3) = 486.93070588_default
p_born(4)%p(0) = 500._default
p_born(4)%p(1:3) = -p_born(3)%p(1:3)
phs_identifiers(1)%emitter = 1
phs_identifiers(2)%emitter = 2
allocate (generator%emitters(2))
generator%n_in = 2
allocate (generator%isr_kinematics)
generator%isr_kinematics%isr_mode = SQRTS_FIXED
call generator%set_xi_and_y_bounds ()
generator%emitters(1) = 1; generator%emitters(2) = 2
generator%sqrts = sqrts
write (u, "(A)") "* Use 2 -> 2 phase space containing: "
call vector4_write_set (p_born, u, testflag = .true., ultra = .true.)
write (u, "(A)") "**********************"
write (u, "(A)")
x1 = 0.5_default; x2 = 0.25_default; x3 = 0.6_default
write (u, "(A)") "* Use random numbers: "
write (u, "(A,F3.2,1X,A,F3.2,A,1X,F3.2)") &
"x1: ", x1, "x2: ", x2, "x3: ", x3
allocate (generator%real_kinematics)
call generator%real_kinematics%init (4, 2, 2, 1)
call generator%real_kinematics%p_born_lab%set_momenta (1, p_born)
allocate (generator%m2 (4))
generator%m2 = 0._default
allocate (generator%is_massive(4))
generator%is_massive = .false.
call generator%generate_radiation_variables ([x1,x2,x3], p_born, phs_identifiers)
call generator%compute_xi_ref_momenta (p_born)
do i_phs = 1, 2
emitter = phs_identifiers(i_phs)%emitter
call generator%compute_xi_max (emitter, i_phs, p_born, &
generator%real_kinematics%xi_max(i_phs))
end do
write (u, "(A)") &
"* With these, the following radiation variables have been produced: "
associate (rad_var => generator%real_kinematics)
write (u, "(A,F4.2)") "xi_tilde: ", rad_var%xi_tilde
do i = 1, 2
write (u, "(A,I1,A,F5.2)") "i: ", i, "y: " , rad_var%y(i)
end do
write (u, "(A,F4.2)") "phi: ", rad_var%phi
end associate
call write_separator (u)
write (u, "(A)") "Produce real momenta via initial-state emission: "
i_phs = 1; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
allocate (p_real(5))
call generator%generate_isr_fixed_beam_energy (i_phs, p_born, p_real)
call pacify (p_real, 1E-6_default)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
call write_separator(u)
i_phs = 2; emitter = phs_identifiers(i_phs)%emitter
write (u, "(A,I1)") "emitter: ", emitter
call generator%generate_isr_fixed_beam_energy (i_phs, p_born, p_real)
call pacify (p_real, 1E-6_default)
call vector4_write_set (p_real, u, testflag = .true., ultra = .true.)
write (u, "(A)")
write (u, "(A)") "* Test output end: phs_fks_generator_7"
end subroutine phs_fks_generator_7
@ %def phs_fks_generator_3
@
\section{Dispatch}
<<[[dispatch_phase_space.f90]]>>=
<<File header>>
module dispatch_phase_space
<<Use kinds>>
<<Use strings>>
use variables, only: var_list_t
use os_interface, only: os_data_t
use sf_mappings, only: sf_channel_t
use beam_structures, only: beam_structure_t
use dispatch_beams, only: sf_prop_t, strfun_mode
use mappings
use phs_forests, only: phs_parameters_t
use phs_base
<<Standard module head>>
<<Dispatch phs: public>>
interface
<<Dispatch phs: sub interfaces>>
end interface
end module dispatch_phase_space
@ %def dispatch_phase_space
@
<<[[dispatch_phase_space_sub.f90]]>>=
<<File header>>
submodule (dispatch_phase_space) dispatch_phase_space_s
use io_units, only: free_unit
use diagnostics
use phs_none
use phs_single
use phs_rambo
use phs_wood
use phs_fks
implicit none
contains
<<Dispatch phs: procedures>>
end submodule dispatch_phase_space_s
@ %def dispatch_phase_space_s
@
Allocate a phase-space object according to the variable [[$phs_method]].
<<Dispatch phs: public>>=
public :: dispatch_phs
<<Dispatch phs: sub interfaces>>=
module subroutine dispatch_phs (phs, var_list, os_data, process_id, &
mapping_defaults, phs_par, phs_method_in)
class(phs_config_t), allocatable, intent(inout) :: phs
type(var_list_t), intent(in) :: var_list
type(os_data_t), intent(in) :: os_data
type(string_t), intent(in) :: process_id
type(mapping_defaults_t), intent(in), optional :: mapping_defaults
type(phs_parameters_t), intent(in), optional :: phs_par
type(string_t), intent(in), optional :: phs_method_in
end subroutine dispatch_phs
<<Dispatch phs: procedures>>=
module subroutine dispatch_phs (phs, var_list, os_data, process_id, &
mapping_defaults, phs_par, phs_method_in)
class(phs_config_t), allocatable, intent(inout) :: phs
type(var_list_t), intent(in) :: var_list
type(os_data_t), intent(in) :: os_data
type(string_t), intent(in) :: process_id
type(mapping_defaults_t), intent(in), optional :: mapping_defaults
type(phs_parameters_t), intent(in), optional :: phs_par
type(string_t), intent(in), optional :: phs_method_in
type(string_t) :: phs_method, phs_file, run_id
logical :: use_equivalences, vis_channels, fatal_beam_decay
integer :: u_phs
logical :: exist
if (present (phs_method_in)) then
phs_method = phs_method_in
else
phs_method = &
var_list%get_sval (var_str ("$phs_method"))
end if
phs_file = &
var_list%get_sval (var_str ("$phs_file"))
use_equivalences = &
var_list%get_lval (var_str ("?use_vamp_equivalences"))
vis_channels = &
var_list%get_lval (var_str ("?vis_channels"))
fatal_beam_decay = &
var_list%get_lval (var_str ("?fatal_beam_decay"))
run_id = &
var_list%get_sval (var_str ("$run_id"))
select case (char (phs_method))
case ("none")
allocate (phs_none_config_t :: phs)
case ("single")
allocate (phs_single_config_t :: phs)
if (vis_channels) then
call msg_warning ("Visualizing phase space channels not " // &
"available for method 'single'.")
end if
case ("rambo")
allocate (phs_rambo_config_t :: phs)
if (vis_channels) &
call msg_warning ("Visualizing phase space channels not " // &
"available for method 'rambo'.")
case ("fks")
allocate (phs_fks_config_t :: phs)
if (use_equivalences) then
select type (phs)
type is (phs_fks_config_t)
call phs%enable_equivalences ()
end select
end if
case ("wood", "default", "fast_wood")
call dispatch_wood ()
case default
call msg_fatal ("Phase space: parameterization method '" &
// char (phs_method) // "' not implemented")
end select
contains
<<Dispatch phs: dispatch phs: procedures>>
end subroutine dispatch_phs
@ %def dispatch_phs
@
<<Dispatch phs: dispatch phs: procedures>>=
subroutine dispatch_wood ()
allocate (phs_wood_config_t :: phs)
select type (phs)
type is (phs_wood_config_t)
if (phs_file /= "") then
inquire (file = char (phs_file), exist = exist)
if (exist) then
call msg_message ("Phase space: reading configuration from '" &
// char (phs_file) // "'")
u_phs = free_unit ()
open (u_phs, file = char (phs_file), &
action = "read", status = "old")
call phs%set_input (u_phs)
else
call msg_fatal ("Phase space: configuration file '" &
// char (phs_file) // "' not found")
end if
end if
if (present (phs_par)) &
call phs%set_parameters (phs_par)
if (use_equivalences) &
call phs%enable_equivalences ()
if (present (mapping_defaults)) &
call phs%set_mapping_defaults (mapping_defaults)
if (phs_method == "fast_wood") phs%use_cascades2 = .true.
phs%vis_channels = vis_channels
phs%fatal_beam_decay = fatal_beam_decay
phs%os_data = os_data
phs%run_id = run_id
end select
end subroutine dispatch_wood
@
@ Configure channel mappings, using some conditions
from the phase space configuration. If there are no structure
functions, we enable a default setup with a single (dummy)
structure-function channel. Otherwise, we look at the channel
collection that we got from the phase-space configuration step. Each
entry should be translated into an independent structure-function
channel, where typically there is one default entry, which could be
mapped using a standard s-channel mapping if the structure function
setup recommends this, and other entries with s-channel resonances.
The latter need to be translated into global mappings from the
structure-function chain.
<<Dispatch phs: public>>=
public :: dispatch_sf_channels
<<Dispatch phs: sub interfaces>>=
module subroutine dispatch_sf_channels (sf_channel, sf_string, sf_prop, &
coll, var_list, sqrts, beam_structure)
type(sf_channel_t), dimension(:), allocatable, intent(out) :: sf_channel
type(string_t), intent(out) :: sf_string
type(sf_prop_t), intent(in) :: sf_prop
type(phs_channel_collection_t), intent(in) :: coll
type(var_list_t), intent(in) :: var_list
real(default), intent(in) :: sqrts
type(beam_structure_t), intent(in) :: beam_structure
end subroutine dispatch_sf_channels
<<Dispatch phs: procedures>>=
module subroutine dispatch_sf_channels (sf_channel, sf_string, sf_prop, &
coll, var_list, sqrts, beam_structure)
type(sf_channel_t), dimension(:), allocatable, intent(out) :: sf_channel
type(string_t), intent(out) :: sf_string
type(sf_prop_t), intent(in) :: sf_prop
type(phs_channel_collection_t), intent(in) :: coll
type(var_list_t), intent(in) :: var_list
real(default), intent(in) :: sqrts
type(beam_structure_t), intent(in) :: beam_structure
type(beam_structure_t) :: beam_structure_tmp
class(channel_prop_t), allocatable :: prop
integer :: n_strfun, n_sf_channel, i
logical :: sf_allow_s_mapping, circe1_map, circe1_generate
logical :: s_mapping_enable, endpoint_mapping, power_mapping
logical :: single_parameter
integer, dimension(:), allocatable :: s_mapping, single_mapping
real(default) :: s_mapping_power
real(default) :: circe1_mapping_slope, endpoint_mapping_slope
real(default) :: power_mapping_eps
beam_structure_tmp = beam_structure
call beam_structure_tmp%expand (strfun_mode)
n_strfun = beam_structure_tmp%get_n_record ()
sf_string = beam_structure_tmp%to_string (sf_only = .true.)
sf_allow_s_mapping = &
var_list%get_lval (var_str ("?sf_allow_s_mapping"))
circe1_generate = &
var_list%get_lval (var_str ("?circe1_generate"))
circe1_map = &
var_list%get_lval (var_str ("?circe1_map"))
circe1_mapping_slope = &
var_list%get_rval (var_str ("circe1_mapping_slope"))
s_mapping_enable = .false.
s_mapping_power = 1
endpoint_mapping = .false.
endpoint_mapping_slope = 1
power_mapping = .false.
single_parameter = .false.
select case (char (sf_string))
case ("", "[any particles]")
case ("pdf_builtin, none", &
"pdf_builtin_photon, none", &
"none, pdf_builtin", &
"none, pdf_builtin_photon", &
"lhapdf, none", &
"lhapdf_photon, none", &
"none, lhapdf", &
"none, lhapdf_photon")
single_parameter = .true.
case ("pdf_builtin, none => none, pdf_builtin", &
"pdf_builtin, none => none, pdf_builtin_photon", &
"pdf_builtin_photon, none => none, pdf_builtin", &
"pdf_builtin_photon, none => none, pdf_builtin_photon", &
"lhapdf, none => none, lhapdf", &
"lhapdf, none => none, lhapdf_photon", &
"lhapdf_photon, none => none, lhapdf", &
"lhapdf_photon, none => none, lhapdf_photon")
allocate (s_mapping (2), source = [1, 2])
s_mapping_enable = .true.
s_mapping_power = 2
case ("pdf_builtin, none => none, pdf_builtin => epa, none => none, epa", &
"pdf_builtin, none => none, pdf_builtin => ewa, none => none, ewa", &
"pdf_builtin, none => none, pdf_builtin => ewa, none => none, epa", &
"pdf_builtin, none => none, pdf_builtin => epa, none => none, ewa")
allocate (s_mapping (2), source = [1, 2])
s_mapping_enable = .true.
s_mapping_power = 2
case ("isr, none", &
"none, isr")
allocate (single_mapping (1), source = [1])
single_parameter = .true.
case ("isr, none => none, isr")
allocate (s_mapping (2), source = [1, 2])
power_mapping = .true.
power_mapping_eps = minval (sf_prop%isr_eps)
case ("isr, none => none, isr => epa, none => none, epa", &
"isr, none => none, isr => ewa, none => none, ewa", &
"isr, none => none, isr => ewa, none => none, epa", &
"isr, none => none, isr => epa, none => none, ewa")
allocate (s_mapping (2), source = [1, 2])
power_mapping = .true.
power_mapping_eps = minval (sf_prop%isr_eps)
case ("circe1 => isr, none => none, isr => epa, none => none, epa", &
"circe1 => isr, none => none, isr => ewa, none => none, ewa", &
"circe1 => isr, none => none, isr => ewa, none => none, epa", &
"circe1 => isr, none => none, isr => epa, none => none, ewa")
if (circe1_generate) then
allocate (s_mapping (2), source = [2, 3])
else
allocate (s_mapping (3), source = [1, 2, 3])
endpoint_mapping = .true.
endpoint_mapping_slope = circe1_mapping_slope
end if
power_mapping = .true.
power_mapping_eps = minval (sf_prop%isr_eps)
case ("pdf_builtin, none => none, isr", &
"pdf_builtin_photon, none => none, isr", &
"lhapdf, none => none, isr", &
"lhapdf_photon, none => none, isr")
allocate (single_mapping (1), source = [2])
case ("isr, none => none, pdf_builtin", &
"isr, none => none, pdf_builtin_photon", &
"isr, none => none, lhapdf", &
"isr, none => none, lhapdf_photon")
allocate (single_mapping (1), source = [1])
case ("epa, none", &
"none, epa")
allocate (single_mapping (1), source = [1])
single_parameter = .true.
case ("epa, none => none, epa")
allocate (single_mapping (2), source = [1, 2])
case ("epa, none => none, isr", &
"isr, none => none, epa", &
"ewa, none => none, isr", &
"isr, none => none, ewa")
allocate (single_mapping (2), source = [1, 2])
case ("pdf_builtin, none => none, epa", &
"pdf_builtin_photon, none => none, epa", &
"lhapdf, none => none, epa", &
"lhapdf_photon, none => none, epa")
allocate (single_mapping (1), source = [2])
case ("pdf_builtin, none => none, ewa", &
"pdf_builtin_photon, none => none, ewa", &
"lhapdf, none => none, ewa", &
"lhapdf_photon, none => none, ewa")
allocate (single_mapping (1), source = [2])
case ("epa, none => none, pdf_builtin", &
"epa, none => none, pdf_builtin_photon", &
"epa, none => none, lhapdf", &
"epa, none => none, lhapdf_photon")
allocate (single_mapping (1), source = [1])
case ("ewa, none => none, pdf_builtin", &
"ewa, none => none, pdf_builtin_photon", &
"ewa, none => none, lhapdf", &
"ewa, none => none, lhapdf_photon")
allocate (single_mapping (1), source = [1])
case ("ewa, none", &
"none, ewa")
allocate (single_mapping (1), source = [1])
single_parameter = .true.
case ("ewa, none => none, ewa")
allocate (single_mapping (2), source = [1, 2])
case ("energy_scan, none => none, energy_scan")
allocate (s_mapping (2), source = [1, 2])
case ("sf_test_1, none => none, sf_test_1")
allocate (s_mapping (2), source = [1, 2])
case ("circe1")
if (circe1_generate) then
!!! no mapping
else if (circe1_map) then
allocate (s_mapping (1), source = [1])
endpoint_mapping = .true.
endpoint_mapping_slope = circe1_mapping_slope
else
allocate (s_mapping (1), source = [1])
s_mapping_enable = .true.
end if
case ("circe1 => isr, none => none, isr")
if (circe1_generate) then
allocate (s_mapping (2), source = [2, 3])
else
allocate (s_mapping (3), source = [1, 2, 3])
endpoint_mapping = .true.
endpoint_mapping_slope = circe1_mapping_slope
end if
power_mapping = .true.
power_mapping_eps = minval (sf_prop%isr_eps)
case ("circe1 => isr, none", &
"circe1 => none, isr")
allocate (single_mapping (1), source = [2])
case ("circe1 => epa, none => none, epa")
if (circe1_generate) then
allocate (single_mapping (2), source = [2, 3])
else
call msg_fatal ("CIRCE/EPA: supported with ?circe1_generate=true &
&only")
end if
case ("circe1 => ewa, none => none, ewa")
if (circe1_generate) then
allocate (single_mapping (2), source = [2, 3])
else
call msg_fatal ("CIRCE/EWA: supported with ?circe1_generate=true &
&only")
end if
case ("circe1 => epa, none", &
"circe1 => none, epa")
if (circe1_generate) then
allocate (single_mapping (1), source = [2])
else
call msg_fatal ("CIRCE/EPA: supported with ?circe1_generate=true &
&only")
end if
case ("circe1 => epa, none => none, isr", &
"circe1 => isr, none => none, epa", &
"circe1 => ewa, none => none, isr", &
"circe1 => isr, none => none, ewa")
if (circe1_generate) then
allocate (single_mapping (2), source = [2, 3])
else
call msg_fatal ("CIRCE/EPA: supported with ?circe1_generate=true &
&only")
end if
case ("circe2", &
"gaussian", &
"beam_events")
!!! no mapping
case ("circe2 => isr, none => none, isr", &
"gaussian => isr, none => none, isr", &
"beam_events => isr, none => none, isr")
allocate (s_mapping (2), source = [2, 3])
power_mapping = .true.
power_mapping_eps = minval (sf_prop%isr_eps)
case ("circe2 => isr, none", &
"circe2 => none, isr", &
"gaussian => isr, none", &
"gaussian => none, isr", &
"beam_events => isr, none", &
"beam_events => none, isr")
allocate (single_mapping (1), source = [2])
case ("circe2 => epa, none => none, epa", &
"gaussian => epa, none => none, epa", &
"beam_events => epa, none => none, epa")
allocate (single_mapping (2), source = [2, 3])
case ("circe2 => epa, none", &
"circe2 => none, epa", &
"circe2 => ewa, none", &
"circe2 => none, ewa", &
"gaussian => epa, none", &
"gaussian => none, epa", &
"gaussian => ewa, none", &
"gaussian => none, ewa", &
"beam_events => epa, none", &
"beam_events => none, epa", &
"beam_events => ewa, none", &
"beam_events => none, ewa")
allocate (single_mapping (1), source = [2])
case ("circe2 => epa, none => none, isr", &
"circe2 => isr, none => none, epa", &
"circe2 => ewa, none => none, isr", &
"circe2 => isr, none => none, ewa", &
"gaussian => epa, none => none, isr", &
"gaussian => isr, none => none, epa", &
"gaussian => ewa, none => none, isr", &
"gaussian => isr, none => none, ewa", &
"beam_events => epa, none => none, isr", &
"beam_events => isr, none => none, epa", &
"beam_events => ewa, none => none, isr", &
"beam_events => isr, none => none, ewa")
allocate (single_mapping (2), source = [2, 3])
case ("energy_scan")
case default
call msg_fatal ("Beam structure: " &
// char (sf_string) // " not supported")
end select
if (sf_allow_s_mapping .and. coll%n > 0) then
n_sf_channel = coll%n
allocate (sf_channel (n_sf_channel))
do i = 1, n_sf_channel
call sf_channel(i)%init (n_strfun)
if (allocated (single_mapping)) then
call sf_channel(i)%activate_mapping (single_mapping)
end if
if (allocated (prop)) deallocate (prop)
call coll%get_entry (i, prop)
if (allocated (prop)) then
if (endpoint_mapping .and. power_mapping) then
select type (prop)
type is (resonance_t)
call sf_channel(i)%set_eir_mapping (s_mapping, &
a = endpoint_mapping_slope, eps = power_mapping_eps, &
m = prop%mass / sqrts, w = prop%width / sqrts)
type is (on_shell_t)
call sf_channel(i)%set_eio_mapping (s_mapping, &
a = endpoint_mapping_slope, eps = power_mapping_eps, &
m = prop%mass / sqrts)
end select
else if (endpoint_mapping) then
select type (prop)
type is (resonance_t)
call sf_channel(i)%set_epr_mapping (s_mapping, &
a = endpoint_mapping_slope, &
m = prop%mass / sqrts, w = prop%width / sqrts)
type is (on_shell_t)
call sf_channel(i)%set_epo_mapping (s_mapping, &
a = endpoint_mapping_slope, &
m = prop%mass / sqrts)
end select
else if (power_mapping) then
select type (prop)
type is (resonance_t)
call sf_channel(i)%set_ipr_mapping (s_mapping, &
eps = power_mapping_eps, &
m = prop%mass / sqrts, w = prop%width / sqrts)
type is (on_shell_t)
call sf_channel(i)%set_ipo_mapping (s_mapping, &
eps = power_mapping_eps, &
m = prop%mass / sqrts)
end select
else if (allocated (s_mapping)) then
select type (prop)
type is (resonance_t)
call sf_channel(i)%set_res_mapping (s_mapping, &
m = prop%mass / sqrts, w = prop%width / sqrts, &
single = single_parameter)
type is (on_shell_t)
call sf_channel(i)%set_os_mapping (s_mapping, &
m = prop%mass / sqrts, &
single = single_parameter)
end select
else if (allocated (single_mapping)) then
select type (prop)
type is (resonance_t)
call sf_channel(i)%set_res_mapping (single_mapping, &
m = prop%mass / sqrts, w = prop%width / sqrts, &
single = single_parameter)
type is (on_shell_t)
call sf_channel(i)%set_os_mapping (single_mapping, &
m = prop%mass / sqrts, &
single = single_parameter)
end select
end if
else if (endpoint_mapping .and. power_mapping) then
call sf_channel(i)%set_ei_mapping (s_mapping, &
a = endpoint_mapping_slope, eps = power_mapping_eps)
else if (endpoint_mapping .and. .not. allocated (single_mapping)) then
call sf_channel(i)%set_ep_mapping (s_mapping, &
a = endpoint_mapping_slope)
else if (power_mapping .and. .not. allocated (single_mapping)) then
call sf_channel(i)%set_ip_mapping (s_mapping, &
eps = power_mapping_eps)
else if (s_mapping_enable .and. .not. allocated (single_mapping)) then
call sf_channel(i)%set_s_mapping (s_mapping, &
power = s_mapping_power)
end if
end do
else if (sf_allow_s_mapping) then
allocate (sf_channel (1))
call sf_channel(1)%init (n_strfun)
if (allocated (single_mapping)) then
call sf_channel(1)%activate_mapping (single_mapping)
else if (endpoint_mapping .and. power_mapping) then
call sf_channel(i)%set_ei_mapping (s_mapping, &
a = endpoint_mapping_slope, eps = power_mapping_eps)
else if (endpoint_mapping) then
call sf_channel(1)%set_ep_mapping (s_mapping, &
a = endpoint_mapping_slope)
else if (power_mapping) then
call sf_channel(1)%set_ip_mapping (s_mapping, &
eps = power_mapping_eps)
else if (s_mapping_enable) then
call sf_channel(1)%set_s_mapping (s_mapping, &
power = s_mapping_power)
end if
else
allocate (sf_channel (1))
call sf_channel(1)%init (n_strfun)
if (allocated (single_mapping)) then
call sf_channel(1)%activate_mapping (single_mapping)
end if
end if
end subroutine dispatch_sf_channels
@ %def dispatch_sf_channels
@
@
\subsection{Unit tests}
Test module, followed by the corresponding implementation module.
<<[[dispatch_phs_ut.f90]]>>=
<<File header>>
module dispatch_phs_ut
use unit_tests
use dispatch_phs_uti
<<Standard module head>>
<<Dispatch phs: public test>>
contains
<<Dispatch phs: test driver>>
end module dispatch_phs_ut
@ %def dispatch_phs_ut
@
<<[[dispatch_phs_uti.f90]]>>=
<<File header>>
module dispatch_phs_uti
<<Use kinds>>
<<Use strings>>
use variables
use io_units, only: free_unit
use os_interface, only: os_data_t
use process_constants
use model_data
use models
use phs_base
use phs_none
use phs_forests
use phs_wood
use mappings
use dispatch_phase_space
<<Standard module head>>
<<Dispatch phs: test declarations>>
contains
<<Dispatch phs: tests>>
end module dispatch_phs_uti
@ %def dispatch_phs_ut
@ API: driver for the unit tests below.
<<Dispatch phs: public test>>=
public ::dispatch_phs_test
<<Dispatch phs: test driver>>=
subroutine dispatch_phs_test (u, results)
integer, intent(in) :: u
type(test_results_t), intent(inout) :: results
<<Dispatch phs: execute tests>>
end subroutine dispatch_phs_test
@ %def dispatch_phs_test
@
\subsubsection{Select type: phase-space configuration object}
<<Dispatch phs: execute tests>>=
call test (dispatch_phs_1, "dispatch_phs_1", &
"phase-space configuration", &
u, results)
<<Dispatch phs: test declarations>>=
public :: dispatch_phs_1
<<Dispatch phs: tests>>=
subroutine dispatch_phs_1 (u)
integer, intent(in) :: u
type(var_list_t) :: var_list
class(phs_config_t), allocatable :: phs
type(phs_parameters_t) :: phs_par
type(os_data_t) :: os_data
type(mapping_defaults_t) :: mapping_defs
write (u, "(A)") "* Test output: dispatch_phs_1"
write (u, "(A)") "* Purpose: select phase-space configuration method"
write (u, "(A)")
call var_list%init_defaults (0)
write (u, "(A)") "* Allocate PHS as phs_none_t"
write (u, "(A)")
call var_list%set_string (&
var_str ("$phs_method"), &
var_str ("none"), is_known = .true.)
call dispatch_phs (phs, var_list, os_data, var_str ("dispatch_phs_1"))
call phs%write (u)
call phs%final ()
deallocate (phs)
write (u, "(A)")
write (u, "(A)") "* Allocate PHS as phs_single_t"
write (u, "(A)")
call var_list%set_string (&
var_str ("$phs_method"), &
var_str ("single"), is_known = .true.)
call dispatch_phs (phs, var_list, os_data, var_str ("dispatch_phs_1"))
call phs%write (u)
call phs%final ()
deallocate (phs)
write (u, "(A)")
write (u, "(A)") "* Allocate PHS as phs_wood_t"
write (u, "(A)")
call var_list%set_string (&
var_str ("$phs_method"), &
var_str ("wood"), is_known = .true.)
call dispatch_phs (phs, var_list, os_data, var_str ("dispatch_phs_1"))
call phs%write (u)
call phs%final ()
deallocate (phs)
write (u, "(A)")
write (u, "(A)") "* Setting parameters for phs_wood_t"
write (u, "(A)")
phs_par%m_threshold_s = 123
phs_par%m_threshold_t = 456
phs_par%t_channel = 42
phs_par%off_shell = 17
phs_par%keep_nonresonant = .false.
mapping_defs%energy_scale = 987
mapping_defs%invariant_mass_scale = 654
mapping_defs%momentum_transfer_scale = 321
mapping_defs%step_mapping = .false.
mapping_defs%step_mapping_exp = .false.
mapping_defs%enable_s_mapping = .true.
call dispatch_phs (phs, var_list, os_data, var_str ("dispatch_phs_1"), &
mapping_defs, phs_par)
call phs%write (u)
call phs%final ()
call var_list%final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: dispatch_phs_1"
end subroutine dispatch_phs_1
@ %def dispatch_phs_1
@
\subsubsection{Phase-space configuration with file}
<<Dispatch phs: execute tests>>=
call test (dispatch_phs_2, "dispatch_phs_2", &
"configure phase space using file", &
u, results)
<<Dispatch phs: test declarations>>=
public :: dispatch_phs_2
<<Dispatch phs: tests>>=
subroutine dispatch_phs_2 (u)
use phs_base_ut, only: init_test_process_data
use phs_wood_ut, only: write_test_phs_file
use phs_forests
integer, intent(in) :: u
type(var_list_t) :: var_list
type(os_data_t) :: os_data
type(process_constants_t) :: process_data
type(model_list_t) :: model_list
type(model_t), pointer :: model
class(phs_config_t), allocatable :: phs
integer :: u_phs
write (u, "(A)") "* Test output: dispatch_phs_2"
write (u, "(A)") "* Purpose: select 'wood' phase-space &
&for a test process"
write (u, "(A)") "* and read phs configuration from file"
write (u, "(A)")
write (u, "(A)") "* Initialize a process"
write (u, "(A)")
call var_list%init_defaults (0)
call os_data%init ()
call syntax_model_file_init ()
call model_list%read_model &
(var_str ("Test"), var_str ("Test.mdl"), os_data, model)
call syntax_phs_forest_init ()
call init_test_process_data (var_str ("dispatch_phs_2"), process_data)
write (u, "(A)") "* Write phase-space file"
u_phs = free_unit ()
open (u_phs, file = "dispatch_phs_2.phs", action = "write", status = "replace")
call write_test_phs_file (u_phs, var_str ("dispatch_phs_2"))
close (u_phs)
write (u, "(A)")
write (u, "(A)") "* Allocate PHS as phs_wood_t"
write (u, "(A)")
call var_list%set_string (&
var_str ("$phs_method"), &
var_str ("wood"), is_known = .true.)
call var_list%set_string (&
var_str ("$phs_file"), &
var_str ("dispatch_phs_2.phs"), is_known = .true.)
call dispatch_phs (phs, var_list, os_data, var_str ("dispatch_phs_2"))
call phs%init (process_data, model)
call phs%configure (sqrts = 1000._default)
call phs%write (u)
write (u, "(A)")
select type (phs)
type is (phs_wood_config_t)
call phs%write_forest (u)
end select
call phs%final ()
call var_list%final ()
call syntax_model_file_final ()
write (u, "(A)")
write (u, "(A)") "* Test output end: dispatch_phs_2"
end subroutine dispatch_phs_2
@ %def dispatch_phs_2
@

File Metadata

Mime Type
text/x-diff
Expires
Tue, Nov 19, 4:51 PM (1 d, 12 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
c4/78/458c06f4120240ed663ba1eb8363
Default Alt Text
(1 MB)

Event Timeline