Index: trunk/src/qft/qft.nw =================================================================== --- trunk/src/qft/qft.nw (revision 8753) +++ trunk/src/qft/qft.nw (revision 8754) @@ -1,15751 +1,15749 @@ %% -*- ess-noweb-default-code-mode: f90-mode; noweb-default-code-mode: f90-mode; -*- % WHIZARD code as NOWEB source: Quantum Field Theory concepts %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Quantum Field Theory Concepts} \includemodulegraph{qft} The objects and methods defined here implement concepts and data for the underlying quantum field theory that we use for computing matrix elements and processes. \begin{description} \item[model\_data] Fields and coupling parameters, operators as vertex structures, for a specific model. \item[model\_testbed] Provide hooks to deal with a [[model_data]] extension without referencing it explicitly. \item[helicities] Types and methods for spin density matrices. \item[colors] Dealing with colored particles, using the color-flow representation. \item[flavors] PDG codes and particle properties, depends on the model. \item[quantum\_numbers] Quantum numbers and density matrices for entangled particle systems. \end{description} \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Model Data} These data represent a specific Lagrangian in numeric terms. That is, we have the fields with their quantum numbers, the masses, widths and couplings as numerical values, and the vertices as arrays of fields. We do not store the relations between coupling parameters. They should be represented by expressions for evaluation, implemented as Sindarin objects in a distinct data structure. Neither do we need the algebraic structure of vertices. The field content of vertices is required for the sole purpose of setting up phase space. <<[[model_data.f90]]>>= <> module model_data use, intrinsic :: iso_c_binding !NODEP! <> use kinds, only: i8, i32 use kinds, only: c_default_float <> use format_defs, only: FMT_19 use io_units use diagnostics use md5 use hashes, only: hash use physics_defs, only: UNDEFINED, SCALAR <> <> <> <> contains <> end module model_data @ %def model_data @ \subsection{Physics Parameters} Couplings, masses, and widths are physics parameters. Each parameter has a unique name (used, essentially, for diagnostics output and debugging) and a value. The value may be a real or a complex number, so we provide to implementations of an abstract type. <>= public :: modelpar_data_t <>= type, abstract :: modelpar_data_t private type(string_t) :: name contains <> end type modelpar_data_t type, extends (modelpar_data_t) :: modelpar_real_t private real(default) :: value end type modelpar_real_t type, extends (modelpar_data_t) :: modelpar_complex_t private complex(default) :: value end type modelpar_complex_t @ %def modelpar_data_t modelpar_real_t modelpar_complex_t @ Output for diagnostics. Non-advancing. <>= procedure :: write => par_write <>= subroutine par_write (par, unit) class(modelpar_data_t), intent(in) :: par integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) write (u, "(1x,A,1x,A)", advance="no") char (par%name), "= " select type (par) type is (modelpar_real_t) write (u, "(" // FMT_19 // ")", advance="no") par%value type is (modelpar_complex_t) write (u, "(" // FMT_19 // ",1x,'+',1x," // FMT_19 // ",1x,'I')", & advance="no") par%value end select end subroutine par_write @ %def par_write @ Pretty-printed on separate line, with fixed line length <>= procedure :: show => par_show <>= subroutine par_show (par, l, u) class(modelpar_data_t), intent(in) :: par integer, intent(in) :: l, u character(len=l) :: buffer buffer = par%name select type (par) type is (modelpar_real_t) write (u, "(4x,A,1x,'=',1x," // FMT_19 // ")") buffer, par%value type is (modelpar_complex_t) write (u, "(4x,A,1x,'=',1x," // FMT_19 // ",1x,'+',1x," & // FMT_19 // ",1x,'I')") buffer, par%value end select end subroutine par_show @ %def par_show @ Initialize with name and value. The type depends on the argument type. If the type does not match, the value is converted following Fortran rules. <>= generic :: init => modelpar_data_init_real, modelpar_data_init_complex procedure, private :: modelpar_data_init_real procedure, private :: modelpar_data_init_complex <>= subroutine modelpar_data_init_real (par, name, value) class(modelpar_data_t), intent(out) :: par type(string_t), intent(in) :: name real(default), intent(in) :: value par%name = name par = value end subroutine modelpar_data_init_real subroutine modelpar_data_init_complex (par, name, value) class(modelpar_data_t), intent(out) :: par type(string_t), intent(in) :: name complex(default), intent(in) :: value par%name = name par = value end subroutine modelpar_data_init_complex @ %def modelpar_data_init_real modelpar_data_init_complex @ Modify the value. We assume that the parameter has been initialized. The type (real or complex) must not be changed, and the name is also fixed. <>= generic :: assignment(=) => modelpar_data_set_real, modelpar_data_set_complex procedure, private :: modelpar_data_set_real procedure, private :: modelpar_data_set_complex <>= elemental subroutine modelpar_data_set_real (par, value) class(modelpar_data_t), intent(inout) :: par real(default), intent(in) :: value select type (par) type is (modelpar_real_t) par%value = value type is (modelpar_complex_t) par%value = value end select end subroutine modelpar_data_set_real elemental subroutine modelpar_data_set_complex (par, value) class(modelpar_data_t), intent(inout) :: par complex(default), intent(in) :: value select type (par) type is (modelpar_real_t) par%value = value type is (modelpar_complex_t) par%value = value end select end subroutine modelpar_data_set_complex @ %def modelpar_data_set_real modelpar_data_set_complex @ Return the parameter name. <>= procedure :: get_name => modelpar_data_get_name <>= function modelpar_data_get_name (par) result (name) class(modelpar_data_t), intent(in) :: par type(string_t) :: name name = par%name end function modelpar_data_get_name @ %def modelpar_data_get_name @ Return the value. In case of a type mismatch, follow Fortran conventions. <>= procedure, pass :: get_real => modelpar_data_get_real procedure, pass :: get_complex => modelpar_data_get_complex <>= elemental function modelpar_data_get_real (par) result (value) class(modelpar_data_t), intent(in), target :: par real(default) :: value select type (par) type is (modelpar_real_t) value = par%value type is (modelpar_complex_t) value = par%value end select end function modelpar_data_get_real elemental function modelpar_data_get_complex (par) result (value) class(modelpar_data_t), intent(in), target :: par complex(default) :: value select type (par) type is (modelpar_real_t) value = par%value type is (modelpar_complex_t) value = par%value end select end function modelpar_data_get_complex @ %def modelpar_data_get_real @ %def modelpar_data_get_complex @ Return a pointer to the value. This makes sense only for matching types. <>= procedure :: get_real_ptr => modelpar_data_get_real_ptr procedure :: get_complex_ptr => modelpar_data_get_complex_ptr <>= function modelpar_data_get_real_ptr (par) result (ptr) class(modelpar_data_t), intent(in), target :: par real(default), pointer :: ptr select type (par) type is (modelpar_real_t) ptr => par%value class default ptr => null () end select end function modelpar_data_get_real_ptr function modelpar_data_get_complex_ptr (par) result (ptr) class(modelpar_data_t), intent(in), target :: par complex(default), pointer :: ptr select type (par) type is (modelpar_complex_t) ptr => par%value class default ptr => null () end select end function modelpar_data_get_complex_ptr @ %def modelpar_data_get_real_ptr @ %def modelpar_data_get_complex_ptr @ \subsection{Field Data} The field-data type holds all information that pertains to a particular field (or particle) within a particular model. Information such as spin type, particle code etc.\ is stored within the object itself, while mass and width are associated to parameters, otherwise assumed zero. <>= public :: field_data_t <>= type :: field_data_t private type(string_t) :: longname integer :: pdg = UNDEFINED logical :: visible = .true. logical :: parton = .false. logical :: gauge = .false. logical :: left_handed = .false. logical :: right_handed = .false. logical :: has_anti = .false. logical :: p_is_stable = .true. logical :: p_decays_isotropically = .false. logical :: p_decays_diagonal = .false. logical :: p_has_decay_helicity = .false. integer :: p_decay_helicity = 0 logical :: a_is_stable = .true. logical :: a_decays_isotropically = .false. logical :: a_decays_diagonal = .false. logical :: a_has_decay_helicity = .false. integer :: a_decay_helicity = 0 logical :: p_polarized = .false. logical :: a_polarized = .false. type(string_t), dimension(:), allocatable :: name, anti type(string_t) :: tex_name, tex_anti integer :: spin_type = UNDEFINED integer :: isospin_type = 1 integer :: charge_type = 1 integer :: color_type = 1 real(default), pointer :: mass_val => null () class(modelpar_data_t), pointer :: mass_data => null () real(default), pointer :: width_val => null () class(modelpar_data_t), pointer :: width_data => null () integer :: multiplicity = 1 type(string_t), dimension(:), allocatable :: p_decay type(string_t), dimension(:), allocatable :: a_decay contains <> end type field_data_t @ %def field_data_t @ Initialize field data with PDG long name and PDG code. \TeX\ names should be initialized to avoid issues with accessing unallocated string contents. <>= procedure :: init => field_data_init <>= subroutine field_data_init (prt, longname, pdg) class(field_data_t), intent(out) :: prt type(string_t), intent(in) :: longname integer, intent(in) :: pdg prt%longname = longname prt%pdg = pdg prt%tex_name = "" prt%tex_anti = "" end subroutine field_data_init @ %def field_data_init @ Copy quantum numbers from another particle. Do not compute the multiplicity yet, because this depends on the association of the [[mass_data]] pointer. <>= procedure :: copy_from => field_data_copy_from <>= subroutine field_data_copy_from (prt, prt_src) class(field_data_t), intent(inout) :: prt class(field_data_t), intent(in) :: prt_src prt%visible = prt_src%visible prt%parton = prt_src%parton prt%gauge = prt_src%gauge prt%left_handed = prt_src%left_handed prt%right_handed = prt_src%right_handed prt%p_is_stable = prt_src%p_is_stable prt%p_decays_isotropically = prt_src%p_decays_isotropically prt%p_decays_diagonal = prt_src%p_decays_diagonal prt%p_has_decay_helicity = prt_src%p_has_decay_helicity prt%p_decay_helicity = prt_src%p_decay_helicity prt%p_decays_diagonal = prt_src%p_decays_diagonal prt%a_is_stable = prt_src%a_is_stable prt%a_decays_isotropically = prt_src%a_decays_isotropically prt%a_decays_diagonal = prt_src%a_decays_diagonal prt%a_has_decay_helicity = prt_src%a_has_decay_helicity prt%a_decay_helicity = prt_src%a_decay_helicity prt%p_polarized = prt_src%p_polarized prt%a_polarized = prt_src%a_polarized prt%spin_type = prt_src%spin_type prt%isospin_type = prt_src%isospin_type prt%charge_type = prt_src%charge_type prt%color_type = prt_src%color_type prt%has_anti = prt_src%has_anti if (allocated (prt_src%name)) then if (allocated (prt%name)) deallocate (prt%name) allocate (prt%name (size (prt_src%name)), source = prt_src%name) end if if (allocated (prt_src%anti)) then if (allocated (prt%anti)) deallocate (prt%anti) allocate (prt%anti (size (prt_src%anti)), source = prt_src%anti) end if prt%tex_name = prt_src%tex_name prt%tex_anti = prt_src%tex_anti if (allocated (prt_src%p_decay)) then if (allocated (prt%p_decay)) deallocate (prt%p_decay) allocate (prt%p_decay (size (prt_src%p_decay)), source = prt_src%p_decay) end if if (allocated (prt_src%a_decay)) then if (allocated (prt%a_decay)) deallocate (prt%a_decay) allocate (prt%a_decay (size (prt_src%a_decay)), source = prt_src%a_decay) end if end subroutine field_data_copy_from @ %def field_data_copy_from @ Set particle quantum numbers. <>= procedure :: set => field_data_set <>= subroutine field_data_set (prt, & is_visible, is_parton, is_gauge, is_left_handed, is_right_handed, & p_is_stable, p_decays_isotropically, p_decays_diagonal, & p_decay_helicity, & a_is_stable, a_decays_isotropically, a_decays_diagonal, & a_decay_helicity, & p_polarized, a_polarized, & name, anti, tex_name, tex_anti, & spin_type, isospin_type, charge_type, color_type, & mass_data, width_data, & p_decay, a_decay) class(field_data_t), intent(inout) :: prt logical, intent(in), optional :: is_visible, is_parton, is_gauge logical, intent(in), optional :: is_left_handed, is_right_handed logical, intent(in), optional :: p_is_stable logical, intent(in), optional :: p_decays_isotropically, p_decays_diagonal integer, intent(in), optional :: p_decay_helicity logical, intent(in), optional :: a_is_stable logical, intent(in), optional :: a_decays_isotropically, a_decays_diagonal integer, intent(in), optional :: a_decay_helicity logical, intent(in), optional :: p_polarized, a_polarized type(string_t), dimension(:), intent(in), optional :: name, anti type(string_t), intent(in), optional :: tex_name, tex_anti integer, intent(in), optional :: spin_type, isospin_type integer, intent(in), optional :: charge_type, color_type class(modelpar_data_t), intent(in), pointer, optional :: mass_data, width_data type(string_t), dimension(:), intent(in), optional :: p_decay, a_decay if (present (is_visible)) prt%visible = is_visible if (present (is_parton)) prt%parton = is_parton if (present (is_gauge)) prt%gauge = is_gauge if (present (is_left_handed)) prt%left_handed = is_left_handed if (present (is_right_handed)) prt%right_handed = is_right_handed if (present (p_is_stable)) prt%p_is_stable = p_is_stable if (present (p_decays_isotropically)) & prt%p_decays_isotropically = p_decays_isotropically if (present (p_decays_diagonal)) & prt%p_decays_diagonal = p_decays_diagonal if (present (p_decay_helicity)) then prt%p_has_decay_helicity = .true. prt%p_decay_helicity = p_decay_helicity end if if (present (a_is_stable)) prt%a_is_stable = a_is_stable if (present (a_decays_isotropically)) & prt%a_decays_isotropically = a_decays_isotropically if (present (a_decays_diagonal)) & prt%a_decays_diagonal = a_decays_diagonal if (present (a_decay_helicity)) then prt%a_has_decay_helicity = .true. prt%a_decay_helicity = a_decay_helicity end if if (present (p_polarized)) prt%p_polarized = p_polarized if (present (a_polarized)) prt%a_polarized = a_polarized if (present (name)) then if (allocated (prt%name)) deallocate (prt%name) allocate (prt%name (size (name)), source = name) end if if (present (anti)) then if (allocated (prt%anti)) deallocate (prt%anti) allocate (prt%anti (size (anti)), source = anti) prt%has_anti = .true. end if if (present (tex_name)) prt%tex_name = tex_name if (present (tex_anti)) prt%tex_anti = tex_anti if (present (spin_type)) prt%spin_type = spin_type if (present (isospin_type)) prt%isospin_type = isospin_type if (present (charge_type)) prt%charge_type = charge_type if (present (color_type)) prt%color_type = color_type if (present (mass_data)) then prt%mass_data => mass_data if (associated (mass_data)) then prt%mass_val => mass_data%get_real_ptr () else prt%mass_val => null () end if end if if (present (width_data)) then prt%width_data => width_data if (associated (width_data)) then prt%width_val => width_data%get_real_ptr () else prt%width_val => null () end if end if if (present (spin_type) .or. present (mass_data)) then call prt%set_multiplicity () end if if (present (p_decay)) then if (allocated (prt%p_decay)) deallocate (prt%p_decay) if (size (p_decay) > 0) & allocate (prt%p_decay (size (p_decay)), source = p_decay) end if if (present (a_decay)) then if (allocated (prt%a_decay)) deallocate (prt%a_decay) if (size (a_decay) > 0) & allocate (prt%a_decay (size (a_decay)), source = a_decay) end if end subroutine field_data_set @ %def field_data_set @ Calculate the multiplicity given spin type and mass. <>= procedure, private :: & set_multiplicity => field_data_set_multiplicity <>= subroutine field_data_set_multiplicity (prt) class(field_data_t), intent(inout) :: prt if (prt%spin_type /= SCALAR) then if (associated (prt%mass_data)) then prt%multiplicity = prt%spin_type else if (prt%left_handed .or. prt%right_handed) then prt%multiplicity = 1 else prt%multiplicity = 2 end if end if end subroutine field_data_set_multiplicity @ %def field_data_set_multiplicity @ Set the mass/width value (not the pointer). The mass/width pointer must be allocated. <>= procedure, private :: set_mass => field_data_set_mass procedure, private :: set_width => field_data_set_width <>= subroutine field_data_set_mass (prt, mass) class(field_data_t), intent(inout) :: prt real(default), intent(in) :: mass if (associated (prt%mass_val)) prt%mass_val = mass end subroutine field_data_set_mass subroutine field_data_set_width (prt, width) class(field_data_t), intent(inout) :: prt real(default), intent(in) :: width if (associated (prt%width_val)) prt%width_val = width end subroutine field_data_set_width @ %def field_data_set_mass field_data_set_width @ Loose ends: name arrays should be allocated. <>= procedure :: freeze => field_data_freeze <>= elemental subroutine field_data_freeze (prt) class(field_data_t), intent(inout) :: prt if (.not. allocated (prt%name)) allocate (prt%name (0)) if (.not. allocated (prt%anti)) allocate (prt%anti (0)) end subroutine field_data_freeze @ %def field_data_freeze @ Output <>= procedure :: write => field_data_write <>= subroutine field_data_write (prt, unit) class(field_data_t), intent(in) :: prt integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit); if (u < 0) return write (u, "(3x,A,1x,A)", advance="no") "particle", char (prt%longname) write (u, "(1x,I0)", advance="no") prt%pdg if (.not. prt%visible) write (u, "(2x,A)", advance="no") "invisible" if (prt%parton) write (u, "(2x,A)", advance="no") "parton" if (prt%gauge) write (u, "(2x,A)", advance="no") "gauge" if (prt%left_handed) write (u, "(2x,A)", advance="no") "left" if (prt%right_handed) write (u, "(2x,A)", advance="no") "right" write (u, *) write (u, "(5x,A)", advance="no") "name" if (allocated (prt%name)) then do i = 1, size (prt%name) write (u, "(1x,A)", advance="no") '"' // char (prt%name(i)) // '"' end do write (u, *) if (prt%has_anti) then write (u, "(5x,A)", advance="no") "anti" do i = 1, size (prt%anti) write (u, "(1x,A)", advance="no") '"' // char (prt%anti(i)) // '"' end do write (u, *) end if if (prt%tex_name /= "") then write (u, "(5x,A)") & "tex_name " // '"' // char (prt%tex_name) // '"' end if if (prt%has_anti .and. prt%tex_anti /= "") then write (u, "(5x,A)") & "tex_anti " // '"' // char (prt%tex_anti) // '"' end if else write (u, "(A)") "???" end if write (u, "(5x,A)", advance="no") "spin " select case (mod (prt%spin_type - 1, 2)) case (0); write (u, "(I0)", advance="no") (prt%spin_type-1) / 2 case default; write (u, "(I0,A)", advance="no") prt%spin_type-1, "/2" end select ! write (u, "(2x,A,I1,A)") "! [multiplicity = ", prt%multiplicity, "]" if (abs (prt%isospin_type) /= 1) then write (u, "(2x,A)", advance="no") "isospin " select case (mod (abs (prt%isospin_type) - 1, 2)) case (0); write (u, "(I0)", advance="no") & sign (abs (prt%isospin_type) - 1, prt%isospin_type) / 2 case default; write (u, "(I0,A)", advance="no") & sign (abs (prt%isospin_type) - 1, prt%isospin_type), "/2" end select end if if (abs (prt%charge_type) /= 1) then write (u, "(2x,A)", advance="no") "charge " select case (mod (abs (prt%charge_type) - 1, 3)) case (0); write (u, "(I0)", advance="no") & sign (abs (prt%charge_type) - 1, prt%charge_type) / 3 case default; write (u, "(I0,A)", advance="no") & sign (abs (prt%charge_type) - 1, prt%charge_type), "/3" end select end if if (prt%color_type /= 1) then write (u, "(2x,A,I0)", advance="no") "color ", prt%color_type end if write (u, *) if (associated (prt%mass_data)) then write (u, "(5x,A)", advance="no") & "mass " // char (prt%mass_data%get_name ()) if (associated (prt%width_data)) then write (u, "(2x,A)") & "width " // char (prt%width_data%get_name ()) else write (u, *) end if end if call prt%write_decays (u) end subroutine field_data_write @ %def field_data_write @ Write decay and polarization data. <>= procedure :: write_decays => field_data_write_decays <>= subroutine field_data_write_decays (prt, unit) class(field_data_t), intent(in) :: prt integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) if (.not. prt%p_is_stable) then if (allocated (prt%p_decay)) then write (u, "(5x,A)", advance="no") "p_decay" do i = 1, size (prt%p_decay) write (u, "(1x,A)", advance="no") char (prt%p_decay(i)) end do if (prt%p_decays_isotropically) then write (u, "(1x,A)", advance="no") "isotropic" else if (prt%p_decays_diagonal) then write (u, "(1x,A)", advance="no") "diagonal" else if (prt%p_has_decay_helicity) then write (u, "(1x,A,I0)", advance="no") "helicity = ", & prt%p_decay_helicity end if write (u, *) end if else if (prt%p_polarized) then write (u, "(5x,A)") "p_polarized" end if if (.not. prt%a_is_stable) then if (allocated (prt%a_decay)) then write (u, "(5x,A)", advance="no") "a_decay" do i = 1, size (prt%a_decay) write (u, "(1x,A)", advance="no") char (prt%a_decay(i)) end do if (prt%a_decays_isotropically) then write (u, "(1x,A)", advance="no") "isotropic" else if (prt%a_decays_diagonal) then write (u, "(1x,A)", advance="no") "diagonal" else if (prt%a_has_decay_helicity) then write (u, "(1x,A,I0)", advance="no") "helicity = ", & prt%a_decay_helicity end if write (u, *) end if else if (prt%a_polarized) then write (u, "(5x,A)") "a_polarized" end if end subroutine field_data_write_decays @ %def field_data_write_decays @ Screen version of output. <>= procedure :: show => field_data_show <>= subroutine field_data_show (prt, l, u) class(field_data_t), intent(in) :: prt integer, intent(in) :: l, u character(len=l) :: buffer integer :: i type(string_t), dimension(:), allocatable :: decay buffer = prt%get_name (.false.) write (u, "(4x,A,1x,I8)", advance="no") buffer, & prt%get_pdg () if (prt%is_polarized ()) then write (u, "(3x,A)") "polarized" else if (.not. prt%is_stable ()) then write (u, "(3x,A)", advance="no") "decays:" call prt%get_decays (decay) do i = 1, size (decay) write (u, "(1x,A)", advance="no") char (decay(i)) end do write (u, *) else write (u, *) end if if (prt%has_antiparticle ()) then buffer = prt%get_name (.true.) write (u, "(4x,A,1x,I8)", advance="no") buffer, & prt%get_pdg_anti () if (prt%is_polarized (.true.)) then write (u, "(3x,A)") "polarized" else if (.not. prt%is_stable (.true.)) then write (u, "(3x,A)", advance="no") "decays:" call prt%get_decays (decay, .true.) do i = 1, size (decay) write (u, "(1x,A)", advance="no") char (decay(i)) end do write (u, *) else write (u, *) end if end if end subroutine field_data_show @ %def field_data_show @ Retrieve data: <>= procedure :: get_pdg => field_data_get_pdg procedure :: get_pdg_anti => field_data_get_pdg_anti <>= elemental function field_data_get_pdg (prt) result (pdg) integer :: pdg class(field_data_t), intent(in) :: prt pdg = prt%pdg end function field_data_get_pdg elemental function field_data_get_pdg_anti (prt) result (pdg) integer :: pdg class(field_data_t), intent(in) :: prt if (prt%has_anti) then pdg = - prt%pdg else pdg = prt%pdg end if end function field_data_get_pdg_anti @ %def field_data_get_pdg field_data_get_pdg_anti @ Predicates: <>= procedure :: is_visible => field_data_is_visible procedure :: is_parton => field_data_is_parton procedure :: is_gauge => field_data_is_gauge procedure :: is_left_handed => field_data_is_left_handed procedure :: is_right_handed => field_data_is_right_handed procedure :: has_antiparticle => field_data_has_antiparticle procedure :: is_stable => field_data_is_stable procedure :: get_decays => field_data_get_decays procedure :: decays_isotropically => field_data_decays_isotropically procedure :: decays_diagonal => field_data_decays_diagonal procedure :: has_decay_helicity => field_data_has_decay_helicity procedure :: decay_helicity => field_data_decay_helicity procedure :: is_polarized => field_data_is_polarized <>= elemental function field_data_is_visible (prt) result (flag) logical :: flag class(field_data_t), intent(in) :: prt flag = prt%visible end function field_data_is_visible elemental function field_data_is_parton (prt) result (flag) logical :: flag class(field_data_t), intent(in) :: prt flag = prt%parton end function field_data_is_parton elemental function field_data_is_gauge (prt) result (flag) logical :: flag class(field_data_t), intent(in) :: prt flag = prt%gauge end function field_data_is_gauge elemental function field_data_is_left_handed (prt) result (flag) logical :: flag class(field_data_t), intent(in) :: prt flag = prt%left_handed end function field_data_is_left_handed elemental function field_data_is_right_handed (prt) result (flag) logical :: flag class(field_data_t), intent(in) :: prt flag = prt%right_handed end function field_data_is_right_handed elemental function field_data_has_antiparticle (prt) result (flag) logical :: flag class(field_data_t), intent(in) :: prt flag = prt%has_anti end function field_data_has_antiparticle elemental function field_data_is_stable (prt, anti) result (flag) logical :: flag class(field_data_t), intent(in) :: prt logical, intent(in), optional :: anti if (present (anti)) then if (anti) then flag = prt%a_is_stable else flag = prt%p_is_stable end if else flag = prt%p_is_stable end if end function field_data_is_stable subroutine field_data_get_decays (prt, decay, anti) class(field_data_t), intent(in) :: prt type(string_t), dimension(:), intent(out), allocatable :: decay logical, intent(in), optional :: anti if (present (anti)) then if (anti) then allocate (decay (size (prt%a_decay)), source = prt%a_decay) else allocate (decay (size (prt%p_decay)), source = prt%p_decay) end if else allocate (decay (size (prt%p_decay)), source = prt%p_decay) end if end subroutine field_data_get_decays elemental function field_data_decays_isotropically & (prt, anti) result (flag) logical :: flag class(field_data_t), intent(in) :: prt logical, intent(in), optional :: anti if (present (anti)) then if (anti) then flag = prt%a_decays_isotropically else flag = prt%p_decays_isotropically end if else flag = prt%p_decays_isotropically end if end function field_data_decays_isotropically elemental function field_data_decays_diagonal & (prt, anti) result (flag) logical :: flag class(field_data_t), intent(in) :: prt logical, intent(in), optional :: anti if (present (anti)) then if (anti) then flag = prt%a_decays_diagonal else flag = prt%p_decays_diagonal end if else flag = prt%p_decays_diagonal end if end function field_data_decays_diagonal elemental function field_data_has_decay_helicity & (prt, anti) result (flag) logical :: flag class(field_data_t), intent(in) :: prt logical, intent(in), optional :: anti if (present (anti)) then if (anti) then flag = prt%a_has_decay_helicity else flag = prt%p_has_decay_helicity end if else flag = prt%p_has_decay_helicity end if end function field_data_has_decay_helicity elemental function field_data_decay_helicity & (prt, anti) result (hel) integer :: hel class(field_data_t), intent(in) :: prt logical, intent(in), optional :: anti if (present (anti)) then if (anti) then hel = prt%a_decay_helicity else hel = prt%p_decay_helicity end if else hel = prt%p_decay_helicity end if end function field_data_decay_helicity elemental function field_data_is_polarized (prt, anti) result (flag) logical :: flag class(field_data_t), intent(in) :: prt logical, intent(in), optional :: anti logical :: a if (present (anti)) then a = anti else a = .false. end if if (a) then flag = prt%a_polarized else flag = prt%p_polarized end if end function field_data_is_polarized @ %def field_data_is_visible field_data_is_parton @ %def field_data_is_gauge @ %def field_data_is_left_handed field_data_is_right_handed @ %def field_data_has_antiparticle @ %def field_data_is_stable @ %def field_data_decays_isotropically @ %def field_data_decays_diagonal @ %def field_data_has_decay_helicity @ %def field_data_decay_helicity @ %def field_data_polarized @ Names. Return the first name in the list (or the first antiparticle name) <>= procedure :: get_longname => field_data_get_longname procedure :: get_name => field_data_get_name procedure :: get_name_array => field_data_get_name_array <>= pure function field_data_get_longname (prt) result (name) type(string_t) :: name class(field_data_t), intent(in) :: prt name = prt%longname end function field_data_get_longname pure function field_data_get_name (prt, is_antiparticle) result (name) type(string_t) :: name class(field_data_t), intent(in) :: prt logical, intent(in) :: is_antiparticle name = prt%longname if (is_antiparticle) then if (prt%has_anti) then if (allocated (prt%anti)) then if (size(prt%anti) > 0) name = prt%anti(1) end if else if (allocated (prt%name)) then if (size (prt%name) > 0) name = prt%name(1) end if end if else if (allocated (prt%name)) then if (size (prt%name) > 0) name = prt%name(1) end if end if end function field_data_get_name subroutine field_data_get_name_array (prt, is_antiparticle, name) class(field_data_t), intent(in) :: prt logical, intent(in) :: is_antiparticle type(string_t), dimension(:), allocatable, intent(inout) :: name if (allocated (name)) deallocate (name) if (is_antiparticle) then if (prt%has_anti) then allocate (name (size (prt%anti))) name = prt%anti else allocate (name (0)) end if else allocate (name (size (prt%name))) name = prt%name end if end subroutine field_data_get_name_array @ %def field_data_get_name @ Same for the \TeX\ name. <>= procedure :: get_tex_name => field_data_get_tex_name <>= elemental function field_data_get_tex_name & (prt, is_antiparticle) result (name) type(string_t) :: name class(field_data_t), intent(in) :: prt logical, intent(in) :: is_antiparticle if (is_antiparticle) then if (prt%has_anti) then name = prt%tex_anti else name = prt%tex_name end if else name = prt%tex_name end if if (name == "") name = prt%get_name (is_antiparticle) end function field_data_get_tex_name @ %def field_data_get_tex_name @ Check if any of the field names matches the given string. <>= procedure, private :: matches_name => field_data_matches_name <>= function field_data_matches_name (field, name, is_antiparticle) result (flag) class(field_data_t), intent(in) :: field type(string_t), intent(in) :: name logical, intent(in) :: is_antiparticle logical :: flag if (is_antiparticle) then if (field%has_anti) then flag = any (name == field%anti) else flag = .false. end if else flag = name == field%longname .or. any (name == field%name) end if end function field_data_matches_name @ %def field_data_matches_name @ Quantum numbers <>= procedure :: get_spin_type => field_data_get_spin_type procedure :: get_multiplicity => field_data_get_multiplicity procedure :: get_isospin_type => field_data_get_isospin_type procedure :: get_charge_type => field_data_get_charge_type procedure :: get_color_type => field_data_get_color_type <>= elemental function field_data_get_spin_type (prt) result (type) integer :: type class(field_data_t), intent(in) :: prt type = prt%spin_type end function field_data_get_spin_type elemental function field_data_get_multiplicity (prt) result (type) integer :: type class(field_data_t), intent(in) :: prt type = prt%multiplicity end function field_data_get_multiplicity elemental function field_data_get_isospin_type (prt) result (type) integer :: type class(field_data_t), intent(in) :: prt type = prt%isospin_type end function field_data_get_isospin_type elemental function field_data_get_charge_type (prt) result (type) integer :: type class(field_data_t), intent(in) :: prt type = prt%charge_type end function field_data_get_charge_type elemental function field_data_get_color_type (prt) result (type) integer :: type class(field_data_t), intent(in) :: prt type = prt%color_type end function field_data_get_color_type @ %def field_data_get_spin_type @ %def field_data_get_multiplicity @ %def field_data_get_isospin_type @ %def field_data_get_charge_type @ %def field_data_get_color_type @ In the MSSM, neutralinos can have a negative mass. This is relevant for computing matrix elements. However, within the \whizard\ main program we are interested only in kinematics, therefore we return the absolute value of the particle mass. If desired, we can extract the sign separately. <>= procedure :: get_charge => field_data_get_charge procedure :: get_isospin => field_data_get_isospin procedure :: get_mass => field_data_get_mass procedure :: get_mass_sign => field_data_get_mass_sign procedure :: get_width => field_data_get_width <>= elemental function field_data_get_charge (prt) result (charge) real(default) :: charge class(field_data_t), intent(in) :: prt if (prt%charge_type /= 0) then charge = real (sign ((abs(prt%charge_type) - 1), & prt%charge_type), default) / 3 else charge = 0 end if end function field_data_get_charge elemental function field_data_get_isospin (prt) result (isospin) real(default) :: isospin class(field_data_t), intent(in) :: prt if (prt%isospin_type /= 0) then isospin = real (sign (abs(prt%isospin_type) - 1, & prt%isospin_type), default) / 2 else isospin = 0 end if end function field_data_get_isospin elemental function field_data_get_mass (prt) result (mass) real(default) :: mass class(field_data_t), intent(in) :: prt if (associated (prt%mass_val)) then mass = abs (prt%mass_val) else mass = 0 end if end function field_data_get_mass elemental function field_data_get_mass_sign (prt) result (sgn) integer :: sgn class(field_data_t), intent(in) :: prt if (associated (prt%mass_val)) then sgn = sign (1._default, prt%mass_val) else sgn = 0 end if end function field_data_get_mass_sign elemental function field_data_get_width (prt) result (width) real(default) :: width class(field_data_t), intent(in) :: prt if (associated (prt%width_val)) then width = prt%width_val else width = 0 end if end function field_data_get_width @ %def field_data_get_charge field_data_get_isospin @ %def field_data_get_mass field_data_get_mass_sign @ %def field_data_get_width @ Find the [[model]] containing the [[PDG]] given two model files. <>= public :: find_model <>= subroutine find_model (model, PDG, model_A, model_B) class(model_data_t), pointer, intent(out) :: model integer, intent(in) :: PDG class(model_data_t), intent(in), target :: model_A, model_B character(len=10) :: buffer if (model_A%test_field (PDG)) then model => model_A else if (model_B%test_field (PDG)) then model => model_B else call model_A%write () call model_B%write () write (buffer, "(I10)") PDG call msg_fatal ("Parton " // buffer // & " not found in the given model files") end if end subroutine find_model @ %def find_model @ \subsection{Vertex data} The vertex object contains an array of particle-data pointers, for which we need a separate type. (We could use the flavor type defined in another module.) The program does not (yet?) make use of vertex definitions, so they are not stored here. <>= type :: field_data_p type(field_data_t), pointer :: p => null () end type field_data_p @ %def field_data_p <>= type :: vertex_t private logical :: trilinear integer, dimension(:), allocatable :: pdg type(field_data_p), dimension(:), allocatable :: prt contains <> end type vertex_t @ %def vertex_t <>= procedure :: write => vertex_write <>= subroutine vertex_write (vtx, unit) class(vertex_t), intent(in) :: vtx integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) write (u, "(3x,A)", advance="no") "vertex" do i = 1, size (vtx%prt) if (associated (vtx%prt(i)%p)) then write (u, "(1x,A)", advance="no") & '"' // char (vtx%prt(i)%p%get_name (vtx%pdg(i) < 0)) & // '"' else write (u, "(1x,I7)", advance="no") vtx%pdg(i) end if end do write (u, *) end subroutine vertex_write @ %def vertex_write @ Initialize using PDG codes. The model is used for finding particle data pointers associated with the pdg codes. <>= procedure :: init => vertex_init <>= subroutine vertex_init (vtx, pdg, model) class(vertex_t), intent(out) :: vtx integer, dimension(:), intent(in) :: pdg type(model_data_t), intent(in), target, optional :: model integer :: i allocate (vtx%pdg (size (pdg))) allocate (vtx%prt (size (pdg))) vtx%trilinear = size (pdg) == 3 vtx%pdg = pdg if (present (model)) then do i = 1, size (pdg) vtx%prt(i)%p => model%get_field_ptr (pdg(i)) end do end if end subroutine vertex_init @ %def vertex_init @ Copy vertex: we must reassign the field-data pointer to a new model. <>= procedure :: copy_from => vertex_copy_from <>= subroutine vertex_copy_from (vtx, old_vtx, new_model) class(vertex_t), intent(out) :: vtx class(vertex_t), intent(in) :: old_vtx type(model_data_t), intent(in), target, optional :: new_model call vtx%init (old_vtx%pdg, new_model) end subroutine vertex_copy_from @ %def vertex_copy_from @ Single-particle lookup: Given a particle code, we return matching codes if present, otherwise zero. Actually, we return the antiparticles of the matching codes, as appropriate for computing splittings. <>= procedure :: get_match => vertex_get_match <>= subroutine vertex_get_match (vtx, pdg1, pdg2) class(vertex_t), intent(in) :: vtx integer, intent(in) :: pdg1 integer, dimension(:), allocatable, intent(out) :: pdg2 integer :: i, j do i = 1, size (vtx%pdg) if (vtx%pdg(i) == pdg1) then allocate (pdg2 (size (vtx%pdg) - 1)) do j = 1, i-1 pdg2(j) = anti (j) end do do j = i, size (pdg2) pdg2(j) = anti (j+1) end do exit end if end do contains function anti (i) result (pdg) integer, intent(in) :: i integer :: pdg if (vtx%prt(i)%p%has_antiparticle ()) then pdg = - vtx%pdg(i) else pdg = vtx%pdg(i) end if end function anti end subroutine vertex_get_match @ %def vertex_get_match @ To access this from the outside, we create an iterator. The iterator has the sole purpose of returning the matching particles for a given array of PDG codes. <>= public :: vertex_iterator_t <>= type :: vertex_iterator_t private class(model_data_t), pointer :: model => null () integer, dimension(:), allocatable :: pdg integer :: vertex_index = 0 integer :: pdg_index = 0 logical :: save_pdg_index contains procedure :: init => vertex_iterator_init procedure :: get_next_match => vertex_iterator_get_next_match end type vertex_iterator_t @ %def vertex_iterator_t @ We initialize the iterator for a particular model with the [[pdg]] index of the particle we are looking at. <>= subroutine vertex_iterator_init (it, model, pdg, save_pdg_index) class(vertex_iterator_t), intent(out) :: it class(model_data_t), intent(in), target :: model integer, dimension(:), intent(in) :: pdg logical, intent(in) :: save_pdg_index it%model => model allocate (it%pdg (size (pdg)), source = pdg) it%save_pdg_index = save_pdg_index end subroutine vertex_iterator_init subroutine vertex_iterator_get_next_match (it, pdg_match) class(vertex_iterator_t), intent(inout) :: it integer, dimension(:), allocatable, intent(out) :: pdg_match integer :: i, j do i = it%vertex_index + 1, size (it%model%vtx) do j = it%pdg_index + 1, size (it%pdg) call it%model%vtx(i)%get_match (it%pdg(j), pdg_match) if (it%save_pdg_index) then if (allocated (pdg_match) .and. j < size (it%pdg)) then it%pdg_index = j return else if (allocated (pdg_match) .and. j == size (it%pdg)) then it%vertex_index = i it%pdg_index = 0 return end if else if (allocated (pdg_match)) then it%vertex_index = i return end if end do end do it%vertex_index = 0 it%pdg_index = 0 end subroutine vertex_iterator_get_next_match @ %def vertex_iterator_get_next_match @ \subsection{Vertex lookup table} The vertex lookup table is a hash table: given two particle codes, we check which codes are allowed for the third one. The size of the hash table should be large enough that collisions are rare. We first select a size based on the number of vertices (multiplied by six because all permutations count), with some margin, and then choose the smallest integer power of two larger than this. <>= integer, parameter :: VERTEX_TABLE_SCALE_FACTOR = 60 @ %def VERTEX_TABLE_SCALE_FACTOR <>= function vertex_table_size (n_vtx) result (n) integer(i32) :: n integer, intent(in) :: n_vtx integer :: i, s s = VERTEX_TABLE_SCALE_FACTOR * n_vtx n = 1 do i = 1, 31 n = ishft (n, 1) s = ishft (s,-1) if (s == 0) exit end do end function vertex_table_size @ %def vertex_table_size @ The specific hash function takes two particle codes (arbitrary integers) and returns a 32-bit integer. It makes use of the universal function [[hash]] which operates on a byte array. <>= function hash2 (pdg1, pdg2) integer(i32) :: hash2 integer, intent(in) :: pdg1, pdg2 integer(i8), dimension(1) :: mold hash2 = hash (transfer ([pdg1, pdg2], mold)) end function hash2 @ %def hash2 @ Each entry in the vertex table stores the two particle codes and an array of possibilities for the third code. <>= type :: vertex_table_entry_t private integer :: pdg1 = 0, pdg2 = 0 integer :: n = 0 integer, dimension(:), allocatable :: pdg3 end type vertex_table_entry_t @ %def vertex_table_entry_t @ The vertex table: <>= type :: vertex_table_t type(vertex_table_entry_t), dimension(:), allocatable :: entry integer :: n_collisions = 0 integer(i32) :: mask contains <> end type vertex_table_t @ %def vertex_table_t @ Output. <>= procedure :: write => vertex_table_write <>= subroutine vertex_table_write (vt, unit) class(vertex_table_t), intent(in) :: vt integer, intent(in), optional :: unit integer :: u, i character(9) :: size_pdg3 u = given_output_unit (unit) write (u, "(A)") "vertex hash table:" write (u, "(A,I7)") " size = ", size (vt%entry) write (u, "(A,I7)") " used = ", count (vt%entry%n /= 0) write (u, "(A,I7)") " coll = ", vt%n_collisions do i = lbound (vt%entry, 1), ubound (vt%entry, 1) if (vt%entry(i)%n /= 0) then write (size_pdg3, "(I7)") size (vt%entry(i)%pdg3) write (u, "(A,1x,I7,1x,A,2(1x,I7),A," // & size_pdg3 // "(1x,I7))") & " ", i, ":", vt%entry(i)%pdg1, & vt%entry(i)%pdg2, "->", vt%entry(i)%pdg3 end if end do end subroutine vertex_table_write @ %def vertex_table_write @ Initializing the vertex table: This is done in two passes. First, we scan all permutations for all vertices and count the number of entries in each bucket of the hashtable. Then, the buckets are allocated accordingly and filled. Collision resolution is done by just incrementing the hash value until an empty bucket is found. The vertex table size is fixed, since we know from the beginning the number of entries. <>= procedure :: init => vertex_table_init <>= subroutine vertex_table_init (vt, prt, vtx) class(vertex_table_t), intent(out) :: vt type(field_data_t), dimension(:), intent(in) :: prt type(vertex_t), dimension(:), intent(in) :: vtx integer :: n_vtx, vt_size, i, p1, p2, p3 integer, dimension(3) :: p n_vtx = size (vtx) vt_size = vertex_table_size (count (vtx%trilinear)) vt%mask = vt_size - 1 allocate (vt%entry (0:vt_size-1)) do i = 1, n_vtx if (vtx(i)%trilinear) then p = vtx(i)%pdg p1 = p(1); p2 = p(2) call create (hash2 (p1, p2)) if (p(2) /= p(3)) then p2 = p(3) call create (hash2 (p1, p2)) end if if (p(1) /= p(2)) then p1 = p(2); p2 = p(1) call create (hash2 (p1, p2)) if (p(1) /= p(3)) then p2 = p(3) call create (hash2 (p1, p2)) end if end if if (p(1) /= p(3)) then p1 = p(3); p2 = p(1) call create (hash2 (p1, p2)) if (p(1) /= p(2)) then p2 = p(2) call create (hash2 (p1, p2)) end if end if end if end do do i = 0, vt_size - 1 allocate (vt%entry(i)%pdg3 (vt%entry(i)%n)) end do vt%entry%n = 0 do i = 1, n_vtx if (vtx(i)%trilinear) then p = vtx(i)%pdg p1 = p(1); p2 = p(2); p3 = p(3) call register (hash2 (p1, p2)) if (p(2) /= p(3)) then p2 = p(3); p3 = p(2) call register (hash2 (p1, p2)) end if if (p(1) /= p(2)) then p1 = p(2); p2 = p(1); p3 = p(3) call register (hash2 (p1, p2)) if (p(1) /= p(3)) then p2 = p(3); p3 = p(1) call register (hash2 (p1, p2)) end if end if if (p(1) /= p(3)) then p1 = p(3); p2 = p(1); p3 = p(2) call register (hash2 (p1, p2)) if (p(1) /= p(2)) then p2 = p(2); p3 = p(1) call register (hash2 (p1, p2)) end if end if end if end do contains recursive subroutine create (hashval) integer(i32), intent(in) :: hashval integer :: h h = iand (hashval, vt%mask) if (vt%entry(h)%n == 0) then vt%entry(h)%pdg1 = p1 vt%entry(h)%pdg2 = p2 vt%entry(h)%n = 1 else if (vt%entry(h)%pdg1 == p1 .and. vt%entry(h)%pdg2 == p2) then vt%entry(h)%n = vt%entry(h)%n + 1 else vt%n_collisions = vt%n_collisions + 1 call create (hashval + 1) end if end subroutine create recursive subroutine register (hashval) integer(i32), intent(in) :: hashval integer :: h h = iand (hashval, vt%mask) if (vt%entry(h)%pdg1 == p1 .and. vt%entry(h)%pdg2 == p2) then vt%entry(h)%n = vt%entry(h)%n + 1 vt%entry(h)%pdg3(vt%entry(h)%n) = p3 else call register (hashval + 1) end if end subroutine register end subroutine vertex_table_init @ %def vertex_table_init @ Return the array of particle codes that match the given pair. <>= procedure :: match => vertex_table_match <>= subroutine vertex_table_match (vt, pdg1, pdg2, pdg3) class(vertex_table_t), intent(in) :: vt integer, intent(in) :: pdg1, pdg2 integer, dimension(:), allocatable, intent(out) :: pdg3 call match (hash2 (pdg1, pdg2)) contains recursive subroutine match (hashval) integer(i32), intent(in) :: hashval integer :: h h = iand (hashval, vt%mask) if (vt%entry(h)%n == 0) then allocate (pdg3 (0)) else if (vt%entry(h)%pdg1 == pdg1 .and. vt%entry(h)%pdg2 == pdg2) then allocate (pdg3 (size (vt%entry(h)%pdg3))) pdg3 = vt%entry(h)%pdg3 else call match (hashval + 1) end if end subroutine match end subroutine vertex_table_match @ %def vertex_table_match @ Return true if the triplet is represented as a vertex. <>= procedure :: check => vertex_table_check <>= function vertex_table_check (vt, pdg1, pdg2, pdg3) result (flag) class(vertex_table_t), intent(in) :: vt integer, intent(in) :: pdg1, pdg2, pdg3 logical :: flag flag = check (hash2 (pdg1, pdg2)) contains recursive function check (hashval) result (flag) integer(i32), intent(in) :: hashval integer :: h logical :: flag h = iand (hashval, vt%mask) if (vt%entry(h)%n == 0) then flag = .false. else if (vt%entry(h)%pdg1 == pdg1 .and. vt%entry(h)%pdg2 == pdg2) then flag = any (vt%entry(h)%pdg3 == pdg3) else flag = check (hashval + 1) end if end function check end function vertex_table_check @ %def vertex_table_check @ \subsection{Model Data Record} This type collects the model data as defined above. We deliberately implement the parameter arrays as pointer arrays. We thus avoid keeping track of TARGET attributes. The [[scheme]] identifier provides meta information. It doesn't give the client code an extra parameter, but it tells something about the interpretation of the parameters. If the scheme ID is left as default (zero), it is ignored. <>= public :: model_data_t <>= type :: model_data_t private type(string_t) :: name integer :: scheme = 0 type(modelpar_real_t), dimension(:), pointer :: par_real => null () type(modelpar_complex_t), dimension(:), pointer :: par_complex => null () type(field_data_t), dimension(:), allocatable :: field type(vertex_t), dimension(:), allocatable :: vtx type(vertex_table_t) :: vt contains <> end type model_data_t @ %def model_data_t @ Finalizer, deallocate pointer arrays. <>= procedure :: final => model_data_final <>= subroutine model_data_final (model) class(model_data_t), intent(inout) :: model if (associated (model%par_real)) then deallocate (model%par_real) end if if (associated (model%par_complex)) then deallocate (model%par_complex) end if end subroutine model_data_final @ %def model_data_final @ Output. The signature matches the signature of the high-level [[model_write]] procedure, so some of the options don't actually apply. <>= procedure :: write => model_data_write <>= subroutine model_data_write (model, unit, verbose, & show_md5sum, show_variables, show_parameters, & show_particles, show_vertices, show_scheme) class(model_data_t), intent(in) :: model integer, intent(in), optional :: unit logical, intent(in), optional :: verbose logical, intent(in), optional :: show_md5sum logical, intent(in), optional :: show_variables logical, intent(in), optional :: show_parameters logical, intent(in), optional :: show_particles logical, intent(in), optional :: show_vertices logical, intent(in), optional :: show_scheme logical :: show_sch, show_par, show_prt, show_vtx integer :: u, i u = given_output_unit (unit) show_sch = .false.; if (present (show_scheme)) & show_sch = show_scheme show_par = .true.; if (present (show_parameters)) & show_par = show_parameters show_prt = .true.; if (present (show_particles)) & show_prt = show_particles show_vtx = .true.; if (present (show_vertices)) & show_vtx = show_vertices if (show_sch) then write (u, "(3x,A,1X,I0)") "scheme =", model%scheme end if if (show_par) then do i = 1, size (model%par_real) call model%par_real(i)%write (u) write (u, "(A)") end do do i = 1, size (model%par_complex) call model%par_complex(i)%write (u) write (u, "(A)") end do end if if (show_prt) then write (u, "(A)") call model%write_fields (u) end if if (show_vtx) then write (u, "(A)") call model%write_vertices (u, verbose) end if end subroutine model_data_write @ %def model_data_write @ Initialize, allocating pointer arrays. The second version makes a deep copy. <>= generic :: init => model_data_init procedure, private :: model_data_init <>= subroutine model_data_init (model, name, & n_par_real, n_par_complex, n_field, n_vtx) class(model_data_t), intent(out) :: model type(string_t), intent(in) :: name integer, intent(in) :: n_par_real, n_par_complex integer, intent(in) :: n_field integer, intent(in) :: n_vtx model%name = name allocate (model%par_real (n_par_real)) allocate (model%par_complex (n_par_complex)) allocate (model%field (n_field)) allocate (model%vtx (n_vtx)) end subroutine model_data_init @ %def model_data_init @ Set the scheme ID. <>= procedure :: set_scheme_num => model_data_set_scheme_num <>= subroutine model_data_set_scheme_num (model, scheme) class(model_data_t), intent(inout) :: model integer, intent(in) :: scheme model%scheme = scheme end subroutine model_data_set_scheme_num @ %def model_data_set_scheme_num @ Complete model data initialization. <>= procedure :: freeze_fields => model_data_freeze_fields <>= subroutine model_data_freeze_fields (model) class(model_data_t), intent(inout) :: model call model%field%freeze () end subroutine model_data_freeze_fields @ %def model_data_freeze @ Deep copy. The new model should already be initialized, so we do not allocate memory. <>= procedure :: copy_from => model_data_copy <>= subroutine model_data_copy (model, src) class(model_data_t), intent(inout), target :: model class(model_data_t), intent(in), target :: src class(modelpar_data_t), pointer :: data, src_data integer :: i model%scheme = src%scheme model%par_real = src%par_real model%par_complex = src%par_complex do i = 1, size (src%field) associate (field => model%field(i), src_field => src%field(i)) call field%init (src_field%get_longname (), src_field%get_pdg ()) call field%copy_from (src_field) src_data => src_field%mass_data if (associated (src_data)) then data => model%get_par_data_ptr (src_data%get_name ()) call field%set (mass_data = data) end if src_data => src_field%width_data if (associated (src_data)) then data => model%get_par_data_ptr (src_data%get_name ()) call field%set (width_data = data) end if call field%set_multiplicity () end associate end do do i = 1, size (src%vtx) call model%vtx(i)%copy_from (src%vtx(i), model) end do call model%freeze_vertices () end subroutine model_data_copy @ %def model_data_copy @ Return the model name and numeric scheme. <>= procedure :: get_name => model_data_get_name procedure :: get_scheme_num => model_data_get_scheme_num <>= function model_data_get_name (model) result (name) class(model_data_t), intent(in) :: model type(string_t) :: name name = model%name end function model_data_get_name function model_data_get_scheme_num (model) result (scheme) class(model_data_t), intent(in) :: model integer :: scheme scheme = model%scheme end function model_data_get_scheme_num @ %def model_data_get_name @ %def model_data_get_scheme @ Retrieve a MD5 sum for the current model parameter values and decay/polarization settings. This is done by writing them to a temporary file, using a standard format. If the model scheme is nonzero, it is also written. <>= procedure :: get_parameters_md5sum => model_data_get_parameters_md5sum <>= function model_data_get_parameters_md5sum (model) result (par_md5sum) character(32) :: par_md5sum class(model_data_t), intent(in) :: model real(default), dimension(:), allocatable :: par type(field_data_t), pointer :: field integer :: unit, i allocate (par (model%get_n_real ())) call model%real_parameters_to_array (par) unit = free_unit () open (unit, status="scratch", action="readwrite") if (model%scheme /= 0) write (unit, "(I0)") model%scheme write (unit, "(" // FMT_19 // ")") par do i = 1, model%get_n_field () field => model%get_field_ptr_by_index (i) if (.not. field%is_stable (.false.) .or. .not. field%is_stable (.true.) & .or. field%is_polarized (.false.) .or. field%is_polarized (.true.))& then write (unit, "(3x,A)") char (field%get_longname ()) call field%write_decays (unit) end if end do rewind (unit) par_md5sum = md5sum (unit) close (unit) end function model_data_get_parameters_md5sum @ %def model_get_parameters_md5sum @ Return the MD5 sum. This is a placeholder, to be overwritten for the complete model definition. <>= procedure :: get_md5sum => model_data_get_md5sum <>= function model_data_get_md5sum (model) result (md5sum) class(model_data_t), intent(in) :: model character(32) :: md5sum md5sum = model%get_parameters_md5sum () end function model_data_get_md5sum @ %def model_data_get_md5sum @ Initialize a real or complex parameter. <>= generic :: init_par => model_data_init_par_real, model_data_init_par_complex procedure, private :: model_data_init_par_real procedure, private :: model_data_init_par_complex <>= subroutine model_data_init_par_real (model, i, name, value) class(model_data_t), intent(inout) :: model integer, intent(in) :: i type(string_t), intent(in) :: name real(default), intent(in) :: value call model%par_real(i)%init (name, value) end subroutine model_data_init_par_real subroutine model_data_init_par_complex (model, i, name, value) class(model_data_t), intent(inout) :: model integer, intent(in) :: i type(string_t), intent(in) :: name complex(default), intent(in) :: value call model%par_complex(i)%init (name, value) end subroutine model_data_init_par_complex @ %def model_data_init_par_real model_data_init_par_complex @ After initialization, return size of parameter array. <>= procedure :: get_n_real => model_data_get_n_real procedure :: get_n_complex => model_data_get_n_complex <>= function model_data_get_n_real (model) result (n) class(model_data_t), intent(in) :: model integer :: n n = size (model%par_real) end function model_data_get_n_real function model_data_get_n_complex (model) result (n) class(model_data_t), intent(in) :: model integer :: n n = size (model%par_complex) end function model_data_get_n_complex @ %def model_data_get_n_real @ %def model_data_get_n_complex @ After initialization, extract the whole parameter array. <>= procedure :: real_parameters_to_array & => model_data_real_par_to_array procedure :: complex_parameters_to_array & => model_data_complex_par_to_array <>= subroutine model_data_real_par_to_array (model, array) class(model_data_t), intent(in) :: model real(default), dimension(:), intent(inout) :: array array = model%par_real%get_real () end subroutine model_data_real_par_to_array subroutine model_data_complex_par_to_array (model, array) class(model_data_t), intent(in) :: model complex(default), dimension(:), intent(inout) :: array array = model%par_complex%get_complex () end subroutine model_data_complex_par_to_array @ %def model_data_real_par_to_array @ %def model_data_complex_par_to_array @ After initialization, set the whole parameter array. <>= procedure :: real_parameters_from_array & => model_data_real_par_from_array procedure :: complex_parameters_from_array & => model_data_complex_par_from_array <>= subroutine model_data_real_par_from_array (model, array) class(model_data_t), intent(inout) :: model real(default), dimension(:), intent(in) :: array model%par_real = array end subroutine model_data_real_par_from_array subroutine model_data_complex_par_from_array (model, array) class(model_data_t), intent(inout) :: model complex(default), dimension(:), intent(in) :: array model%par_complex = array end subroutine model_data_complex_par_from_array @ %def model_data_real_par_from_array @ %def model_data_complex_par_from_array @ Analogous, for a C parameter array. <>= procedure :: real_parameters_to_c_array & => model_data_real_par_to_c_array <>= subroutine model_data_real_par_to_c_array (model, array) class(model_data_t), intent(in) :: model real(c_default_float), dimension(:), intent(inout) :: array array = model%par_real%get_real () end subroutine model_data_real_par_to_c_array @ %def model_data_real_par_to_c_array @ After initialization, set the whole parameter array. <>= procedure :: real_parameters_from_c_array & => model_data_real_par_from_c_array <>= subroutine model_data_real_par_from_c_array (model, array) class(model_data_t), intent(inout) :: model real(c_default_float), dimension(:), intent(in) :: array model%par_real = real (array, default) end subroutine model_data_real_par_from_c_array @ %def model_data_real_par_from_c_array @ After initialization, get pointer to a real or complex parameter, directly by index. <>= procedure :: get_par_real_ptr => model_data_get_par_real_ptr_index procedure :: get_par_complex_ptr => model_data_get_par_complex_ptr_index <>= function model_data_get_par_real_ptr_index (model, i) result (ptr) class(model_data_t), intent(inout) :: model integer, intent(in) :: i class(modelpar_data_t), pointer :: ptr ptr => model%par_real(i) end function model_data_get_par_real_ptr_index function model_data_get_par_complex_ptr_index (model, i) result (ptr) class(model_data_t), intent(inout) :: model integer, intent(in) :: i class(modelpar_data_t), pointer :: ptr ptr => model%par_complex(i) end function model_data_get_par_complex_ptr_index @ %def model_data_get_par_real_ptr model_data_get_par_complex_ptr @ After initialization, get pointer to a parameter by name. <>= procedure :: get_par_data_ptr => model_data_get_par_data_ptr_name <>= function model_data_get_par_data_ptr_name (model, name) result (ptr) class(model_data_t), intent(in) :: model type(string_t), intent(in) :: name class(modelpar_data_t), pointer :: ptr integer :: i do i = 1, size (model%par_real) if (model%par_real(i)%name == name) then ptr => model%par_real(i) return end if end do do i = 1, size (model%par_complex) if (model%par_complex(i)%name == name) then ptr => model%par_complex(i) return end if end do ptr => null () end function model_data_get_par_data_ptr_name @ %def model_data_get_par_data_ptr @ Return the value by name. Again, type conversion is allowed. <>= procedure :: get_real => model_data_get_par_real_value procedure :: get_complex => model_data_get_par_complex_value <>= function model_data_get_par_real_value (model, name) result (value) class(model_data_t), intent(in) :: model type(string_t), intent(in) :: name class(modelpar_data_t), pointer :: par real(default) :: value par => model%get_par_data_ptr (name) value = par%get_real () end function model_data_get_par_real_value function model_data_get_par_complex_value (model, name) result (value) class(model_data_t), intent(in) :: model type(string_t), intent(in) :: name class(modelpar_data_t), pointer :: par complex(default) :: value par => model%get_par_data_ptr (name) value = par%get_complex () end function model_data_get_par_complex_value @ %def model_data_get_real @ %def model_data_get_complex @ Modify a real or complex parameter. <>= generic :: set_par => model_data_set_par_real, model_data_set_par_complex procedure, private :: model_data_set_par_real procedure, private :: model_data_set_par_complex <>= subroutine model_data_set_par_real (model, name, value) class(model_data_t), intent(inout) :: model type(string_t), intent(in) :: name real(default), intent(in) :: value class(modelpar_data_t), pointer :: par par => model%get_par_data_ptr (name) par = value end subroutine model_data_set_par_real subroutine model_data_set_par_complex (model, name, value) class(model_data_t), intent(inout) :: model type(string_t), intent(in) :: name complex(default), intent(in) :: value class(modelpar_data_t), pointer :: par par => model%get_par_data_ptr (name) par = value end subroutine model_data_set_par_complex @ %def model_data_set_par_real model_data_set_par_complex @ List all fields in the model. <>= procedure :: write_fields => model_data_write_fields <>= subroutine model_data_write_fields (model, unit) class(model_data_t), intent(in) :: model integer, intent(in), optional :: unit integer :: i do i = 1, size (model%field) call model%field(i)%write (unit) end do end subroutine model_data_write_fields @ %def model_data_write_fields @ After initialization, return number of fields (particles): <>= procedure :: get_n_field => model_data_get_n_field <>= function model_data_get_n_field (model) result (n) class(model_data_t), intent(in) :: model integer :: n n = size (model%field) end function model_data_get_n_field @ %def model_data_get_n_field @ Return the PDG code of a field. The field is identified by name or by index. If the field is not found, return zero. <>= generic :: get_pdg => & model_data_get_field_pdg_index, & model_data_get_field_pdg_name procedure, private :: model_data_get_field_pdg_index procedure, private :: model_data_get_field_pdg_name <>= function model_data_get_field_pdg_index (model, i) result (pdg) class(model_data_t), intent(in) :: model integer, intent(in) :: i integer :: pdg pdg = model%field(i)%get_pdg () end function model_data_get_field_pdg_index function model_data_get_field_pdg_name (model, name, check) result (pdg) class(model_data_t), intent(in) :: model type(string_t), intent(in) :: name logical, intent(in), optional :: check integer :: pdg integer :: i do i = 1, size (model%field) associate (field => model%field(i)) if (field%matches_name (name, .false.)) then pdg = field%get_pdg () return else if (field%matches_name (name, .true.)) then pdg = - field%get_pdg () return end if end associate end do pdg = 0 call model%field_error (check, name) end function model_data_get_field_pdg_name @ %def model_data_get_field_pdg @ Return an array of all PDG codes, including antiparticles. The antiparticle are sorted after all particles. <>= procedure :: get_all_pdg => model_data_get_all_pdg <>= subroutine model_data_get_all_pdg (model, pdg) class(model_data_t), intent(in) :: model integer, dimension(:), allocatable, intent(inout) :: pdg integer :: n0, n1, i, k n0 = size (model%field) n1 = n0 + count (model%field%has_antiparticle ()) allocate (pdg (n1)) pdg(1:n0) = model%field%get_pdg () k = n0 do i = 1, size (model%field) associate (field => model%field(i)) if (field%has_antiparticle ()) then k = k + 1 pdg(k) = - field%get_pdg () end if end associate end do end subroutine model_data_get_all_pdg @ %def model_data_get_all_pdg @ Return pointer to the field array. <>= procedure :: get_field_array_ptr => model_data_get_field_array_ptr <>= function model_data_get_field_array_ptr (model) result (ptr) class(model_data_t), intent(in), target :: model type(field_data_t), dimension(:), pointer :: ptr ptr => model%field end function model_data_get_field_array_ptr @ %def model_data_get_field_array_ptr @ Return pointer to a field. The identifier should be the unique long name, the PDG code, or the index. We can issue an error message, if the [[check]] flag is set. We never return an error if the PDG code is zero, this yields just a null pointer. <>= generic :: get_field_ptr => & model_data_get_field_ptr_name, & model_data_get_field_ptr_pdg procedure, private :: model_data_get_field_ptr_name procedure, private :: model_data_get_field_ptr_pdg procedure :: get_field_ptr_by_index => model_data_get_field_ptr_index <>= function model_data_get_field_ptr_name (model, name, check) result (ptr) class(model_data_t), intent(in), target :: model type(string_t), intent(in) :: name logical, intent(in), optional :: check type(field_data_t), pointer :: ptr integer :: i do i = 1, size (model%field) if (model%field(i)%matches_name (name, .false.)) then ptr => model%field(i) return else if (model%field(i)%matches_name (name, .true.)) then ptr => model%field(i) return end if end do ptr => null () call model%field_error (check, name) end function model_data_get_field_ptr_name function model_data_get_field_ptr_pdg (model, pdg, check) result (ptr) class(model_data_t), intent(in), target :: model integer, intent(in) :: pdg logical, intent(in), optional :: check type(field_data_t), pointer :: ptr integer :: i, pdg_abs if (pdg == 0) then ptr => null () return end if pdg_abs = abs (pdg) do i = 1, size (model%field) if (abs(model%field(i)%get_pdg ()) == pdg_abs) then ptr => model%field(i) return end if end do ptr => null () call model%field_error (check, pdg=pdg) end function model_data_get_field_ptr_pdg function model_data_get_field_ptr_index (model, i) result (ptr) class(model_data_t), intent(in), target :: model integer, intent(in) :: i type(field_data_t), pointer :: ptr ptr => model%field(i) end function model_data_get_field_ptr_index @ %def model_data_get_field_ptr @ Don't assign a pointer, just check. <>= procedure :: test_field => model_data_test_field_pdg <>= function model_data_test_field_pdg (model, pdg, check) result (exist) class(model_data_t), intent(in), target :: model integer, intent(in) :: pdg logical, intent(in), optional :: check logical :: exist exist = associated (model%get_field_ptr (pdg, check)) end function model_data_test_field_pdg @ %def model_data_test_field_pdg @ Error message, if [[check]] is set. <>= procedure :: field_error => model_data_field_error <>= subroutine model_data_field_error (model, check, name, pdg) class(model_data_t), intent(in) :: model logical, intent(in), optional :: check type(string_t), intent(in), optional :: name integer, intent(in), optional :: pdg if (present (check)) then if (check) then if (present (name)) then write (msg_buffer, "(A,1x,A,1x,A,1x,A)") & "No particle with name", char (name), & "is contained in model", char (model%name) else if (present (pdg)) then write (msg_buffer, "(A,1x,I0,1x,A,1x,A)") & "No particle with PDG code", pdg, & "is contained in model", char (model%name) else write (msg_buffer, "(A,1x,A,1x,A)") & "Particle missing", & "in model", char (model%name) end if call msg_fatal () end if end if end subroutine model_data_field_error @ %def model_data_field_error @ Assign mass and width value, which are associated via pointer. Identify the particle via pdg. <>= procedure :: set_field_mass => model_data_set_field_mass_pdg procedure :: set_field_width => model_data_set_field_width_pdg <>= subroutine model_data_set_field_mass_pdg (model, pdg, value) class(model_data_t), intent(inout) :: model integer, intent(in) :: pdg real(default), intent(in) :: value type(field_data_t), pointer :: field field => model%get_field_ptr (pdg, check = .true.) call field%set_mass (value) end subroutine model_data_set_field_mass_pdg subroutine model_data_set_field_width_pdg (model, pdg, value) class(model_data_t), intent(inout) :: model integer, intent(in) :: pdg real(default), intent(in) :: value type(field_data_t), pointer :: field field => model%get_field_ptr (pdg, check = .true.) call field%set_width (value) end subroutine model_data_set_field_width_pdg @ %def model_data_set_field_mass @ %def model_data_set_field_width @ Mark a particle as unstable and provide a list of names for its decay processes. In contrast with the previous subroutine which is for internal use, we address the particle by its PDG code. If the index is negative, we address the antiparticle. <>= procedure :: set_unstable => model_data_set_unstable procedure :: set_stable => model_data_set_stable <>= subroutine model_data_set_unstable & (model, pdg, decay, isotropic, diagonal, decay_helicity) class(model_data_t), intent(inout), target :: model integer, intent(in) :: pdg type(string_t), dimension(:), intent(in) :: decay logical, intent(in), optional :: isotropic, diagonal integer, intent(in), optional :: decay_helicity type(field_data_t), pointer :: field field => model%get_field_ptr (pdg) if (pdg > 0) then call field%set ( & p_is_stable = .false., p_decay = decay, & p_decays_isotropically = isotropic, & p_decays_diagonal = diagonal, & p_decay_helicity = decay_helicity) else call field%set ( & a_is_stable = .false., a_decay = decay, & a_decays_isotropically = isotropic, & a_decays_diagonal = diagonal, & a_decay_helicity = decay_helicity) end if end subroutine model_data_set_unstable subroutine model_data_set_stable (model, pdg) class(model_data_t), intent(inout), target :: model integer, intent(in) :: pdg type(field_data_t), pointer :: field field => model%get_field_ptr (pdg) if (pdg > 0) then call field%set (p_is_stable = .true.) else call field%set (a_is_stable = .true.) end if end subroutine model_data_set_stable @ %def model_data_set_unstable @ %def model_data_set_stable @ Mark a particle as polarized. <>= procedure :: set_polarized => model_data_set_polarized procedure :: set_unpolarized => model_data_set_unpolarized <>= subroutine model_data_set_polarized (model, pdg) class(model_data_t), intent(inout), target :: model integer, intent(in) :: pdg type(field_data_t), pointer :: field field => model%get_field_ptr (pdg) if (pdg > 0) then call field%set (p_polarized = .true.) else call field%set (a_polarized = .true.) end if end subroutine model_data_set_polarized subroutine model_data_set_unpolarized (model, pdg) class(model_data_t), intent(inout), target :: model integer, intent(in) :: pdg type(field_data_t), pointer :: field field => model%get_field_ptr (pdg) if (pdg > 0) then call field%set (p_polarized = .false.) else call field%set (a_polarized = .false.) end if end subroutine model_data_set_unpolarized @ %def model_data_set_polarized @ %def model_data_set_unpolarized @ Revert all polarized (unstable) particles to unpolarized (stable) status, respectively. <>= procedure :: clear_unstable => model_clear_unstable procedure :: clear_polarized => model_clear_polarized <>= subroutine model_clear_unstable (model) class(model_data_t), intent(inout), target :: model integer :: i type(field_data_t), pointer :: field do i = 1, model%get_n_field () field => model%get_field_ptr_by_index (i) call field%set (p_is_stable = .true.) if (field%has_antiparticle ()) then call field%set (a_is_stable = .true.) end if end do end subroutine model_clear_unstable subroutine model_clear_polarized (model) class(model_data_t), intent(inout), target :: model integer :: i type(field_data_t), pointer :: field do i = 1, model%get_n_field () field => model%get_field_ptr_by_index (i) call field%set (p_polarized = .false.) if (field%has_antiparticle ()) then call field%set (a_polarized = .false.) end if end do end subroutine model_clear_polarized @ %def model_clear_unstable @ %def model_clear_polarized @ List all vertices, optionally also the hash table. <>= procedure :: write_vertices => model_data_write_vertices <>= subroutine model_data_write_vertices (model, unit, verbose) class(model_data_t), intent(in) :: model integer, intent(in), optional :: unit logical, intent(in), optional :: verbose integer :: i, u u = given_output_unit (unit) do i = 1, size (model%vtx) call vertex_write (model%vtx(i), unit) end do if (present (verbose)) then if (verbose) then write (u, *) call vertex_table_write (model%vt, unit) end if end if end subroutine model_data_write_vertices @ %def model_data_write_vertices @ Vertex definition. <>= generic :: set_vertex => & model_data_set_vertex_pdg, model_data_set_vertex_names procedure, private :: model_data_set_vertex_pdg procedure, private :: model_data_set_vertex_names <>= subroutine model_data_set_vertex_pdg (model, i, pdg) class(model_data_t), intent(inout), target :: model integer, intent(in) :: i integer, dimension(:), intent(in) :: pdg call vertex_init (model%vtx(i), pdg, model) end subroutine model_data_set_vertex_pdg subroutine model_data_set_vertex_names (model, i, name) class(model_data_t), intent(inout), target :: model integer, intent(in) :: i type(string_t), dimension(:), intent(in) :: name integer, dimension(size(name)) :: pdg integer :: j do j = 1, size (name) pdg(j) = model%get_pdg (name(j)) end do call model%set_vertex (i, pdg) end subroutine model_data_set_vertex_names @ %def model_data_set_vertex @ Finalize vertex definition: set up the hash table. <>= procedure :: freeze_vertices => model_data_freeze_vertices <>= subroutine model_data_freeze_vertices (model) class(model_data_t), intent(inout) :: model call model%vt%init (model%field, model%vtx) end subroutine model_data_freeze_vertices @ %def model_data_freeze_vertices @ Number of vertices in model <>= procedure :: get_n_vtx => model_data_get_n_vtx <>= function model_data_get_n_vtx (model) result (n) class(model_data_t), intent(in) :: model integer :: n n = size (model%vtx) end function model_data_get_n_vtx @ %def model_data_get_n_vtx @ Lookup functions <>= procedure :: match_vertex => model_data_match_vertex <>= subroutine model_data_match_vertex (model, pdg1, pdg2, pdg3) class(model_data_t), intent(in) :: model integer, intent(in) :: pdg1, pdg2 integer, dimension(:), allocatable, intent(out) :: pdg3 call model%vt%match (pdg1, pdg2, pdg3) end subroutine model_data_match_vertex @ %def model_data_match_vertex <>= procedure :: check_vertex => model_data_check_vertex <>= function model_data_check_vertex (model, pdg1, pdg2, pdg3) result (flag) logical :: flag class(model_data_t), intent(in) :: model integer, intent(in) :: pdg1, pdg2, pdg3 flag = model%vt%check (pdg1, pdg2, pdg3) end function model_data_check_vertex @ %def model_data_check_vertex @ \subsection{Toy Models} This is a stripped-down version of the (already trivial) model 'Test'. <>= procedure :: init_test => model_data_init_test <>= subroutine model_data_init_test (model) class(model_data_t), intent(out) :: model type(field_data_t), pointer :: field integer, parameter :: n_real = 4 integer, parameter :: n_field = 2 integer, parameter :: n_vertex = 2 integer :: i call model%init (var_str ("Test"), & n_real, 0, n_field, n_vertex) i = 0 i = i + 1 call model%init_par (i, var_str ("gy"), 1._default) i = i + 1 call model%init_par (i, var_str ("ms"), 125._default) i = i + 1 call model%init_par (i, var_str ("ff"), 1.5_default) i = i + 1 call model%init_par (i, var_str ("mf"), 1.5_default * 125._default) i = 0 i = i + 1 field => model%get_field_ptr_by_index (i) call field%init (var_str ("SCALAR"), 25) call field%set (spin_type=1) call field%set (mass_data=model%get_par_real_ptr (2)) call field%set (name = [var_str ("s")]) i = i + 1 field => model%get_field_ptr_by_index (i) call field%init (var_str ("FERMION"), 6) call field%set (spin_type=2) call field%set (mass_data=model%get_par_real_ptr (4)) call field%set (name = [var_str ("f")], anti = [var_str ("fbar")]) call model%freeze_fields () i = 0 i = i + 1 call model%set_vertex (i, [var_str ("fbar"), var_str ("f"), var_str ("s")]) i = i + 1 call model%set_vertex (i, [var_str ("s"), var_str ("s"), var_str ("s")]) call model%freeze_vertices () end subroutine model_data_init_test @ %def model_data_init_test @ This procedure prepares a subset of QED for testing purposes. <>= procedure :: init_qed_test => model_data_init_qed_test <>= subroutine model_data_init_qed_test (model) class(model_data_t), intent(out) :: model type(field_data_t), pointer :: field integer, parameter :: n_real = 1 integer, parameter :: n_field = 2 integer :: i call model%init (var_str ("QED_test"), & n_real, 0, n_field, 0) i = 0 i = i + 1 call model%init_par (i, var_str ("me"), 0.000510997_default) i = 0 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, charge_type=-4) call field%set (mass_data=model%get_par_real_ptr (1)) 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 ("PHOTON"), 22) call field%set (spin_type=3) call field%set (name = [var_str ("A")]) call model%freeze_fields () call model%freeze_vertices () end subroutine model_data_init_qed_test @ %def model_data_init_qed_test @ This procedure prepares a subset of the Standard Model for testing purposes. We can thus avoid dependencies on model I/O, which is not defined here. <>= procedure :: init_sm_test => model_data_init_sm_test <>= subroutine model_data_init_sm_test (model) class(model_data_t), intent(out) :: model type(field_data_t), pointer :: field integer, parameter :: n_real = 11 integer, parameter :: n_field = 19 integer, parameter :: n_vtx = 9 integer :: i call model%init (var_str ("SM_test"), & n_real, 0, n_field, n_vtx) i = 0 i = i + 1 call model%init_par (i, var_str ("mZ"), 91.1882_default) i = i + 1 call model%init_par (i, var_str ("mW"), 80.419_default) i = i + 1 call model%init_par (i, var_str ("me"), 0.000510997_default) i = i + 1 call model%init_par (i, var_str ("mmu"), 0.105658389_default) i = i + 1 call model%init_par (i, var_str ("mb"), 4.2_default) i = i + 1 call model%init_par (i, var_str ("mtop"), 173.1_default) i = i + 1 call model%init_par (i, var_str ("wZ"), 2.443_default) i = i + 1 call model%init_par (i, var_str ("wW"), 2.049_default) i = i + 1 call model%init_par (i, var_str ("ee"), 0.3079561542961_default) i = i + 1 call model%init_par (i, var_str ("cw"), 8.819013863636E-01_default) i = i + 1 call model%init_par (i, 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 (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 (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 (5)) 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 (6)) 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 (3)) 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 (4)) 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 ("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 (7)) 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 (8)) 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 (2)) ! call field%set (width_data=model%get_par_real_ptr (8)) 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 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 ("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 ("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 ("ubar"), var_str ("d"), var_str ("W+")]) i = i + 1 call model%set_vertex (i, [var_str ("dbar"), var_str ("u"), var_str ("W-")]) call model%freeze_vertices () end subroutine model_data_init_sm_test @ %def model_data_init_sm_test @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Model Testbed} The standard way of defining a model uses concrete variables and expressions to interpret the model file. Some of this is not available at the point of use. This is no problem for the \whizard\ program as a whole, but unit tests are kept local to their respective module and don't access all definitions. Instead, we introduce a separate module that provides hooks, one for initializing a model and one for finalizing a model. The main program can assign real routines to the hooks (procedure pointers of abstract type) before unit tests are called. The unit tests can call the abstract routines without knowing about their implementation. <<[[model_testbed.f90]]>>= <> module model_testbed <> use model_data use var_base <> <> <> <> end module model_testbed @ %def model_testbed @ \subsection{Abstract Model Handlers} Both routines take a polymorphic model (data) target, which is not allocated/deallocated inside the subroutine. The model constructor [[prepare_model]] requires the model name as input. It can, optionally, return a link to the variable list of the model. <>= public :: prepare_model public :: cleanup_model <>= procedure (prepare_model_proc), pointer :: prepare_model => null () procedure (cleanup_model_proc), pointer :: cleanup_model => null () <>= abstract interface subroutine prepare_model_proc (model, name, vars) import class(model_data_t), intent(inout), pointer :: model type(string_t), intent(in) :: name class(vars_t), pointer, intent(out), optional :: vars end subroutine prepare_model_proc end interface abstract interface subroutine cleanup_model_proc (model) import class(model_data_t), intent(inout), target :: model end subroutine cleanup_model_proc end interface @ %def prepare_model @ %def cleanup_model @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Helicities} This module defines types and tools for dealing with helicity information. <<[[helicities.f90]]>>= <> module helicities use io_units <> <> <> <> contains <> end module helicities @ %def helicities @ \subsection{Helicity types} Helicities may be defined or undefined, corresponding to a polarized or unpolarized state. Each helicity is actually a pair of helicities, corresponding to an entry in the spin density matrix. Obviously, diagonal entries are distinguished. <>= public :: helicity_t <>= type :: helicity_t private logical :: defined = .false. integer :: h1, h2 contains <> end type helicity_t @ %def helicity_t @ Constructor functions, for convenience: <>= public :: helicity <>= interface helicity module procedure helicity0, helicity1, helicity2 end interface helicity <>= pure function helicity0 () result (hel) type(helicity_t) :: hel end function helicity0 elemental function helicity1 (h) result (hel) type(helicity_t) :: hel integer, intent(in) :: h call hel%init (h) end function helicity1 elemental function helicity2 (h2, h1) result (hel) type(helicity_t) :: hel integer, intent(in) :: h1, h2 call hel%init (h2, h1) end function helicity2 @ %def helicity @ Initializers. Note: conceptually, the argument to initializers should be INTENT(OUT). However, Interp.\ F08/0033 prohibited this. The reason is that, in principle, the call could result in the execution of an impure finalizer for a type extension of [[hel]] (ugh). <>= generic :: init => helicity_init_empty, helicity_init_same, helicity_init_different procedure, private :: helicity_init_empty procedure, private :: helicity_init_same procedure, private :: helicity_init_different <>= elemental subroutine helicity_init_empty (hel) class(helicity_t), intent(inout) :: hel hel%defined = .false. end subroutine helicity_init_empty elemental subroutine helicity_init_same (hel, h) class(helicity_t), intent(inout) :: hel integer, intent(in) :: h hel%defined = .true. hel%h1 = h hel%h2 = h end subroutine helicity_init_same elemental subroutine helicity_init_different (hel, h2, h1) class(helicity_t), intent(inout) :: hel integer, intent(in) :: h1, h2 hel%defined = .true. hel%h2 = h2 hel%h1 = h1 end subroutine helicity_init_different @ %def helicity_init @ Undefine: <>= procedure :: undefine => helicity_undefine <>= elemental subroutine helicity_undefine (hel) class(helicity_t), intent(inout) :: hel hel%defined = .false. end subroutine helicity_undefine @ %def helicity_undefine @ Diagonalize by removing the second entry (use with care!) <>= procedure :: diagonalize => helicity_diagonalize <>= elemental subroutine helicity_diagonalize (hel) class(helicity_t), intent(inout) :: hel hel%h2 = hel%h1 end subroutine helicity_diagonalize @ %def helicity_diagonalize @ Flip helicity indices by sign. <>= procedure :: flip => helicity_flip <>= elemental subroutine helicity_flip (hel) class(helicity_t), intent(inout) :: hel hel%h1 = - hel%h1 hel%h2 = - hel%h2 end subroutine helicity_flip @ %def helicity_flip @ <>= procedure :: get_indices => helicity_get_indices <>= subroutine helicity_get_indices (hel, h1, h2) class(helicity_t), intent(in) :: hel integer, intent(out) :: h1, h2 h1 = hel%h1; h2 = hel%h2 end subroutine helicity_get_indices @ %def helicity_get_indices @ Output (no linebreak). No output if undefined. <>= procedure :: write => helicity_write <>= subroutine helicity_write (hel, unit) class(helicity_t), intent(in) :: hel integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit); if (u < 0) return if (hel%defined) then write (u, "(A)", advance="no") "h(" write (u, "(I0)", advance="no") hel%h1 if (hel%h1 /= hel%h2) then write (u, "(A)", advance="no") "|" write (u, "(I0)", advance="no") hel%h2 end if write (u, "(A)", advance="no") ")" end if end subroutine helicity_write @ %def helicity_write @ Binary I/O. Write contents only if defined. <>= procedure :: write_raw => helicity_write_raw procedure :: read_raw => helicity_read_raw <>= subroutine helicity_write_raw (hel, u) class(helicity_t), intent(in) :: hel integer, intent(in) :: u write (u) hel%defined if (hel%defined) then write (u) hel%h1, hel%h2 end if end subroutine helicity_write_raw subroutine helicity_read_raw (hel, u, iostat) class(helicity_t), intent(out) :: hel integer, intent(in) :: u integer, intent(out), optional :: iostat read (u, iostat=iostat) hel%defined if (hel%defined) then read (u, iostat=iostat) hel%h1, hel%h2 end if end subroutine helicity_read_raw @ %def helicity_write_raw helicity_read_raw @ \subsection{Predicates} Check if the helicity is defined: <>= procedure :: is_defined => helicity_is_defined <>= elemental function helicity_is_defined (hel) result (defined) logical :: defined class(helicity_t), intent(in) :: hel defined = hel%defined end function helicity_is_defined @ %def helicity_is_defined @ Return true if the two helicities are equal or the particle is unpolarized: <>= procedure :: is_diagonal => helicity_is_diagonal <>= elemental function helicity_is_diagonal (hel) result (diagonal) logical :: diagonal class(helicity_t), intent(in) :: hel if (hel%defined) then diagonal = hel%h1 == hel%h2 else diagonal = .true. end if end function helicity_is_diagonal @ %def helicity_is_diagonal @ \subsection{Accessing contents} This returns a two-element array and thus cannot be elemental. The result is unpredictable if the helicity is undefined. <>= procedure :: to_pair => helicity_to_pair <>= pure function helicity_to_pair (hel) result (h) integer, dimension(2) :: h class(helicity_t), intent(in) :: hel h(1) = hel%h2 h(2) = hel%h1 end function helicity_to_pair @ %def helicity_to_pair @ \subsection{Comparisons} When comparing helicities, if either one is undefined, they are considered to match. In other words, an unpolarized particle matches any polarization. In the [[dmatch]] variant, it matches only diagonal helicity. <>= generic :: operator(.match.) => helicity_match generic :: operator(.dmatch.) => helicity_match_diagonal generic :: operator(==) => helicity_eq generic :: operator(/=) => helicity_neq procedure, private :: helicity_match procedure, private :: helicity_match_diagonal procedure, private :: helicity_eq procedure, private :: helicity_neq @ %def .match. .dmatch. == /= <>= elemental function helicity_match (hel1, hel2) result (eq) logical :: eq class(helicity_t), intent(in) :: hel1, hel2 if (hel1%defined .and. hel2%defined) then eq = (hel1%h1 == hel2%h1) .and. (hel1%h2 == hel2%h2) else eq = .true. end if end function helicity_match elemental function helicity_match_diagonal (hel1, hel2) result (eq) logical :: eq class(helicity_t), intent(in) :: hel1, hel2 if (hel1%defined .and. hel2%defined) then eq = (hel1%h1 == hel2%h1) .and. (hel1%h2 == hel2%h2) else if (hel1%defined) then eq = hel1%h1 == hel1%h2 else if (hel2%defined) then eq = hel2%h1 == hel2%h2 else eq = .true. end if end function helicity_match_diagonal @ %def helicity_match helicity_match_diagonal <>= elemental function helicity_eq (hel1, hel2) result (eq) logical :: eq class(helicity_t), intent(in) :: hel1, hel2 if (hel1%defined .and. hel2%defined) then eq = (hel1%h1 == hel2%h1) .and. (hel1%h2 == hel2%h2) else if (.not. hel1%defined .and. .not. hel2%defined) then eq = .true. else eq = .false. end if end function helicity_eq @ %def helicity_eq <>= elemental function helicity_neq (hel1, hel2) result (neq) logical :: neq class(helicity_t), intent(in) :: hel1, hel2 if (hel1%defined .and. hel2%defined) then neq = (hel1%h1 /= hel2%h1) .or. (hel1%h2 /= hel2%h2) else if (.not. hel1%defined .and. .not. hel2%defined) then neq = .false. else neq = .true. end if end function helicity_neq @ %def helicity_neq @ \subsection{Tools} Merge two helicity objects by taking the first entry from the first and the second entry from the second argument. Makes sense only if the input helicities were defined and diagonal. The handling of ghost flags is not well-defined; one should verify beforehand that they match. <>= generic :: operator(.merge.) => merge_helicities procedure, private :: merge_helicities @ %def .merge. <>= elemental function merge_helicities (hel1, hel2) result (hel) type(helicity_t) :: hel class(helicity_t), intent(in) :: hel1, hel2 if (hel1%defined .and. hel2%defined) then call hel%init (hel2%h1, hel1%h1) else if (hel1%defined) then call hel%init (hel1%h2, hel1%h1) else if (hel2%defined) then call hel%init (hel2%h2, hel2%h1) end if end function merge_helicities @ %def merge_helicities @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Colors} This module defines a type and tools for dealing with color information. Each particle can have zero or more (in practice, usually not more than two) color indices. Color indices are positive; flow direction can be determined from the particle nature. While parton shower matrix elements are diagonal in color, some special applications (e.g., subtractions for NLO matrix elements) require non-diagonal color matrices. <<[[colors.f90]]>>= <> module colors <> <> use io_units use diagnostics <> <> <> <> contains <> end module colors @ %def colors @ \subsection{The color type} A particle may have an arbitrary number of color indices (in practice, from zero to two, but more are possible). This object acts as a container. (The current implementation has a fixed array of length two.) The fact that color comes as an array prohibits elemental procedures in some places. (May add interfaces and multi versions where necessary.) The color may be undefined. NOTE: Due to a compiler bug in nagfor 5.2, we do not use allocatable but fixed-size arrays with dimension 2. Only nonzero entries count. This may be more efficient anyway, but gives up some flexibility. However, the squaring algorithm currently works only for singlets, (anti)triplets and octets anyway, so two components are enough. This type has to be generalized (abstract type and specific implementations) when trying to pursue generalized color flows or Monte Carlo over continuous color. <>= public :: color_t <>= type :: color_t private logical :: defined = .false. integer, dimension(2) :: c1 = 0, c2 = 0 logical :: ghost = .false. contains <> end type color_t @ %def color_t @ Initializers: <>= generic :: init => & color_init_trivial, color_init_trivial_ghost, & color_init_array, color_init_array_ghost, & color_init_arrays, color_init_arrays_ghost procedure, private :: color_init_trivial procedure, private :: color_init_trivial_ghost procedure, private :: color_init_array procedure, private :: color_init_array_ghost procedure, private :: color_init_arrays procedure, private :: color_init_arrays_ghost @ Undefined color: array remains unallocated <>= pure subroutine color_init_trivial (col) class(color_t), intent(inout) :: col col%defined = .true. col%c1 = 0 col%c2 = 0 col%ghost = .false. end subroutine color_init_trivial pure subroutine color_init_trivial_ghost (col, ghost) class(color_t), intent(inout) :: col logical, intent(in) :: ghost col%defined = .true. col%c1 = 0 col%c2 = 0 col%ghost = ghost end subroutine color_init_trivial_ghost @ This defines color from an arbitrary length color array, suitable for any representation. We may have two color arrays (non-diagonal matrix elements). This cannot be elemental. The third version assigns an array of colors, using a two-dimensional array as input. <>= pure subroutine color_init_array (col, c1) class(color_t), intent(inout) :: col integer, dimension(:), intent(in) :: c1 col%defined = .true. col%c1 = pack (c1, c1 /= 0, [0,0]) col%c2 = col%c1 col%ghost = .false. end subroutine color_init_array pure subroutine color_init_array_ghost (col, c1, ghost) class(color_t), intent(inout) :: col integer, dimension(:), intent(in) :: c1 logical, intent(in) :: ghost call color_init_array (col, c1) col%ghost = ghost end subroutine color_init_array_ghost pure subroutine color_init_arrays (col, c1, c2) class(color_t), intent(inout) :: col integer, dimension(:), intent(in) :: c1, c2 col%defined = .true. if (size (c1) == size (c2)) then col%c1 = pack (c1, c1 /= 0, [0,0]) col%c2 = pack (c2, c2 /= 0, [0,0]) else if (size (c1) /= 0) then col%c1 = pack (c1, c1 /= 0, [0,0]) col%c2 = col%c1 else if (size (c2) /= 0) then col%c1 = pack (c2, c2 /= 0, [0,0]) col%c2 = col%c1 end if col%ghost = .false. end subroutine color_init_arrays pure subroutine color_init_arrays_ghost (col, c1, c2, ghost) class(color_t), intent(inout) :: col integer, dimension(:), intent(in) :: c1, c2 logical, intent(in) :: ghost call color_init_arrays (col, c1, c2) col%ghost = ghost end subroutine color_init_arrays_ghost @ %def color_init @ This version is restricted to singlets, triplets, antitriplets, and octets: The input contains the color and anticolor index, each of the may be zero. <>= procedure :: init_col_acl => color_init_col_acl <>= elemental subroutine color_init_col_acl (col, col_in, acl_in) class(color_t), intent(inout) :: col integer, intent(in) :: col_in, acl_in integer, dimension(0) :: null_array select case (col_in) case (0) select case (acl_in) case (0) call color_init_array (col, null_array) case default call color_init_array (col, [-acl_in]) end select case default select case (acl_in) case (0) call color_init_array (col, [col_in]) case default call color_init_array (col, [col_in, -acl_in]) end select end select end subroutine color_init_col_acl @ %def color_init_col_acl @ This version is used for the external interface. We convert a fixed-size array of colors (for each particle) to the internal form by packing only the nonzero entries. Some of these procedures produce an arry, so they can't be all type-bound. We implement them as ordinary procedures. <>= public :: color_init_from_array <>= interface color_init_from_array module procedure color_init_from_array1 module procedure color_init_from_array1g module procedure color_init_from_array2 module procedure color_init_from_array2g end interface color_init_from_array @ %def color_init_from_array <>= pure subroutine color_init_from_array1 (col, c1) type(color_t), intent(inout) :: col integer, dimension(:), intent(in) :: c1 logical, dimension(size(c1)) :: mask mask = c1 /= 0 col%defined = .true. col%c1 = pack (c1, mask, col%c1) col%c2 = col%c1 col%ghost = .false. end subroutine color_init_from_array1 pure subroutine color_init_from_array1g (col, c1, ghost) type(color_t), intent(inout) :: col integer, dimension(:), intent(in) :: c1 logical, intent(in) :: ghost call color_init_from_array1 (col, c1) col%ghost = ghost end subroutine color_init_from_array1g pure subroutine color_init_from_array2 (col, c1) integer, dimension(:,:), intent(in) :: c1 type(color_t), dimension(:), intent(inout) :: col integer :: i do i = 1, size (c1,2) call color_init_from_array1 (col(i), c1(:,i)) end do end subroutine color_init_from_array2 pure subroutine color_init_from_array2g (col, c1, ghost) integer, dimension(:,:), intent(in) :: c1 type(color_t), dimension(:), intent(out) :: col logical, intent(in), dimension(:) :: ghost call color_init_from_array2 (col, c1) col%ghost = ghost end subroutine color_init_from_array2g @ %def color_init_from_array @ Set the ghost property <>= procedure :: set_ghost => color_set_ghost <>= elemental subroutine color_set_ghost (col, ghost) class(color_t), intent(inout) :: col logical, intent(in) :: ghost col%ghost = ghost end subroutine color_set_ghost @ %def color_set_ghost @ Undefine the color state: <>= procedure :: undefine => color_undefine <>= elemental subroutine color_undefine (col, undefine_ghost) class(color_t), intent(inout) :: col logical, intent(in), optional :: undefine_ghost col%defined = .false. if (present (undefine_ghost)) then if (undefine_ghost) col%ghost = .false. else col%ghost = .false. end if end subroutine color_undefine @ %def color_undefine @ Output. As dense as possible, no linebreak. If color is undefined, no output. The separate version for a color array suggest two distinct interfaces. <>= public :: color_write <>= interface color_write module procedure color_write_single module procedure color_write_array end interface color_write <>= procedure :: write => color_write_single <>= subroutine color_write_single (col, unit) class(color_t), intent(in) :: col integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit); if (u < 0) return if (col%ghost) then write (u, "(A)", advance="no") "c*" else if (col%defined) then write (u, "(A)", advance="no") "c(" if (col%c1(1) /= 0) write (u, "(I0)", advance="no") col%c1(1) if (any (col%c1 /= 0)) write (u, "(1x)", advance="no") if (col%c1(2) /= 0) write (u, "(I0)", advance="no") col%c1(2) if (.not. col%is_diagonal ()) then write (u, "(A)", advance="no") "|" if (col%c2(1) /= 0) write (u, "(I0)", advance="no") col%c2(1) if (any (col%c2 /= 0)) write (u, "(1x)", advance="no") if (col%c2(2) /= 0) write (u, "(I0)", advance="no") col%c2(2) end if write (u, "(A)", advance="no") ")" end if end subroutine color_write_single subroutine color_write_array (col, unit) type(color_t), dimension(:), intent(in) :: col integer, intent(in), optional :: unit integer :: u integer :: i u = given_output_unit (unit); if (u < 0) return write (u, "(A)", advance="no") "[" do i = 1, size (col) if (i > 1) write (u, "(1x)", advance="no") call color_write_single (col(i), u) end do write (u, "(A)", advance="no") "]" end subroutine color_write_array @ %def color_write @ Binary I/O. For allocatable colors, this would have to be modified. <>= procedure :: write_raw => color_write_raw procedure :: read_raw => color_read_raw <>= subroutine color_write_raw (col, u) class(color_t), intent(in) :: col integer, intent(in) :: u logical :: defined defined = col%is_defined () .or. col%is_ghost () write (u) defined if (defined) then write (u) col%c1, col%c2 write (u) col%ghost end if end subroutine color_write_raw subroutine color_read_raw (col, u, iostat) class(color_t), intent(inout) :: col integer, intent(in) :: u integer, intent(out), optional :: iostat logical :: defined read (u, iostat=iostat) col%defined if (col%defined) then read (u, iostat=iostat) col%c1, col%c2 read (u, iostat=iostat) col%ghost end if end subroutine color_read_raw @ %def color_write_raw color_read_raw @ \subsection{Predicates} Return the definition status. A color state may be defined but trivial. <>= procedure :: is_defined => color_is_defined procedure :: is_nonzero => color_is_nonzero <>= elemental function color_is_defined (col) result (defined) logical :: defined class(color_t), intent(in) :: col defined = col%defined end function color_is_defined elemental function color_is_nonzero (col) result (flag) logical :: flag class(color_t), intent(in) :: col flag = col%defined & .and. .not. col%ghost & .and. any (col%c1 /= 0 .or. col%c2 /= 0) end function color_is_nonzero @ %def color_is_defined @ %def color_is_nonzero @ Diagonal color objects have only one array allocated: <>= procedure :: is_diagonal => color_is_diagonal <>= elemental function color_is_diagonal (col) result (diagonal) logical :: diagonal class(color_t), intent(in) :: col if (col%defined) then diagonal = all (col%c1 == col%c2) else diagonal = .true. end if end function color_is_diagonal @ %def color_is_diagonal @ Return the ghost flag <>= procedure :: is_ghost => color_is_ghost <>= elemental function color_is_ghost (col) result (ghost) logical :: ghost class(color_t), intent(in) :: col ghost = col%ghost end function color_is_ghost @ %def color_is_ghost @ The ghost parity: true if the color-ghost flag is set. Again, no TBP since this is an array. <>= pure function color_ghost_parity (col) result (parity) type(color_t), dimension(:), intent(in) :: col logical :: parity parity = mod (count (col%ghost), 2) == 1 end function color_ghost_parity @ %def color_ghost_parity @ Determine the color representation, given a color object. We allow only singlet ($1$), (anti)triplet ($\pm 3$), and octet states ($8$). A color ghost must not have color assigned, but the color type is $8$. For non-diagonal color, representations must match. If the color type is undefined, return $0$. If it is invalid or unsupported, return $-1$. Assumption: nonzero entries precede nonzero ones. <>= procedure :: get_type => color_get_type <>= elemental function color_get_type (col) result (ctype) class(color_t), intent(in) :: col integer :: ctype if (col%defined) then ctype = -1 if (col%ghost) then if (all (col%c1 == 0 .and. col%c2 == 0)) then ctype = 8 end if else if (all ((col%c1 == 0 .and. col%c2 == 0) & & .or. (col%c1 > 0 .and. col%c2 > 0) & & .or. (col%c1 < 0 .and. col%c2 < 0))) then if (all (col%c1 == 0)) then ctype = 1 else if ((col%c1(1) > 0 .and. col%c1(2) == 0)) then ctype = 3 else if ((col%c1(1) < 0 .and. col%c1(2) == 0)) then ctype = -3 else if ((col%c1(1) > 0 .and. col%c1(2) < 0) & .or.(col%c1(1) < 0 .and. col%c1(2) > 0)) then ctype = 8 end if end if end if else ctype = 0 end if end function color_get_type @ %def color_get_type @ \subsection{Accessing contents} Return the number of color indices. We assume that it is identical for both arrays. <>= procedure, private :: get_number_of_indices => color_get_number_of_indices <>= elemental function color_get_number_of_indices (col) result (n) integer :: n class(color_t), intent(in) :: col if (col%defined .and. .not. col%ghost) then n = count (col%c1 /= 0) else n = 0 end if end function color_get_number_of_indices @ %def color_get_number_of_indices @ Return the (first) color/anticolor entry (assuming that color is diagonal). The result is a positive color index. <>= procedure :: get_col => color_get_col procedure :: get_acl => color_get_acl <>= elemental function color_get_col (col) result (c) integer :: c class(color_t), intent(in) :: col integer :: i if (col%defined .and. .not. col%ghost) then do i = 1, size (col%c1) if (col%c1(i) > 0) then c = col%c1(i) return end if end do end if c = 0 end function color_get_col elemental function color_get_acl (col) result (c) integer :: c class(color_t), intent(in) :: col integer :: i if (col%defined .and. .not. col%ghost) then do i = 1, size (col%c1) if (col%c1(i) < 0) then c = - col%c1(i) return end if end do end if c = 0 end function color_get_acl @ %def color_get_col color_get_acl @ Return the color index with highest absolute value <>= public :: color_get_max_value <>= interface color_get_max_value module procedure color_get_max_value0 module procedure color_get_max_value1 module procedure color_get_max_value2 end interface color_get_max_value <>= elemental function color_get_max_value0 (col) result (cmax) integer :: cmax type(color_t), intent(in) :: col if (col%defined .and. .not. col%ghost) then cmax = maxval (abs (col%c1)) else cmax = 0 end if end function color_get_max_value0 pure function color_get_max_value1 (col) result (cmax) integer :: cmax type(color_t), dimension(:), intent(in) :: col cmax = maxval (color_get_max_value0 (col)) end function color_get_max_value1 pure function color_get_max_value2 (col) result (cmax) integer :: cmax type(color_t), dimension(:,:), intent(in) :: col integer, dimension(size(col, 2)) :: cm integer :: i forall (i = 1:size(col, 2)) cm(i) = color_get_max_value1 (col(:,i)) end forall cmax = maxval (cm) end function color_get_max_value2 @ %def color_get_max_value @ \subsection{Comparisons} Similar to helicities, colors match if they are equal, or if either one is undefined. <>= generic :: operator(.match.) => color_match generic :: operator(==) => color_eq generic :: operator(/=) => color_neq procedure, private :: color_match procedure, private :: color_eq procedure, private :: color_neq @ %def .match. == /= <>= elemental function color_match (col1, col2) result (eq) logical :: eq class(color_t), intent(in) :: col1, col2 if (col1%defined .and. col2%defined) then if (col1%ghost .and. col2%ghost) then eq = .true. else if (.not. col1%ghost .and. .not. col2%ghost) then eq = all (col1%c1 == col2%c1) .and. all (col1%c2 == col2%c2) else eq = .false. end if else eq = .true. end if end function color_match elemental function color_eq (col1, col2) result (eq) logical :: eq class(color_t), intent(in) :: col1, col2 if (col1%defined .and. col2%defined) then if (col1%ghost .and. col2%ghost) then eq = .true. else if (.not. col1%ghost .and. .not. col2%ghost) then eq = all (col1%c1 == col2%c1) .and. all (col1%c2 == col2%c2) else eq = .false. end if else if (.not. col1%defined & .and. .not. col2%defined) then eq = col1%ghost .eqv. col2%ghost else eq = .false. end if end function color_eq @ %def color_eq <>= elemental function color_neq (col1, col2) result (neq) logical :: neq class(color_t), intent(in) :: col1, col2 if (col1%defined .and. col2%defined) then if (col1%ghost .and. col2%ghost) then neq = .false. else if (.not. col1%ghost .and. .not. col2%ghost) then neq = any (col1%c1 /= col2%c1) .or. any (col1%c2 /= col2%c2) else neq = .true. end if else if (.not. col1%defined & .and. .not. col2%defined) then neq = col1%ghost .neqv. col2%ghost else neq = .true. end if end function color_neq @ %def color_neq @ \subsection{Tools} Shift color indices by a common offset. <>= procedure :: add_offset => color_add_offset <>= elemental subroutine color_add_offset (col, offset) class(color_t), intent(inout) :: col integer, intent(in) :: offset if (col%defined .and. .not. col%ghost) then where (col%c1 /= 0) col%c1 = col%c1 + sign (offset, col%c1) where (col%c2 /= 0) col%c2 = col%c2 + sign (offset, col%c2) end if end subroutine color_add_offset @ %def color_add_offset @ Reassign color indices for an array of colored particle in canonical order. The allocated size of the color map is such that two colors per particle can be accomodated. The algorithm works directly on the contents of the color objects, it <>= public :: color_canonicalize <>= subroutine color_canonicalize (col) type(color_t), dimension(:), intent(inout) :: col integer, dimension(2*size(col)) :: map integer :: n_col, i, j, k n_col = 0 do i = 1, size (col) if (col(i)%defined .and. .not. col(i)%ghost) then do j = 1, size (col(i)%c1) if (col(i)%c1(j) /= 0) then k = find (abs (col(i)%c1(j)), map(:n_col)) if (k == 0) then n_col = n_col + 1 map(n_col) = abs (col(i)%c1(j)) k = n_col end if col(i)%c1(j) = sign (k, col(i)%c1(j)) end if if (col(i)%c2(j) /= 0) then k = find (abs (col(i)%c2(j)), map(:n_col)) if (k == 0) then n_col = n_col + 1 map(n_col) = abs (col(i)%c2(j)) k = n_col end if col(i)%c2(j) = sign (k, col(i)%c2(j)) end if end do end if end do contains function find (c, array) result (k) integer :: k integer, intent(in) :: c integer, dimension(:), intent(in) :: array integer :: i k = 0 do i = 1, size (array) if (c == array (i)) then k = i return end if end do end function find end subroutine color_canonicalize @ %def color_canonicalize @ Return an array of different color indices from an array of colors. The last argument is a pseudo-color array, where the color entries correspond to the position of the corresponding index entry in the index array. The colors are assumed to be diagonal. The algorithm works directly on the contents of the color objects. <>= subroutine extract_color_line_indices (col, c_index, col_pos) type(color_t), dimension(:), intent(in) :: col integer, dimension(:), intent(out), allocatable :: c_index type(color_t), dimension(size(col)), intent(out) :: col_pos integer, dimension(:), allocatable :: c_tmp integer :: i, j, k, n, c allocate (c_tmp (sum (col%get_number_of_indices ())), source=0) n = 0 SCAN1: do i = 1, size (col) if (col(i)%defined .and. .not. col(i)%ghost) then SCAN2: do j = 1, 2 c = abs (col(i)%c1(j)) if (c /= 0) then do k = 1, n if (c_tmp(k) == c) then col_pos(i)%c1(j) = k cycle SCAN2 end if end do n = n + 1 c_tmp(n) = c col_pos(i)%c1(j) = n end if end do SCAN2 end if end do SCAN1 allocate (c_index (n)) c_index = c_tmp(1:n) end subroutine extract_color_line_indices @ %def extract_color_line_indices @ Given a color array, pairwise contract the color lines in all possible ways and return the resulting array of arrays. The input color array must be diagonal, and each color should occur exactly twice, once as color and once as anticolor. Gluon entries with equal color and anticolor are explicitly excluded. This algorithm is generic, but for long arrays it is neither efficient, nor does it avoid duplicates. It is intended for small arrays, in particular for the state matrix of a structure-function pair. The algorithm works directly on the contents of the color objects, it thus depends on the implementation. <>= public :: color_array_make_contractions <>= subroutine color_array_make_contractions (col_in, col_out) type(color_t), dimension(:), intent(in) :: col_in type(color_t), dimension(:,:), intent(out), allocatable :: col_out type :: entry_t integer, dimension(:), allocatable :: map type(color_t), dimension(:), allocatable :: col type(entry_t), pointer :: next => null () logical :: nlo_event = .false. end type entry_t type :: list_t integer :: n = 0 type(entry_t), pointer :: first => null () type(entry_t), pointer :: last => null () end type list_t type(list_t) :: list type(entry_t), pointer :: entry integer, dimension(:), allocatable :: c_index type(color_t), dimension(size(col_in)) :: col_pos integer :: n_prt, n_c_index integer, dimension(:), allocatable :: map integer :: i, j, c n_prt = size (col_in) call extract_color_line_indices (col_in, c_index, col_pos) n_c_index = size (c_index) allocate (map (n_c_index)) map = 0 call list_append_if_valid (list, map) entry => list%first do while (associated (entry)) do i = 1, n_c_index if (entry%map(i) == 0) then c = c_index(i) do j = i + 1, n_c_index if (entry%map(j) == 0) then map = entry%map map(i) = c map(j) = c call list_append_if_valid (list, map) end if end do end if end do entry => entry%next end do call list_to_array (list, col_out) contains subroutine list_append_if_valid (list, map) type(list_t), intent(inout) :: list integer, dimension(:), intent(in) :: map type(entry_t), pointer :: entry integer :: i, j, c, p entry => list%first do while (associated (entry)) if (all (map == entry%map)) return entry => entry%next end do allocate (entry) allocate (entry%map (n_c_index)) entry%map = map allocate (entry%col (n_prt)) do i = 1, n_prt do j = 1, 2 c = col_in(i)%c1(j) if (c /= 0) then p = col_pos(i)%c1(j) entry%col(i)%defined = .true. if (map(p) /= 0) then entry%col(i)%c1(j) = sign (map(p), c) else entry%col(i)%c1(j) = c endif entry%col(i)%c2(j) = entry%col(i)%c1(j) end if end do if (any (entry%col(i)%c1 /= 0) .and. & entry%col(i)%c1(1) == - entry%col(i)%c1(2)) return end do if (associated (list%last)) then list%last%next => entry else list%first => entry end if list%last => entry list%n = list%n + 1 end subroutine list_append_if_valid subroutine list_to_array (list, col) type(list_t), intent(inout) :: list type(color_t), dimension(:,:), intent(out), allocatable :: col type(entry_t), pointer :: entry integer :: i allocate (col (n_prt, list%n - 1)) do i = 0, list%n - 1 entry => list%first list%first => list%first%next if (i /= 0) col(:,i) = entry%col deallocate (entry) end do list%last => null () end subroutine list_to_array end subroutine color_array_make_contractions @ %def color_array_make_contractions @ Invert the color index, switching from particle to antiparticle. For gluons, we have to swap the order of color entries. <>= procedure :: invert => color_invert <>= elemental subroutine color_invert (col) class(color_t), intent(inout) :: col if (col%defined .and. .not. col%ghost) then col%c1 = - col%c1 col%c2 = - col%c2 if (col%c1(1) < 0 .and. col%c1(2) > 0) then col%c1 = col%c1(2:1:-1) col%c2 = col%c2(2:1:-1) end if end if end subroutine color_invert @ %def color_invert @ Make a color map for two matching color arrays. The result is an array of integer pairs. <>= public :: make_color_map <>= interface make_color_map module procedure color_make_color_map end interface make_color_map <>= subroutine color_make_color_map (map, col1, col2) integer, dimension(:,:), intent(out), allocatable :: map type(color_t), dimension(:), intent(in) :: col1, col2 integer, dimension(:,:), allocatable :: map1 integer :: i, j, k allocate (map1 (2, 2 * sum (col1%get_number_of_indices ()))) k = 0 do i = 1, size (col1) if (col1(i)%defined .and. .not. col1(i)%ghost) then do j = 1, size (col1(i)%c1) if (col1(i)%c1(j) /= 0 & .and. all (map1(1,:k) /= abs (col1(i)%c1(j)))) then k = k + 1 map1(1,k) = abs (col1(i)%c1(j)) map1(2,k) = abs (col2(i)%c1(j)) end if if (col1(i)%c2(j) /= 0 & .and. all (map1(1,:k) /= abs (col1(i)%c2(j)))) then k = k + 1 map1(1,k) = abs (col1(i)%c2(j)) map1(2,k) = abs (col2(i)%c2(j)) end if end do end if end do allocate (map (2, k)) map(:,:) = map1(:,:k) end subroutine color_make_color_map @ %def make_color_map @ Translate colors which have a match in the translation table (an array of integer pairs). Color that do not match an entry are simply transferred; this is done by first transferring all components, then modifiying entries where appropriate. <>= public :: color_translate <>= interface color_translate module procedure color_translate0 module procedure color_translate0_offset module procedure color_translate1 end interface color_translate <>= subroutine color_translate0 (col, map) type(color_t), intent(inout) :: col integer, dimension(:,:), intent(in) :: map type(color_t) :: col_tmp integer :: i if (col%defined .and. .not. col%ghost) then col_tmp = col do i = 1, size (map,2) where (abs (col%c1) == map(1,i)) col_tmp%c1 = sign (map(2,i), col%c1) end where where (abs (col%c2) == map(1,i)) col_tmp%c2 = sign (map(2,i), col%c2) end where end do col = col_tmp end if end subroutine color_translate0 subroutine color_translate0_offset (col, map, offset) type(color_t), intent(inout) :: col integer, dimension(:,:), intent(in) :: map integer, intent(in) :: offset logical, dimension(size(col%c1)) :: mask1, mask2 type(color_t) :: col_tmp integer :: i if (col%defined .and. .not. col%ghost) then col_tmp = col mask1 = col%c1 /= 0 mask2 = col%c2 /= 0 do i = 1, size (map,2) where (abs (col%c1) == map(1,i)) col_tmp%c1 = sign (map(2,i), col%c1) mask1 = .false. end where where (abs (col%c2) == map(1,i)) col_tmp%c2 = sign (map(2,i), col%c2) mask2 = .false. end where end do col = col_tmp where (mask1) col%c1 = sign (abs (col%c1) + offset, col%c1) where (mask2) col%c2 = sign (abs (col%c2) + offset, col%c2) end if end subroutine color_translate0_offset subroutine color_translate1 (col, map, offset) type(color_t), dimension(:), intent(inout) :: col integer, dimension(:,:), intent(in) :: map integer, intent(in), optional :: offset integer :: i if (present (offset)) then do i = 1, size (col) call color_translate0_offset (col(i), map, offset) end do else do i = 1, size (col) call color_translate0 (col(i), map) end do end if end subroutine color_translate1 @ %def color_translate @ Merge two color objects by taking the first entry from the first and the first entry from the second argument. Makes sense only if the input colors are defined (and diagonal). If either one is undefined, transfer the defined one. <>= generic :: operator(.merge.) => merge_colors procedure, private :: merge_colors @ %def .merge. <>= elemental function merge_colors (col1, col2) result (col) type(color_t) :: col class(color_t), intent(in) :: col1, col2 if (color_is_defined (col1) .and. color_is_defined (col2)) then if (color_is_ghost (col1) .and. color_is_ghost (col2)) then call color_init_trivial_ghost (col, .true.) else call color_init_arrays (col, col1%c1, col2%c1) end if else if (color_is_defined (col1)) then call color_init_array (col, col1%c1) else if (color_is_defined (col2)) then call color_init_array (col, col2%c1) end if end function merge_colors @ %def merge_colors @ Merge up to two (diagonal!) color objects. The result inherits the unmatched color lines of the input colors. If one of the input colors is undefined, the output is undefined as well. It must be in a supported color representation. A color-ghost object should not actually occur in real-particle events, but for completeness we define its behavior. For simplicity, it is identified as a color-octet with zero color/anticolor. It can only couple to a triplet or antitriplet. A fusion of triplet with matching antitriplet will yield a singlet, not a ghost, however. If the fusion fails, the result is undefined. <>= generic :: operator (.fuse.) => color_fusion procedure, private :: color_fusion <>= function color_fusion (col1, col2) result (col) class(color_t), intent(in) :: col1, col2 type(color_t) :: col integer, dimension(2) :: ctype if (col1%is_defined () .and. col2%is_defined ()) then if (col1%is_diagonal () .and. col2%is_diagonal ()) then ctype = [col1%get_type (), col2%get_type ()] select case (ctype(1)) case (1) select case (ctype(2)) case (1,3,-3,8) col = col2 end select case (3) select case (ctype(2)) case (1) col = col1 case (-3) call t_a (col1%get_col (), col2%get_acl ()) case (8) call t_o (col1%get_col (), col2%get_acl (), & & col2%get_col ()) end select case (-3) select case (ctype(2)) case (1) col = col1 case (3) call t_a (col2%get_col (), col1%get_acl ()) case (8) call a_o (col1%get_acl (), col2%get_col (), & & col2%get_acl ()) end select case (8) select case (ctype(2)) case (1) col = col1 case (3) call t_o (col2%get_col (), col1%get_acl (), & & col1%get_col ()) case (-3) call a_o (col2%get_acl (), col1%get_col (), & & col1%get_acl ()) case (8) call o_o (col1%get_col (), col1%get_acl (), & & col2%get_col (), col2%get_acl ()) end select end select end if end if contains subroutine t_a (c1, c2) integer, intent(in) :: c1, c2 if (c1 == c2) then call col%init_col_acl (0, 0) else call col%init_col_acl (c1, c2) end if end subroutine t_a subroutine t_o (c1, c2, c3) integer, intent(in) :: c1, c2, c3 if (c1 == c2) then call col%init_col_acl (c3, 0) else if (c2 == 0 .and. c3 == 0) then call col%init_col_acl (c1, 0) end if end subroutine t_o subroutine a_o (c1, c2, c3) integer, intent(in) :: c1, c2, c3 if (c1 == c2) then call col%init_col_acl (0, c3) else if (c2 == 0 .and. c3 == 0) then call col%init_col_acl (0, c1) end if end subroutine a_o subroutine o_o (c1, c2, c3, c4) integer, intent(in) :: c1, c2, c3, c4 if (all ([c1,c2,c3,c4] /= 0)) then if (c2 == c3 .and. c4 == c1) then call col%init_col_acl (0, 0) else if (c2 == c3) then call col%init_col_acl (c1, c4) else if (c4 == c1) then call col%init_col_acl (c3, c2) end if end if end subroutine o_o end function color_fusion @ %def color_fusion @ Compute the color factor, given two interfering color arrays. <>= public :: compute_color_factor <>= function compute_color_factor (col1, col2, nc) result (factor) real(default) :: factor type(color_t), dimension(:), intent(in) :: col1, col2 integer, intent(in), optional :: nc type(color_t), dimension(size(col1)) :: col integer :: ncol, nloops, nghost ncol = 3; if (present (nc)) ncol = nc col = col1 .merge. col2 nloops = count_color_loops (col) nghost = count (col%is_ghost ()) factor = real (ncol, default) ** (nloops - nghost) if (color_ghost_parity (col)) factor = - factor end function compute_color_factor @ %def compute_color_factor @ We have a pair of color index arrays which corresponds to a squared matrix element. We want to determine the number of color loops in this square matrix element. So we first copy the colors (stored in a single color array with a pair of color lists in each entry) to a temporary where the color indices are shifted by some offset. We then recursively follow each loop, starting at the first color that has the offset, resetting the first color index to the loop index and each further index to zero as we go. We check that (a) each color index occurs twice within the left (right) color array, (b) the loops are closed, so we always come back to a line which has the loop index. In order for the algorithm to work we have to conjugate the colors of initial state particles (one for decays, two for scatterings) into their corresponding anticolors of outgoing particles. <>= public :: count_color_loops <>= function count_color_loops (col) result (count) integer :: count type(color_t), dimension(:), intent(in) :: col type(color_t), dimension(size(col)) :: cc integer :: i, n, offset cc = col n = size (cc) offset = n call color_add_offset (cc, offset) count = 0 SCAN_LOOPS: do do i = 1, n if (color_is_nonzero (cc(i))) then if (any (cc(i)%c1 > offset)) then count = count + 1 call follow_line1 (pick_new_line (cc(i)%c1, count, 1)) cycle SCAN_LOOPS end if end if end do exit SCAN_LOOPS end do SCAN_LOOPS contains function pick_new_line (c, reset_val, sgn) result (line) integer :: line integer, dimension(:), intent(inout) :: c integer, intent(in) :: reset_val integer, intent(in) :: sgn integer :: i if (any (c == count)) then line = count else do i = 1, size (c) if (sign (1, c(i)) == sgn .and. abs (c(i)) > offset) then line = c(i) c(i) = reset_val return end if end do call color_mismatch end if end function pick_new_line subroutine reset_line (c, line) integer, dimension(:), intent(inout) :: c integer, intent(in) :: line integer :: i do i = 1, size (c) if (c(i) == line) then c(i) = 0 return end if end do end subroutine reset_line recursive subroutine follow_line1 (line) integer, intent(in) :: line integer :: i if (line == count) return do i = 1, n if (any (cc(i)%c1 == -line)) then call reset_line (cc(i)%c1, -line) call follow_line2 (pick_new_line (cc(i)%c2, 0, sign (1, -line))) return end if end do call color_mismatch () end subroutine follow_line1 recursive subroutine follow_line2 (line) integer, intent(in) :: line integer :: i do i = 1, n if (any (cc(i)%c2 == -line)) then call reset_line (cc(i)%c2, -line) call follow_line1 (pick_new_line (cc(i)%c1, 0, sign (1, -line))) return end if end do call color_mismatch () end subroutine follow_line2 subroutine color_mismatch () call color_write (col) print * call msg_fatal ("Color flow mismatch: Non-closed color lines appear during ", & [var_str ("the evaluation of color correlations. This can happen if there "), & var_str ("are different color structures in the initial or final state of "), & var_str ("the process definition. If so, please use separate processes for "), & var_str ("the different initial / final states. In a future WHIZARD version "), & var_str ("this will be fixed.")]) end subroutine color_mismatch end function count_color_loops @ %def count_color_loops @ \subsection{Unit tests} Test module, followed by the corresponding implementation module. <<[[colors_ut.f90]]>>= <> module colors_ut use unit_tests use colors_uti <> <> contains <> end module colors_ut @ %def colors_ut @ <<[[colors_uti.f90]]>>= <> module colors_uti use colors <> <> contains <> end module colors_uti @ %def colors_ut @ API: driver for the unit tests below. <>= public :: color_test <>= subroutine color_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine color_test @ %def color_test @ This is a color counting test. <>= call test (color_1, "color_1", & "check color counting", & u, results) <>= public :: color_1 <>= subroutine color_1 (u) integer, intent(in) :: u type(color_t), dimension(4) :: col1, col2, col type(color_t), dimension(:), allocatable :: col3 type(color_t), dimension(:,:), allocatable :: col_array integer :: count, i call col1%init_col_acl ([1, 0, 2, 3], [0, 1, 3, 2]) col2 = col1 call color_write (col1, u) write (u, "(A)") call color_write (col2, u) write (u, "(A)") col = col1 .merge. col2 call color_write (col, u) write (u, "(A)") count = count_color_loops (col) write (u, "(A,I1)") "Number of color loops (3): ", count call col2%init_col_acl ([1, 0, 2, 3], [0, 2, 3, 1]) call color_write (col1, u) write (u, "(A)") call color_write (col2, u) write (u, "(A)") col = col1 .merge. col2 call color_write (col, u) write (u, "(A)") count = count_color_loops (col) write (u, "(A,I1)") "Number of color loops (2): ", count write (u, "(A)") allocate (col3 (4)) call color_init_from_array (col3, & reshape ([1, 0, 0, -1, 2, -3, 3, -2], & [2, 4])) call color_write (col3, u) write (u, "(A)") call color_array_make_contractions (col3, col_array) write (u, "(A)") "Contractions:" do i = 1, size (col_array, 2) call color_write (col_array(:,i), u) write (u, "(A)") end do deallocate (col3) write (u, "(A)") allocate (col3 (6)) call color_init_from_array (col3, & reshape ([1, -2, 3, 0, 0, -1, 2, -4, -3, 0, 4, 0], & [2, 6])) call color_write (col3, u) write (u, "(A)") call color_array_make_contractions (col3, col_array) write (u, "(A)") "Contractions:" do i = 1, size (col_array, 2) call color_write (col_array(:,i), u) write (u, "(A)") end do end subroutine color_1 @ %def color_1 @ A color fusion test. <>= call test (color_2, "color_2", & "color fusion", & u, results) <>= public :: color_2 <>= subroutine color_2 (u) integer, intent(in) :: u type(color_t) :: s1, t1, t2, a1, a2, o1, o2, o3, o4, g1 write (u, "(A)") "* Test output: color_2" write (u, "(A)") "* Purpose: test all combinations for color-object fusion" write (u, "(A)") call s1%init_col_acl (0,0) call t1%init_col_acl (1,0) call t2%init_col_acl (2,0) call a1%init_col_acl (0,1) call a2%init_col_acl (0,2) call o1%init_col_acl (1,2) call o2%init_col_acl (1,3) call o3%init_col_acl (2,3) call o4%init_col_acl (2,1) call g1%init (ghost=.true.) call wrt ("s1", s1) call wrt ("t1", t1) call wrt ("t2", t2) call wrt ("a1", a1) call wrt ("a2", a2) call wrt ("o1", o1) call wrt ("o2", o2) call wrt ("o3", o3) call wrt ("o4", o4) call wrt ("g1", g1) write (u, *) call wrt ("s1 * s1", s1 .fuse. s1) write (u, *) call wrt ("s1 * t1", s1 .fuse. t1) call wrt ("s1 * a1", s1 .fuse. a1) call wrt ("s1 * o1", s1 .fuse. o1) write (u, *) call wrt ("t1 * s1", t1 .fuse. s1) call wrt ("a1 * s1", a1 .fuse. s1) call wrt ("o1 * s1", o1 .fuse. s1) write (u, *) call wrt ("t1 * t1", t1 .fuse. t1) write (u, *) call wrt ("t1 * t2", t1 .fuse. t2) call wrt ("t1 * a1", t1 .fuse. a1) call wrt ("t1 * a2", t1 .fuse. a2) call wrt ("t1 * o1", t1 .fuse. o1) call wrt ("t2 * o1", t2 .fuse. o1) write (u, *) call wrt ("t2 * t1", t2 .fuse. t1) call wrt ("a1 * t1", a1 .fuse. t1) call wrt ("a2 * t1", a2 .fuse. t1) call wrt ("o1 * t1", o1 .fuse. t1) call wrt ("o1 * t2", o1 .fuse. t2) write (u, *) call wrt ("a1 * a1", a1 .fuse. a1) write (u, *) call wrt ("a1 * a2", a1 .fuse. a2) call wrt ("a1 * o1", a1 .fuse. o1) call wrt ("a2 * o2", a2 .fuse. o2) write (u, *) call wrt ("a2 * a1", a2 .fuse. a1) call wrt ("o1 * a1", o1 .fuse. a1) call wrt ("o2 * a2", o2 .fuse. a2) write (u, *) call wrt ("o1 * o1", o1 .fuse. o1) write (u, *) call wrt ("o1 * o2", o1 .fuse. o2) call wrt ("o1 * o3", o1 .fuse. o3) call wrt ("o1 * o4", o1 .fuse. o4) write (u, *) call wrt ("o2 * o1", o2 .fuse. o1) call wrt ("o3 * o1", o3 .fuse. o1) call wrt ("o4 * o1", o4 .fuse. o1) write (u, *) call wrt ("g1 * g1", g1 .fuse. g1) write (u, *) call wrt ("g1 * s1", g1 .fuse. s1) call wrt ("g1 * t1", g1 .fuse. t1) call wrt ("g1 * a1", g1 .fuse. a1) call wrt ("g1 * o1", g1 .fuse. o1) write (u, *) call wrt ("s1 * g1", s1 .fuse. g1) call wrt ("t1 * g1", t1 .fuse. g1) call wrt ("a1 * g1", a1 .fuse. g1) call wrt ("o1 * g1", o1 .fuse. g1) write (u, "(A)") write (u, "(A)") "* Test output end: color_2" contains subroutine wrt (s, col) character(*), intent(in) :: s class(color_t), intent(in) :: col write (u, "(A,1x,'=',1x)", advance="no") s call col%write (u) write (u, *) end subroutine wrt end subroutine color_2 @ %def color_2 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{The Madgraph color model} This section describes the method for matrix element and color flow calculation within Madgraph. For each Feynman diagram, the colorless amplitude for a specified helicity and momentum configuration (in- and out- combined) is computed: \begin{equation} A_d(p,h) \end{equation} Inserting color, the squared matrix element for definite helicity and momentum is \begin{equation} M^2(p,h) = \sum_{dd'} A_{d}(p,h)\,C_{dd'} A_{d'}^*(p,h) \end{equation} where $C_{dd'}$ describes the color interference of the two diagrams $A_d$ and $A_d'$, which is independent of momentum and helicity and can be calculated for each Feynman diagram pair by reducing it to the corresponding color graph. Obviously, one could combine all diagrams with identical color structure, such that the index $d$ runs only over different color graphs. For colorless diagrams all elements of $C_{dd'}$ are equal to unity. The hermitian matrix $C_{dd'}$ is diagonalized once and for all, such that it can be written in the form \begin{equation} C_{dd'} = \sum_\lambda c_d^\lambda \lambda\, c_d^\lambda{}^*, \end{equation} where the eigenvectors $c_d$ are normalized, \begin{equation} \sum_d |c_d^\lambda|^2 = 1, \end{equation} and the $\lambda$ values are the corresponding eigenvalues. In the colorless case, this means $c_d = 1/\sqrt{N_d}$ for all diagrams ($N_d=$ number of diagrams), and $\lambda=N_d$ is the only nonzero eigenvalue. Consequently, the squared matrix element for definite helicity and momentum can also be written as \begin{equation} M^2(p,h) = \sum_\lambda A_\lambda(p,h)\, \lambda\, A_\lambda(p,h)^* \end{equation} with \begin{equation} A_\lambda(p,h) = \sum_d c_d^\lambda A_d(p,h). \end{equation} For generic spin density matrices, this is easily generalized to \begin{equation} M^2(p,h,h') = \sum_\lambda A_\lambda(p,h)\, \lambda\, A_\lambda(p,h')^* \end{equation} To determine the color flow probabilities of a given momentum-helicity configuration, the color flow amplitudes are calculated as \begin{equation} a_f(p,h) = \sum_d \beta^f_d A_d(p,h), \end{equation} where the coefficients $\beta^f_d$ describe the amplitude for a given Feynman diagram (or color graph) $d$ to correspond to a definite color flow~$f$. They are computed from $C_{dd'}$ by transforming this matrix into the color flow basis and neglecting all off-diagonal elements. Again, these coefficients do not depend on momentum or helicity and can therefore be calculated in advance. This gives the color flow transition matrix \begin{equation} F^f(p,h,h') = a_f(p,h)\, a^*_f(p,h') \end{equation} which is assumed diagonal in color flow space and is separate from the color-summed transition matrix $M^2$. They are, however, equivalent (up to a factor) to leading order in $1/N_c$, and using the color flow transition matrix is appropriate for matching to hadronization. Note that the color flow transition matrix is not normalized at this stage. To make use of it, we have to fold it with the in-state density matrix to get a pseudo density matrix \begin{equation} \hat\rho_{\rm out}^f(p,h_{\rm out},h'_{\rm out}) = \sum_{h_{\rm in} h'_{\rm in}} F^f(p,h,h')\, \rho_{\rm in}(p,h_{\rm in},h'_{\rm in}) \end{equation} which gets a meaning only after contracted with projections on the outgoing helicity states $k_{\rm out}$, given as linear combinations of helicity states with the unitary coefficient matrix $c(k_{\rm out}, h_{\rm out})$. Then the probability of finding color flow $f$ when the helicity state $k_{\rm out}$ is measured is given by \begin{equation} P^f(p, k_{\rm out}) = Q^f(p, k_{\rm out}) / \sum_f Q^f(p, k_{\rm out}) \end{equation} where \begin{equation} Q^f(p, k_{\rm out}) = \sum_{h_{\rm out} h'_{\rm out}} c(k_{\rm out}, h_{\rm out})\, \hat\rho_{\rm out}^f(p,h_{\rm out},h'_{\rm out})\, c^*(k_{\rm out}, h'_{\rm out}) \end{equation} However, if we can assume that the out-state helicity basis is the canonical one, we can throw away the off diagonal elements in the color flow density matrix and normalize the ones on the diagonal to obtain \begin{equation} P^f(p, h_{\rm out}) = \hat\rho_{\rm out}^f(p,h_{\rm out},h_{\rm out}) / \sum_f \hat\rho_{\rm out}^f(p,h_{\rm out},h_{\rm out}) \end{equation} Finally, the color-summed out-state density matrix is computed by the scattering formula \begin{align} {\rho_{\rm out}(p,h_{\rm out},h'_{\rm out})} &= \sum_{h_{\rm in} h'_{\rm in}} M^2(p,h,h')\, \rho_{\rm in}(p,h_{\rm in},h'_{\rm in}) \\ &= \sum_{h_{\rm in} h'_{\rm in} \lambda} A_\lambda(p,h)\, \lambda\, A_\lambda(p,h')^* \rho_{\rm in}(p,h_{\rm in},h'_{\rm in}), \end{align} The trace of $\rho_{\rm out}$ is the squared matrix element, summed over all internal degrees of freedom. To get the squared matrix element for a definite helicity $k_{\rm out}$ and color flow $f$, one has to project the density matrix onto the given helicity state and multiply with $P^f(p, k_{\rm out})$. For diagonal helicities the out-state density reduces to \begin{equation} \rho_{\rm out}(p,h_{\rm out}) = \sum_{h_{\rm in}\lambda} \lambda|A_\lambda(p,h)|^2 \rho_{\rm in}(p,h_{\rm in}). \end{equation} Since no basis transformation is involved, we can use the normalized color flow probability $P^f(p, h_{\rm out})$ and express the result as \begin{align} \rho_{\rm out}^f(p,h_{\rm out}) &= \rho_{\rm out}(p,h_{\rm out})\,P^f(p, h_{\rm out}) \\ &= \sum_{h_{\rm in}\lambda} \frac{|a^f(p,h)|^2}{\sum_f|a^f(p,h)|^2} \lambda|A_\lambda(p,h)|^2 \rho_{\rm in}(p,h_{\rm in}). \end{align} From these considerations, the following calculation strategy can be derived: \begin{itemize} \item Before the first event is generated, the color interference matrix $C_{dd'}$ is computed and diagonalized, so the eigenvectors $c^\lambda_d$, eigenvalues $\lambda$ and color flow coefficients $\beta^f_d$ are obtained. In practice, these calculations are done when the matrix element code is generated, and the results are hardcoded in the matrix element subroutine as [[DATA]] statements. \item For each event, one loops over helicities once and stores the matrices $A_\lambda(p,h)$ and $a^f(p,h)$. The allowed color flows, helicity combinations and eigenvalues are each labeled by integer indices, so one has to store complex matrices of dimension $N_\lambda\times N_h$ and $N_f\times N_h$, respectively. \item The further strategy depends on the requested information. \begin{enumerate} \item If colorless diagonal helicity amplitudes are required, the eigenvalues $A_\lambda(p,h)$ are squared, summed with weight $\lambda$, and the result contracted with the in-state probability vector $\rho_{\rm in}(p, h_{\rm in})$. The result is a probability vector $\rho_{\rm out}(p, h_{\rm out})$. \item For colored diagonal helicity amplitudes, the color coefficients $a^f(p,h)$ are also squared and used as weights to obtain the color-flow probability vector $\rho_{\rm out}^f(p, h_{\rm out})$. \item For colorless non-diagonal helicity amplitudes, we contract the tensor product of $A_\lambda(p,h)$ with $A_\lambda(p,h')$, weighted with $\lambda$, with the correlated in-state density matrix, to obtain a correlated out-state density matrix. \item In the general (colored, non-diagonal) case, we do the same as in the colorless case, but return the un-normalized color flow density matrix $\hat\rho_{\rm out}^f(p,h_{\rm out},h'_{\rm out})$ in addition. When the relevant helicity basis is known, the latter can be used by the caller program to determine flow probabilities. (In reality, we assume the canonical basis and reduce the correlated out-state density to its diagonal immediately.) \end{enumerate} \end{itemize} @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Flavors: Particle properties} This module contains a type for holding the flavor code, and all functions that depend on the model, i.e., that determine particle properties. The PDG code is packed in a special [[flavor]] type. (This prohibits meaningless operations, and it allows for a different implementation, e.g., some non-PDG scheme internally, if appropiate at some point.) There are lots of further particle properties that depend on the model. Implementing a flyweight pattern, the associated field data object is to be stored in a central area, the [[flavor]] object just receives a pointer to this, so all queries can be delegated. <<[[flavors.f90]]>>= <> module flavors <> <> use io_units use diagnostics use physics_defs, only: UNDEFINED use physics_defs, only: INVALID use physics_defs, only: HADRON_REMNANT use physics_defs, only: HADRON_REMNANT_SINGLET use physics_defs, only: HADRON_REMNANT_TRIPLET use physics_defs, only: HADRON_REMNANT_OCTET use model_data use colors, only: color_t <> <> <> <> contains <> end module flavors @ %def flavors @ \subsection{The flavor type} The flavor type is an integer representing the PDG code, or undefined (zero). Negative codes represent antiflavors. They should be used only for particles which do have a distinct antiparticle. The [[hard_process]] flag can be set for particles that are participating in the hard interaction. The [[radiated]] flag can be set for particles that are the result of a beam-structure interaction (hadron beam remnant, ISR photon, etc.), not of the hard interaction itself. Further properties of the given flavor can be retrieved via the particle-data pointer, if it is associated. <>= public :: flavor_t <>= type :: flavor_t private integer :: f = UNDEFINED logical :: hard_process = .false. logical :: radiated = .false. type(field_data_t), pointer :: field_data => null () contains <> end type flavor_t @ %def flavor_t @ Initializer form. If the model is assigned, the procedure is impure, therefore we have to define a separate array version. Note: The pure elemental subroutines can't have an intent(out) CLASS argument (because of the potential for an impure finalizer in a type extension), so we stick to intent(inout) and (re)set all components explicitly. <>= generic :: init => & flavor_init_empty, & flavor_init, & flavor_init_field_data, & flavor_init_model, & flavor_init_model_alt, & flavor_init_name_model procedure, private :: flavor_init_empty procedure, private :: flavor_init procedure, private :: flavor_init_field_data procedure, private :: flavor_init_model procedure, private :: flavor_init_model_alt procedure, private :: flavor_init_name_model <>= elemental subroutine flavor_init_empty (flv) class(flavor_t), intent(inout) :: flv flv%f = UNDEFINED flv%hard_process = .false. flv%radiated = .false. flv%field_data => null () end subroutine flavor_init_empty elemental subroutine flavor_init (flv, f) class(flavor_t), intent(inout) :: flv integer, intent(in) :: f flv%f = f flv%hard_process = .false. flv%radiated = .false. flv%field_data => null () end subroutine flavor_init impure elemental subroutine flavor_init_field_data (flv, field_data) class(flavor_t), intent(inout) :: flv type(field_data_t), intent(in), target :: field_data flv%f = field_data%get_pdg () flv%hard_process = .false. flv%radiated = .false. flv%field_data => field_data end subroutine flavor_init_field_data impure elemental subroutine flavor_init_model (flv, f, model) class(flavor_t), intent(inout) :: flv integer, intent(in) :: f class(model_data_t), intent(in), target :: model flv%f = f flv%hard_process = .false. flv%radiated = .false. flv%field_data => model%get_field_ptr (f, check=.true.) end subroutine flavor_init_model impure elemental subroutine flavor_init_model_alt (flv, f, model, alt_model) class(flavor_t), intent(inout) :: flv integer, intent(in) :: f class(model_data_t), intent(in), target :: model, alt_model flv%f = f flv%hard_process = .false. flv%radiated = .false. flv%field_data => model%get_field_ptr (f, check=.false.) if (.not. associated (flv%field_data)) then flv%field_data => alt_model%get_field_ptr (f, check=.false.) if (.not. associated (flv%field_data)) then write (msg_buffer, "(A,1x,I0,1x,A,1x,A,1x,A,1x,A)") & "Particle with code", f, & "found neither in model", char (model%get_name ()), & "nor in model", char (alt_model%get_name ()) call msg_fatal () end if end if end subroutine flavor_init_model_alt impure elemental subroutine flavor_init_name_model (flv, name, model) class(flavor_t), intent(inout) :: flv type(string_t), intent(in) :: name class(model_data_t), intent(in), target :: model flv%f = model%get_pdg (name) flv%hard_process = .false. flv%radiated = .false. flv%field_data => model%get_field_ptr (name, check=.true.) end subroutine flavor_init_name_model @ %def flavor_init @ Set the [[radiated]] flag. <>= procedure :: tag_radiated => flavor_tag_radiated <>= elemental subroutine flavor_tag_radiated (flv) class(flavor_t), intent(inout) :: flv flv%radiated = .true. end subroutine flavor_tag_radiated @ %def flavor_tag_radiated @ Set the [[hard_process]] flag. <>= procedure :: tag_hard_process => flavor_tag_hard_process <>= elemental subroutine flavor_tag_hard_process (flv, hard) class(flavor_t), intent(inout) :: flv logical, intent(in), optional :: hard if (present (hard)) then flv%hard_process = hard else flv%hard_process = .true. end if end subroutine flavor_tag_hard_process @ %def flavor_tag_hard_process @ Undefine the flavor state: <>= procedure :: undefine => flavor_undefine <>= elemental subroutine flavor_undefine (flv) class(flavor_t), intent(inout) :: flv flv%f = UNDEFINED flv%field_data => null () end subroutine flavor_undefine @ %def flavor_undefine @ Output: dense, no linebreak A hard-process tag is only shown if debugging is on. <>= procedure :: write => flavor_write <>= subroutine flavor_write (flv, unit) class(flavor_t), intent(in) :: flv integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit); if (u < 0) return if (associated (flv%field_data)) then write (u, "(A)", advance="no") "f(" else write (u, "(A)", advance="no") "p(" end if write (u, "(I0)", advance="no") flv%f if (flv%radiated) then write (u, "('*')", advance="no") end if if (msg_level (D_FLAVOR) >= DEBUG) then if (flv%hard_process) then write (u, "('#')", advance="no") end if end if write (u, "(A)", advance="no") ")" end subroutine flavor_write @ %def flavor_write @ <>= public :: flavor_write_array <>= subroutine flavor_write_array (flv, unit) type(flavor_t), intent(in), dimension(:) :: flv integer, intent(in), optional :: unit integer :: u, i_flv u = given_output_unit (unit); if (u < 0) return do i_flv = 1, size (flv) call flv(i_flv)%write (u) if (i_flv /= size (flv)) write (u,"(A)", advance = "no") " / " end do write (u,"(A)") end subroutine flavor_write_array @ %def flavor_write_array @ Binary I/O. Currently, the model information is not written/read, so after reading the particle-data pointer is empty. <>= procedure :: write_raw => flavor_write_raw procedure :: read_raw => flavor_read_raw <>= subroutine flavor_write_raw (flv, u) class(flavor_t), intent(in) :: flv integer, intent(in) :: u write (u) flv%f write (u) flv%radiated write (u) flv%hard_process end subroutine flavor_write_raw subroutine flavor_read_raw (flv, u, iostat) class(flavor_t), intent(out) :: flv integer, intent(in) :: u integer, intent(out), optional :: iostat read (u, iostat=iostat) flv%f if (present (iostat)) then if (iostat /= 0) return end if read (u, iostat=iostat) flv%radiated read (u, iostat=iostat) flv%hard_process end subroutine flavor_read_raw @ %def flavor_write_raw flavor_read_raw @ \subsubsection{Assignment} Default assignment of flavor objects is possible, but cannot be used in pure procedures, because a pointer assignment is involved. Assign the particle pointer separately. This cannot be elemental, so we define a scalar and an array version explicitly. We refer to an array of flavors, not an array of models. <>= procedure :: set_model => flavor_set_model_single <>= impure elemental subroutine flavor_set_model_single (flv, model) class(flavor_t), intent(inout) :: flv class(model_data_t), intent(in), target :: model if (flv%f /= UNDEFINED) & flv%field_data => model%get_field_ptr (flv%f) end subroutine flavor_set_model_single @ %def flavor_set_model @ \subsubsection{Predicates} Return the definition status. By definition, the flavor object is defined if the flavor PDG code is nonzero. <>= procedure :: is_defined => flavor_is_defined <>= elemental function flavor_is_defined (flv) result (defined) class(flavor_t), intent(in) :: flv logical :: defined defined = flv%f /= UNDEFINED end function flavor_is_defined @ %def flavor_is_defined @ Check for valid flavor (including undefined). This is distinct from the [[is_defined]] status. Invalid flavor is actually a specific PDG code. <>= procedure :: is_valid => flavor_is_valid <>= elemental function flavor_is_valid (flv) result (valid) class(flavor_t), intent(in) :: flv logical :: valid valid = flv%f /= INVALID end function flavor_is_valid @ %def flavor_is_valid @ Return true if the particle-data pointer is associated. (Debugging aid) <>= procedure :: is_associated => flavor_is_associated <>= elemental function flavor_is_associated (flv) result (flag) class(flavor_t), intent(in) :: flv logical :: flag flag = associated (flv%field_data) end function flavor_is_associated @ %def flavor_is_associated @ Check the [[radiated]] flag. A radiated particle has a definite PDG flavor status, but it is actually a pseudoparticle (a beam remnant) which may be subject to fragmentation. <>= procedure :: is_radiated => flavor_is_radiated <>= elemental function flavor_is_radiated (flv) result (flag) class(flavor_t), intent(in) :: flv logical :: flag flag = flv%radiated end function flavor_is_radiated @ %def flavor_is_radiated @ Check the [[hard_process]] flag. A particle is tagged with this flag if it participates in the hard interaction and is not a beam remnant. <>= procedure :: is_hard_process => flavor_is_hard_process <>= elemental function flavor_is_hard_process (flv) result (flag) class(flavor_t), intent(in) :: flv logical :: flag flag = flv%hard_process end function flavor_is_hard_process @ %def flavor_is_hard_process @ \subsubsection{Accessing contents} With the exception of the PDG code, all particle property enquiries are delegated to the [[field_data]] pointer. If this is unassigned, some access function will crash. Return the flavor as an integer <>= procedure :: get_pdg => flavor_get_pdg <>= elemental function flavor_get_pdg (flv) result (f) integer :: f class(flavor_t), intent(in) :: flv f = flv%f end function flavor_get_pdg @ %def flavor_get_pdg @ Return the flavor of the antiparticle <>= procedure :: get_pdg_anti => flavor_get_pdg_anti <>= elemental function flavor_get_pdg_anti (flv) result (f) integer :: f class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then if (flv%field_data%has_antiparticle ()) then f = -flv%f else f = flv%f end if else f = 0 end if end function flavor_get_pdg_anti @ %def flavor_get_pdg_anti @ Absolute value: <>= procedure :: get_pdg_abs => flavor_get_pdg_abs <>= elemental function flavor_get_pdg_abs (flv) result (f) integer :: f class(flavor_t), intent(in) :: flv f = abs (flv%f) end function flavor_get_pdg_abs @ %def flavor_get_pdg_abs @ Generic properties <>= procedure :: is_visible => flavor_is_visible procedure :: is_parton => flavor_is_parton procedure :: is_beam_remnant => flavor_is_beam_remnant procedure :: is_gauge => flavor_is_gauge procedure :: is_left_handed => flavor_is_left_handed procedure :: is_right_handed => flavor_is_right_handed procedure :: is_antiparticle => flavor_is_antiparticle procedure :: has_antiparticle => flavor_has_antiparticle procedure :: is_stable => flavor_is_stable procedure :: get_decays => flavor_get_decays procedure :: decays_isotropically => flavor_decays_isotropically procedure :: decays_diagonal => flavor_decays_diagonal procedure :: has_decay_helicity => flavor_has_decay_helicity procedure :: get_decay_helicity => flavor_get_decay_helicity procedure :: is_polarized => flavor_is_polarized <>= elemental function flavor_is_visible (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%is_visible () else flag = .false. end if end function flavor_is_visible elemental function flavor_is_parton (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%is_parton () else flag = .false. end if end function flavor_is_parton elemental function flavor_is_beam_remnant (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv select case (abs (flv%f)) case (HADRON_REMNANT, & HADRON_REMNANT_SINGLET, HADRON_REMNANT_TRIPLET, HADRON_REMNANT_OCTET) flag = .true. case default flag = .false. end select end function flavor_is_beam_remnant elemental function flavor_is_gauge (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%is_gauge () else flag = .false. end if end function flavor_is_gauge elemental function flavor_is_left_handed (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then if (flv%f > 0) then flag = flv%field_data%is_left_handed () else flag = flv%field_data%is_right_handed () end if else flag = .false. end if end function flavor_is_left_handed elemental function flavor_is_right_handed (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then if (flv%f > 0) then flag = flv%field_data%is_right_handed () else flag = flv%field_data%is_left_handed () end if else flag = .false. end if end function flavor_is_right_handed elemental function flavor_is_antiparticle (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv flag = flv%f < 0 end function flavor_is_antiparticle elemental function flavor_has_antiparticle (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%has_antiparticle () else flag = .false. end if end function flavor_has_antiparticle elemental function flavor_is_stable (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%is_stable (anti = flv%f < 0) else flag = .true. end if end function flavor_is_stable subroutine flavor_get_decays (flv, decay) class(flavor_t), intent(in) :: flv type(string_t), dimension(:), intent(out), allocatable :: decay logical :: anti anti = flv%f < 0 if (.not. flv%field_data%is_stable (anti)) then call flv%field_data%get_decays (decay, anti) end if end subroutine flavor_get_decays elemental function flavor_decays_isotropically (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%decays_isotropically (anti = flv%f < 0) else flag = .true. end if end function flavor_decays_isotropically elemental function flavor_decays_diagonal (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%decays_diagonal (anti = flv%f < 0) else flag = .true. end if end function flavor_decays_diagonal elemental function flavor_has_decay_helicity (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%has_decay_helicity (anti = flv%f < 0) else flag = .false. end if end function flavor_has_decay_helicity elemental function flavor_get_decay_helicity (flv) result (hel) integer :: hel class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then hel = flv%field_data%decay_helicity (anti = flv%f < 0) else hel = 0 end if end function flavor_get_decay_helicity elemental function flavor_is_polarized (flv) result (flag) logical :: flag class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then flag = flv%field_data%is_polarized (anti = flv%f < 0) else flag = .false. end if end function flavor_is_polarized @ %def flavor_is_visible @ %def flavor_is_parton @ %def flavor_is_beam_remnant @ %def flavor_is_gauge @ %def flavor_is_left_handed @ %def flavor_is_right_handed @ %def flavor_is_antiparticle @ %def flavor_has_antiparticle @ %def flavor_is_stable @ %def flavor_get_decays @ %def flavor_decays_isotropically @ %def flavor_decays_diagonal @ %def flavor_has_decays_helicity @ %def flavor_get_decay_helicity @ %def flavor_is_polarized @ Names: <>= procedure :: get_name => flavor_get_name procedure :: get_tex_name => flavor_get_tex_name <>= elemental function flavor_get_name (flv) result (name) type(string_t) :: name class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then name = flv%field_data%get_name (flv%f < 0) else name = "?" end if end function flavor_get_name elemental function flavor_get_tex_name (flv) result (name) type(string_t) :: name class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then name = flv%field_data%get_tex_name (flv%f < 0) else name = "?" end if end function flavor_get_tex_name @ %def flavor_get_name flavor_get_tex_name <>= procedure :: get_spin_type => flavor_get_spin_type procedure :: get_multiplicity => flavor_get_multiplicity procedure :: get_isospin_type => flavor_get_isospin_type procedure :: get_charge_type => flavor_get_charge_type procedure :: get_color_type => flavor_get_color_type <>= elemental function flavor_get_spin_type (flv) result (type) integer :: type class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then type = flv%field_data%get_spin_type () else type = 1 end if end function flavor_get_spin_type elemental function flavor_get_multiplicity (flv) result (type) integer :: type class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then type = flv%field_data%get_multiplicity () else type = 1 end if end function flavor_get_multiplicity elemental function flavor_get_isospin_type (flv) result (type) integer :: type class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then type = flv%field_data%get_isospin_type () else type = 1 end if end function flavor_get_isospin_type elemental function flavor_get_charge_type (flv) result (type) integer :: type class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then type = flv%field_data%get_charge_type () else type = 1 end if end function flavor_get_charge_type elemental function flavor_get_color_type (flv) result (type) integer :: type class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then if (flavor_is_antiparticle (flv)) then type = - flv%field_data%get_color_type () else type = flv%field_data%get_color_type () end if select case (type) case (-1,-8); type = abs (type) end select else type = 1 end if end function flavor_get_color_type @ %def flavor_get_spin_type @ %def flavor_get_multiplicity @ %def flavor_get_isospin_type @ %def flavor_get_charge_type @ %def flavor_get_color_type @ These functions return real values: <>= procedure :: get_charge => flavor_get_charge procedure :: get_mass => flavor_get_mass procedure :: get_width => flavor_get_width procedure :: get_isospin => flavor_get_isospin <>= elemental function flavor_get_charge (flv) result (charge) real(default) :: charge class(flavor_t), intent(in) :: flv integer :: charge_type if (associated (flv%field_data)) then charge_type = flv%get_charge_type () if (charge_type == 0 .or. charge_type == 1) then charge = 0 else if (flavor_is_antiparticle (flv)) then charge = - flv%field_data%get_charge () else charge = flv%field_data%get_charge () end if end if else charge = 0 end if end function flavor_get_charge elemental function flavor_get_mass (flv) result (mass) real(default) :: mass class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then mass = flv%field_data%get_mass () else mass = 0 end if end function flavor_get_mass elemental function flavor_get_width (flv) result (width) real(default) :: width class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then width = flv%field_data%get_width () else width = 0 end if end function flavor_get_width elemental function flavor_get_isospin (flv) result (isospin) real(default) :: isospin class(flavor_t), intent(in) :: flv if (associated (flv%field_data)) then if (flavor_is_antiparticle (flv)) then isospin = - flv%field_data%get_isospin () else isospin = flv%field_data%get_isospin () end if else isospin = 0 end if end function flavor_get_isospin @ %def flavor_get_charge flavor_get_mass flavor_get_width @ %def flavor_get_isospin @ \subsubsection{Comparisons} If one of the flavors is undefined, the other defined, they match. <>= generic :: operator(.match.) => flavor_match generic :: operator(==) => flavor_eq generic :: operator(/=) => flavor_neq procedure, private :: flavor_match procedure, private :: flavor_eq procedure, private :: flavor_neq @ %def .match. == /= <>= elemental function flavor_match (flv1, flv2) result (eq) logical :: eq class(flavor_t), intent(in) :: flv1, flv2 if (flv1%f /= UNDEFINED .and. flv2%f /= UNDEFINED) then eq = flv1%f == flv2%f else eq = .true. end if end function flavor_match elemental function flavor_eq (flv1, flv2) result (eq) logical :: eq class(flavor_t), intent(in) :: flv1, flv2 if (flv1%f /= UNDEFINED .and. flv2%f /= UNDEFINED) then eq = flv1%f == flv2%f else if (flv1%f == UNDEFINED .and. flv2%f == UNDEFINED) then eq = .true. else eq = .false. end if end function flavor_eq @ %def flavor_match flavor_eq <>= elemental function flavor_neq (flv1, flv2) result (neq) logical :: neq class(flavor_t), intent(in) :: flv1, flv2 if (flv1%f /= UNDEFINED .and. flv2%f /= UNDEFINED) then neq = flv1%f /= flv2%f else if (flv1%f == UNDEFINED .and. flv2%f == UNDEFINED) then neq = .false. else neq = .true. end if end function flavor_neq @ %def flavor_neq @ \subsubsection{Tools} Merge two flavor indices. This works only if both are equal or either one is undefined, because we have no off-diagonal flavor entries. Otherwise, generate an invalid flavor. We cannot use elemental procedures because of the pointer component. <>= public :: operator(.merge.) <>= interface operator(.merge.) module procedure merge_flavors0 module procedure merge_flavors1 end interface @ %def .merge. <>= function merge_flavors0 (flv1, flv2) result (flv) type(flavor_t) :: flv type(flavor_t), intent(in) :: flv1, flv2 if (flavor_is_defined (flv1) .and. flavor_is_defined (flv2)) then if (flv1 == flv2) then flv = flv1 else flv%f = INVALID end if else if (flavor_is_defined (flv1)) then flv = flv1 else if (flavor_is_defined (flv2)) then flv = flv2 end if end function merge_flavors0 function merge_flavors1 (flv1, flv2) result (flv) type(flavor_t), dimension(:), intent(in) :: flv1, flv2 type(flavor_t), dimension(size(flv1)) :: flv integer :: i do i = 1, size (flv1) flv(i) = flv1(i) .merge. flv2(i) end do end function merge_flavors1 @ %def merge_flavors @ Generate consecutive color indices for a given flavor. The indices are counted starting with the stored value of c, so new indices are created each time this (impure) function is called. The counter can be reset by the optional argument [[c_seed]] if desired. The optional flag [[reverse]] is used only for octets. If set, the color and anticolor entries of the octet particle are exchanged. <>= public :: color_from_flavor <>= interface color_from_flavor module procedure color_from_flavor0 module procedure color_from_flavor1 end interface <>= function color_from_flavor0 (flv, c_seed, reverse) result (col) type(color_t) :: col type(flavor_t), intent(in) :: flv integer, intent(in), optional :: c_seed logical, intent(in), optional :: reverse integer, save :: c = 1 logical :: rev if (present (c_seed)) c = c_seed rev = .false.; if (present (reverse)) rev = reverse select case (flavor_get_color_type (flv)) case (1) call col%init () case (3) call col%init ([c]); c = c + 1 case (-3) call col%init ([-c]); c = c + 1 case (8) if (rev) then call col%init ([c+1, -c]); c = c + 2 else call col%init ([c, -(c+1)]); c = c + 2 end if end select end function color_from_flavor0 function color_from_flavor1 (flv, c_seed, reverse) result (col) type(flavor_t), dimension(:), intent(in) :: flv integer, intent(in), optional :: c_seed logical, intent(in), optional :: reverse type(color_t), dimension(size(flv)) :: col integer :: i col(1) = color_from_flavor0 (flv(1), c_seed, reverse) do i = 2, size (flv) col(i) = color_from_flavor0 (flv(i), reverse=reverse) end do end function color_from_flavor1 @ %def color_from_flavor @ This procedure returns the flavor object for the antiparticle. The antiparticle code may either be the same code or its negative. <>= procedure :: anti => flavor_anti <>= function flavor_anti (flv) result (aflv) type(flavor_t) :: aflv class(flavor_t), intent(in) :: flv if (flavor_has_antiparticle (flv)) then aflv%f = - flv%f else aflv%f = flv%f end if aflv%field_data => flv%field_data end function flavor_anti @ %def flavor_anti @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Quantum numbers} This module collects helicity, color, and flavor in a single type and defines procedures <<[[quantum_numbers.f90]]>>= <> module quantum_numbers use io_units use model_data use helicities use colors use flavors <> <> <> <> contains <> end module quantum_numbers @ %def quantum_numbers @ \subsection{The quantum number type} <>= public :: quantum_numbers_t <>= type :: quantum_numbers_t private type(flavor_t) :: f type(color_t) :: c type(helicity_t) :: h integer :: sub = 0 contains <> end type quantum_numbers_t @ %def quantum_number_t @ Define quantum numbers: Initializer form. All arguments may be present or absent. Some elemental initializers are impure because they set the [[flv]] component. This implies transfer of a pointer behind the scenes. <>= generic :: init => & quantum_numbers_init_f, & quantum_numbers_init_c, & quantum_numbers_init_h, & quantum_numbers_init_fc, & quantum_numbers_init_fh, & quantum_numbers_init_ch, & quantum_numbers_init_fch, & quantum_numbers_init_fs, & quantum_numbers_init_fhs, & quantum_numbers_init_fcs, & quantum_numbers_init_fhcs procedure, private :: quantum_numbers_init_f procedure, private :: quantum_numbers_init_c procedure, private :: quantum_numbers_init_h procedure, private :: quantum_numbers_init_fc procedure, private :: quantum_numbers_init_fh procedure, private :: quantum_numbers_init_ch procedure, private :: quantum_numbers_init_fch procedure, private :: quantum_numbers_init_fs procedure, private :: quantum_numbers_init_fhs procedure, private :: quantum_numbers_init_fcs procedure, private :: quantum_numbers_init_fhcs <>= impure elemental subroutine quantum_numbers_init_f (qn, flv) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv qn%f = flv call qn%c%undefine () call qn%h%undefine () qn%sub = 0 end subroutine quantum_numbers_init_f impure elemental subroutine quantum_numbers_init_c (qn, col) class(quantum_numbers_t), intent(out) :: qn type(color_t), intent(in) :: col call qn%f%undefine () qn%c = col call qn%h%undefine () qn%sub = 0 end subroutine quantum_numbers_init_c impure elemental subroutine quantum_numbers_init_h (qn, hel) class(quantum_numbers_t), intent(out) :: qn type(helicity_t), intent(in) :: hel call qn%f%undefine () call qn%c%undefine () qn%h = hel qn%sub = 0 end subroutine quantum_numbers_init_h impure elemental subroutine quantum_numbers_init_fc (qn, flv, col) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv type(color_t), intent(in) :: col qn%f = flv qn%c = col call qn%h%undefine () qn%sub = 0 end subroutine quantum_numbers_init_fc impure elemental subroutine quantum_numbers_init_fh (qn, flv, hel) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv type(helicity_t), intent(in) :: hel qn%f = flv call qn%c%undefine () qn%h = hel qn%sub = 0 end subroutine quantum_numbers_init_fh impure elemental subroutine quantum_numbers_init_ch (qn, col, hel) class(quantum_numbers_t), intent(out) :: qn type(color_t), intent(in) :: col type(helicity_t), intent(in) :: hel call qn%f%undefine () qn%c = col qn%h = hel qn%sub = 0 end subroutine quantum_numbers_init_ch impure elemental subroutine quantum_numbers_init_fch (qn, flv, col, hel) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv type(color_t), intent(in) :: col type(helicity_t), intent(in) :: hel qn%f = flv qn%c = col qn%h = hel qn%sub = 0 end subroutine quantum_numbers_init_fch impure elemental subroutine quantum_numbers_init_fs (qn, flv, sub) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv integer, intent(in) :: sub qn%f = flv; qn%sub = sub end subroutine quantum_numbers_init_fs impure elemental subroutine quantum_numbers_init_fhs (qn, flv, hel, sub) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv type(helicity_t), intent(in) :: hel integer, intent(in) :: sub qn%f = flv; qn%h = hel; qn%sub = sub end subroutine quantum_numbers_init_fhs impure elemental subroutine quantum_numbers_init_fcs (qn, flv, col, sub) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv type(color_t), intent(in) :: col integer, intent(in) :: sub qn%f = flv; qn%c = col; qn%sub = sub end subroutine quantum_numbers_init_fcs impure elemental subroutine quantum_numbers_init_fhcs (qn, flv, hel, col, sub) class(quantum_numbers_t), intent(out) :: qn type(flavor_t), intent(in) :: flv type(helicity_t), intent(in) :: hel type(color_t), intent(in) :: col integer, intent(in) :: sub qn%f = flv; qn%h = hel; qn%c = col; qn%sub = sub end subroutine quantum_numbers_init_fhcs @ %def quantum_numbers_init @ \subsection{I/O} Write the quantum numbers in condensed form, enclosed by square brackets. Color is written only if nontrivial. For convenience, introduce also an array version. If the [[col_verbose]] option is set, show the quantum number color also if it is zero, but defined. Otherwise, suppress zero color. <>= public :: quantum_numbers_write <>= procedure :: write => quantum_numbers_write_single <>= interface quantum_numbers_write module procedure quantum_numbers_write_single module procedure quantum_numbers_write_array end interface <>= subroutine quantum_numbers_write_single (qn, unit, col_verbose) class(quantum_numbers_t), intent(in) :: qn integer, intent(in), optional :: unit logical, intent(in), optional :: col_verbose integer :: u logical :: col_verb u = given_output_unit (unit); if (u < 0) return col_verb = .false.; if (present (col_verbose)) col_verb = col_verbose write (u, "(A)", advance = "no") "[" if (qn%f%is_defined ()) then call qn%f%write (u) if (qn%c%is_nonzero () .or. qn%h%is_defined ()) & write (u, "(1x)", advance = "no") end if if (col_verb) then if (qn%c%is_defined () .or. qn%c%is_ghost ()) then call color_write (qn%c, u) if (qn%h%is_defined ()) write (u, "(1x)", advance = "no") end if else if (qn%c%is_nonzero () .or. qn%c%is_ghost ()) then call color_write (qn%c, u) if (qn%h%is_defined ()) write (u, "(1x)", advance = "no") end if end if if (qn%h%is_defined ()) then call qn%h%write (u) end if if (qn%sub > 0) & write (u, "(A,I0)", advance = "no") " SUB = ", qn%sub write (u, "(A)", advance="no") "]" end subroutine quantum_numbers_write_single subroutine quantum_numbers_write_array (qn, unit, col_verbose) type(quantum_numbers_t), dimension(:), intent(in) :: qn integer, intent(in), optional :: unit logical, intent(in), optional :: col_verbose integer :: i integer :: u logical :: col_verb u = given_output_unit (unit); if (u < 0) return col_verb = .false.; if (present (col_verbose)) col_verb = col_verbose write (u, "(A)", advance="no") "[" do i = 1, size (qn) if (i > 1) write (u, "(A)", advance="no") " / " if (qn(i)%f%is_defined ()) then call qn(i)%f%write (u) if (qn(i)%c%is_nonzero () .or. qn(i)%h%is_defined ()) & write (u, "(1x)", advance="no") end if if (col_verb) then if (qn(i)%c%is_defined () .or. qn(i)%c%is_ghost ()) then call color_write (qn(i)%c, u) if (qn(i)%h%is_defined ()) write (u, "(1x)", advance="no") end if else if (qn(i)%c%is_nonzero () .or. qn(i)%c%is_ghost ()) then call color_write (qn(i)%c, u) if (qn(i)%h%is_defined ()) write (u, "(1x)", advance="no") end if end if if (qn(i)%h%is_defined ()) then call qn(i)%h%write (u) end if if (qn(i)%sub > 0) & write (u, "(A,I2)", advance = "no") " SUB = ", qn(i)%sub end do write (u, "(A)", advance = "no") "]" end subroutine quantum_numbers_write_array @ %def quantum_numbers_write @ Binary I/O. <>= procedure :: write_raw => quantum_numbers_write_raw procedure :: read_raw => quantum_numbers_read_raw <>= subroutine quantum_numbers_write_raw (qn, u) class(quantum_numbers_t), intent(in) :: qn integer, intent(in) :: u call qn%f%write_raw (u) call qn%c%write_raw (u) call qn%h%write_raw (u) end subroutine quantum_numbers_write_raw subroutine quantum_numbers_read_raw (qn, u, iostat) class(quantum_numbers_t), intent(out) :: qn integer, intent(in) :: u integer, intent(out), optional :: iostat call qn%f%read_raw (u, iostat=iostat) call qn%c%read_raw (u, iostat=iostat) call qn%h%read_raw (u, iostat=iostat) end subroutine quantum_numbers_read_raw @ %def quantum_numbers_write_raw quantum_numbers_read_raw @ \subsection{Accessing contents} Color and helicity can be done by elemental functions. Flavor needs impure elemental. We export also the functions directly, this allows us to avoid temporaries in some places. <>= public :: quantum_numbers_get_flavor public :: quantum_numbers_get_color public :: quantum_numbers_get_helicity <>= procedure :: get_flavor => quantum_numbers_get_flavor procedure :: get_color => quantum_numbers_get_color procedure :: get_helicity => quantum_numbers_get_helicity procedure :: get_sub => quantum_numbers_get_sub <>= impure elemental function quantum_numbers_get_flavor (qn) result (flv) type(flavor_t) :: flv class(quantum_numbers_t), intent(in) :: qn flv = qn%f end function quantum_numbers_get_flavor elemental function quantum_numbers_get_color (qn) result (col) type(color_t) :: col class(quantum_numbers_t), intent(in) :: qn col = qn%c end function quantum_numbers_get_color elemental function quantum_numbers_get_helicity (qn) result (hel) type(helicity_t) :: hel class(quantum_numbers_t), intent(in) :: qn hel = qn%h end function quantum_numbers_get_helicity elemental function quantum_numbers_get_sub (qn) result (sub) integer :: sub class(quantum_numbers_t), intent(in) :: qn sub = qn%sub end function quantum_numbers_get_sub @ %def quantum_numbers_get_flavor @ %def quantum_numbers_get_color @ %def quantum_numbers_get_helicity @ %def quantum_numbers_get_sub @ This just resets the ghost property of the color part: <>= procedure :: set_color_ghost => quantum_numbers_set_color_ghost <>= elemental subroutine quantum_numbers_set_color_ghost (qn, ghost) class(quantum_numbers_t), intent(inout) :: qn logical, intent(in) :: ghost call qn%c%set_ghost (ghost) end subroutine quantum_numbers_set_color_ghost @ %def quantum_numbers_set_color_ghost @ Assign a model to the flavor part of quantum numbers. <>= procedure :: set_model => quantum_numbers_set_model <>= impure elemental subroutine quantum_numbers_set_model (qn, model) class(quantum_numbers_t), intent(inout) :: qn class(model_data_t), intent(in), target :: model call qn%f%set_model (model) end subroutine quantum_numbers_set_model @ %def quantum_numbers_set_model @ Set the [[radiated]] flag for the flavor component. <>= procedure :: tag_radiated => quantum_numbers_tag_radiated <>= elemental subroutine quantum_numbers_tag_radiated (qn) class(quantum_numbers_t), intent(inout) :: qn call qn%f%tag_radiated () end subroutine quantum_numbers_tag_radiated @ %def quantum_numbers_tag_radiated @ Set the [[hard_process]] flag for the flavor component. <>= procedure :: tag_hard_process => quantum_numbers_tag_hard_process <>= elemental subroutine quantum_numbers_tag_hard_process (qn, hard) class(quantum_numbers_t), intent(inout) :: qn logical, intent(in), optional :: hard call qn%f%tag_hard_process (hard) end subroutine quantum_numbers_tag_hard_process @ %def quantum_numbers_tag_hard_process @ <>= procedure :: set_subtraction_index => quantum_numbers_set_subtraction_index <>= elemental subroutine quantum_numbers_set_subtraction_index (qn, i) class(quantum_numbers_t), intent(inout) :: qn integer, intent(in) :: i qn%sub = i end subroutine quantum_numbers_set_subtraction_index @ %def quantum_numbers_set_subtraction_index @ <>= procedure :: get_subtraction_index => quantum_numbers_get_subtraction_index <>= elemental function quantum_numbers_get_subtraction_index (qn) result (sub) integer :: sub class(quantum_numbers_t), intent(in) :: qn sub = qn%sub end function quantum_numbers_get_subtraction_index @ %def quantum_numbers_get_subtraction_index @ This is a convenience function: return the color type for the flavor (array). <>= procedure :: get_color_type => quantum_numbers_get_color_type <>= elemental function quantum_numbers_get_color_type (qn) result (color_type) integer :: color_type class(quantum_numbers_t), intent(in) :: qn color_type = qn%f%get_color_type () end function quantum_numbers_get_color_type @ %def quantum_numbers_get_color_type @ \subsection{Predicates} Check if the flavor index is valid (including UNDEFINED). <>= procedure :: are_valid => quantum_numbers_are_valid <>= elemental function quantum_numbers_are_valid (qn) result (valid) logical :: valid class(quantum_numbers_t), intent(in) :: qn valid = qn%f%is_valid () end function quantum_numbers_are_valid @ %def quantum_numbers_are_valid @ Check if the flavor part has its particle-data pointer associated (debugging aid). <>= procedure :: are_associated => quantum_numbers_are_associated <>= elemental function quantum_numbers_are_associated (qn) result (flag) logical :: flag class(quantum_numbers_t), intent(in) :: qn flag = qn%f%is_associated () end function quantum_numbers_are_associated @ %def quantum_numbers_are_associated @ Check if the helicity and color quantum numbers are diagonal. (Unpolarized/colorless also counts as diagonal.) Flavor is diagonal by definition. <>= procedure :: are_diagonal => quantum_numbers_are_diagonal <>= elemental function quantum_numbers_are_diagonal (qn) result (diagonal) logical :: diagonal class(quantum_numbers_t), intent(in) :: qn diagonal = qn%h%is_diagonal () .and. qn%c%is_diagonal () end function quantum_numbers_are_diagonal @ %def quantum_numbers_are_diagonal @ Check if the color part has the ghost property. <>= procedure :: is_color_ghost => quantum_numbers_is_color_ghost <>= elemental function quantum_numbers_is_color_ghost (qn) result (ghost) logical :: ghost class(quantum_numbers_t), intent(in) :: qn ghost = qn%c%is_ghost () end function quantum_numbers_is_color_ghost @ %def quantum_numbers_is_color_ghost @ Check if the flavor participates in the hard interaction. <>= procedure :: are_hard_process => quantum_numbers_are_hard_process <>= elemental function quantum_numbers_are_hard_process (qn) result (hard_process) logical :: hard_process class(quantum_numbers_t), intent(in) :: qn hard_process = qn%f%is_hard_process () end function quantum_numbers_are_hard_process @ %def quantum_numbers_are_hard_process @ \subsection{Comparisons} Matching and equality is derived from the individual quantum numbers. The variant [[fhmatch]] matches only flavor and helicity. The variant [[dhmatch]] matches only diagonal helicity, if the matching helicity is undefined. <>= public :: quantum_numbers_eq_wo_sub <>= generic :: operator(.match.) => quantum_numbers_match generic :: operator(.fmatch.) => quantum_numbers_match_f generic :: operator(.hmatch.) => quantum_numbers_match_h generic :: operator(.fhmatch.) => quantum_numbers_match_fh generic :: operator(.dhmatch.) => quantum_numbers_match_hel_diag generic :: operator(==) => quantum_numbers_eq generic :: operator(/=) => quantum_numbers_neq procedure, private :: quantum_numbers_match procedure, private :: quantum_numbers_match_f procedure, private :: quantum_numbers_match_h procedure, private :: quantum_numbers_match_fh procedure, private :: quantum_numbers_match_hel_diag procedure, private :: quantum_numbers_eq procedure, private :: quantum_numbers_neq @ %def .match. == /= <>= elemental function quantum_numbers_match (qn1, qn2) result (match) logical :: match class(quantum_numbers_t), intent(in) :: qn1, qn2 match = (qn1%f .match. qn2%f) .and. & (qn1%c .match. qn2%c) .and. & (qn1%h .match. qn2%h) end function quantum_numbers_match elemental function quantum_numbers_match_f (qn1, qn2) result (match) logical :: match class(quantum_numbers_t), intent(in) :: qn1, qn2 match = (qn1%f .match. qn2%f) end function quantum_numbers_match_f elemental function quantum_numbers_match_h (qn1, qn2) result (match) logical :: match class(quantum_numbers_t), intent(in) :: qn1, qn2 match = (qn1%h .match. qn2%h) end function quantum_numbers_match_h elemental function quantum_numbers_match_fh (qn1, qn2) result (match) logical :: match class(quantum_numbers_t), intent(in) :: qn1, qn2 match = (qn1%f .match. qn2%f) .and. & (qn1%h .match. qn2%h) end function quantum_numbers_match_fh elemental function quantum_numbers_match_hel_diag (qn1, qn2) result (match) logical :: match class(quantum_numbers_t), intent(in) :: qn1, qn2 match = (qn1%f .match. qn2%f) .and. & (qn1%c .match. qn2%c) .and. & (qn1%h .dmatch. qn2%h) end function quantum_numbers_match_hel_diag elemental function quantum_numbers_eq_wo_sub (qn1, qn2) result (eq) logical :: eq type(quantum_numbers_t), intent(in) :: qn1, qn2 eq = (qn1%f == qn2%f) .and. & (qn1%c == qn2%c) .and. & (qn1%h == qn2%h) end function quantum_numbers_eq_wo_sub elemental function quantum_numbers_eq (qn1, qn2) result (eq) logical :: eq class(quantum_numbers_t), intent(in) :: qn1, qn2 eq = (qn1%f == qn2%f) .and. & (qn1%c == qn2%c) .and. & (qn1%h == qn2%h) .and. & (qn1%sub == qn2%sub) end function quantum_numbers_eq elemental function quantum_numbers_neq (qn1, qn2) result (neq) logical :: neq class(quantum_numbers_t), intent(in) :: qn1, qn2 neq = (qn1%f /= qn2%f) .or. & (qn1%c /= qn2%c) .or. & (qn1%h /= qn2%h) .or. & (qn1%sub /= qn2%sub) end function quantum_numbers_neq @ %def quantum_numbers_match @ %def quantum_numbers_eq @ %def quantum_numbers_neq <>= public :: assignment(=) <>= interface assignment(=) module procedure quantum_numbers_assign end interface <>= subroutine quantum_numbers_assign (qn_out, qn_in) type(quantum_numbers_t), intent(out) :: qn_out type(quantum_numbers_t), intent(in) :: qn_in qn_out%f = qn_in%f qn_out%c = qn_in%c qn_out%h = qn_in%h qn_out%sub = qn_in%sub end subroutine quantum_numbers_assign @ %def quantum_numbers_assign @ Two sets of quantum numbers are compatible if the individual quantum numbers are compatible, depending on the mask. Flavor has to match, regardless of the flavor mask. If the color flag is set, color is compatible if the ghost property is identical. If the color flag is unset, color has to be identical. I.e., if the flag is set, the color amplitudes can interfere. If it is not set, they must be identical, and there must be no ghost. The latter property is used for expanding physical color flows. Helicity is compatible if the mask is unset, otherwise it has to match. This determines if two amplitudes can be multiplied (no mask) or traced (mask). <>= public :: quantum_numbers_are_compatible <>= elemental function quantum_numbers_are_compatible (qn1, qn2, mask) & result (flag) logical :: flag type(quantum_numbers_t), intent(in) :: qn1, qn2 type(quantum_numbers_mask_t), intent(in) :: mask if (mask%h .or. mask%hd) then flag = (qn1%f .match. qn2%f) .and. (qn1%h .match. qn2%h) else flag = (qn1%f .match. qn2%f) end if if (mask%c) then flag = flag .and. (qn1%c%is_ghost () .eqv. qn2%c%is_ghost ()) else flag = flag .and. & .not. (qn1%c%is_ghost () .or. qn2%c%is_ghost ()) .and. & (qn1%c == qn2%c) end if end function quantum_numbers_are_compatible @ %def quantum_numbers_are_compatible @ This is the analog for a single quantum-number set. We just check for color ghosts; they are excluded if the color mask is unset (color-flow expansion). <>= public :: quantum_numbers_are_physical <>= elemental function quantum_numbers_are_physical (qn, mask) result (flag) logical :: flag type(quantum_numbers_t), intent(in) :: qn type(quantum_numbers_mask_t), intent(in) :: mask if (mask%c) then flag = .true. else flag = .not. qn%c%is_ghost () end if end function quantum_numbers_are_physical @ %def quantum_numbers_are_physical @ \subsection{Operations} Inherited from the color component: reassign color indices in canonical order. <>= public :: quantum_numbers_canonicalize_color <>= subroutine quantum_numbers_canonicalize_color (qn) type(quantum_numbers_t), dimension(:), intent(inout) :: qn call color_canonicalize (qn%c) end subroutine quantum_numbers_canonicalize_color @ %def quantum_numbers_canonicalize_color @ Inherited from the color component: make a color map for two matching quantum-number arrays. <>= public :: make_color_map <>= interface make_color_map module procedure quantum_numbers_make_color_map end interface make_color_map <>= subroutine quantum_numbers_make_color_map (map, qn1, qn2) integer, dimension(:,:), intent(out), allocatable :: map type(quantum_numbers_t), dimension(:), intent(in) :: qn1, qn2 call make_color_map (map, qn1%c, qn2%c) end subroutine quantum_numbers_make_color_map @ %def make_color_map @ Inherited from the color component: translate the color part using a color-map array <>= public :: quantum_numbers_translate_color <>= interface quantum_numbers_translate_color module procedure quantum_numbers_translate_color0 module procedure quantum_numbers_translate_color1 end interface <>= subroutine quantum_numbers_translate_color0 (qn, map, offset) type(quantum_numbers_t), intent(inout) :: qn integer, dimension(:,:), intent(in) :: map integer, intent(in), optional :: offset call color_translate (qn%c, map, offset) end subroutine quantum_numbers_translate_color0 subroutine quantum_numbers_translate_color1 (qn, map, offset) type(quantum_numbers_t), dimension(:), intent(inout) :: qn integer, dimension(:,:), intent(in) :: map integer, intent(in), optional :: offset call color_translate (qn%c, map, offset) end subroutine quantum_numbers_translate_color1 @ %def quantum_numbers_translate_color @ Inherited from the color component: return the color index with highest absolute value. Since the algorithm is not elemental, we keep the separate procedures for different array rank. <>= public :: quantum_numbers_get_max_color_value <>= interface quantum_numbers_get_max_color_value module procedure quantum_numbers_get_max_color_value0 module procedure quantum_numbers_get_max_color_value1 module procedure quantum_numbers_get_max_color_value2 end interface <>= pure function quantum_numbers_get_max_color_value0 (qn) result (cmax) integer :: cmax type(quantum_numbers_t), intent(in) :: qn cmax = color_get_max_value (qn%c) end function quantum_numbers_get_max_color_value0 pure function quantum_numbers_get_max_color_value1 (qn) result (cmax) integer :: cmax type(quantum_numbers_t), dimension(:), intent(in) :: qn cmax = color_get_max_value (qn%c) end function quantum_numbers_get_max_color_value1 pure function quantum_numbers_get_max_color_value2 (qn) result (cmax) integer :: cmax type(quantum_numbers_t), dimension(:,:), intent(in) :: qn cmax = color_get_max_value (qn%c) end function quantum_numbers_get_max_color_value2 @ Inherited from the color component: add an offset to the indices of the color part <>= procedure :: add_color_offset => quantum_numbers_add_color_offset <>= elemental subroutine quantum_numbers_add_color_offset (qn, offset) class(quantum_numbers_t), intent(inout) :: qn integer, intent(in) :: offset call qn%c%add_offset (offset) end subroutine quantum_numbers_add_color_offset @ %def quantum_numbers_add_color_offset @ Given a quantum number array, return all possible color contractions, leaving the other quantum numbers intact. <>= public :: quantum_number_array_make_color_contractions <>= subroutine quantum_number_array_make_color_contractions (qn_in, qn_out) type(quantum_numbers_t), dimension(:), intent(in) :: qn_in type(quantum_numbers_t), dimension(:,:), intent(out), allocatable :: qn_out type(color_t), dimension(:,:), allocatable :: col integer :: i call color_array_make_contractions (qn_in%c, col) allocate (qn_out (size (col, 1), size (col, 2))) do i = 1, size (qn_out, 2) qn_out(:,i)%f = qn_in%f qn_out(:,i)%c = col(:,i) qn_out(:,i)%h = qn_in%h end do end subroutine quantum_number_array_make_color_contractions @ %def quantum_number_array_make_color_contractions @ Inherited from the color component: invert the color, switching particle/antiparticle. <>= procedure :: invert_color => quantum_numbers_invert_color <>= elemental subroutine quantum_numbers_invert_color (qn) class(quantum_numbers_t), intent(inout) :: qn call qn%c%invert () end subroutine quantum_numbers_invert_color @ %def quantum_numbers_invert_color @ Flip helicity. <>= procedure :: flip_helicity => quantum_numbers_flip_helicity <>= elemental subroutine quantum_numbers_flip_helicity (qn) class(quantum_numbers_t), intent(inout) :: qn call qn%h%flip () end subroutine quantum_numbers_flip_helicity @ %def quantum_numbers_flip_helicity @ Merge two quantum number sets: for each entry, if both are defined, combine them to an off-diagonal entry (meaningful only if the input was diagonal). If either entry is undefined, take the defined one. For flavor, off-diagonal entries are invalid, so both flavors must be equal, otherwise an invalid flavor is inserted. <>= public :: operator(.merge.) <>= interface operator(.merge.) module procedure merge_quantum_numbers0 module procedure merge_quantum_numbers1 end interface <>= function merge_quantum_numbers0 (qn1, qn2) result (qn3) type(quantum_numbers_t) :: qn3 type(quantum_numbers_t), intent(in) :: qn1, qn2 qn3%f = qn1%f .merge. qn2%f qn3%c = qn1%c .merge. qn2%c qn3%h = qn1%h .merge. qn2%h qn3%sub = merge_subtraction_index (qn1%sub, qn2%sub) end function merge_quantum_numbers0 function merge_quantum_numbers1 (qn1, qn2) result (qn3) type(quantum_numbers_t), dimension(:), intent(in) :: qn1, qn2 type(quantum_numbers_t), dimension(size(qn1)) :: qn3 qn3%f = qn1%f .merge. qn2%f qn3%c = qn1%c .merge. qn2%c qn3%h = qn1%h .merge. qn2%h qn3%sub = merge_subtraction_index (qn1%sub, qn2%sub) end function merge_quantum_numbers1 @ %def merge_quantum_numbers @ <>= elemental function merge_subtraction_index (sub1, sub2) result (sub3) integer :: sub3 integer, intent(in) :: sub1, sub2 if (sub1 > 0 .and. sub2 > 0) then if (sub1 == sub2) then sub3 = sub1 else sub3 = 0 end if else if (sub1 > 0) then sub3 = sub1 else if (sub2 > 0) then sub3 = sub2 else sub3 = 0 end if end function merge_subtraction_index @ %def merge_subtraction_index @ \subsection{The quantum number mask} The quantum numbers mask is true for quantum numbers that should be ignored or summed over. The three mandatory entries correspond to flavor, color, and helicity, respectively. There is an additional entry [[cg]]: If false, the color-ghosts property should be kept even if color is ignored. This is relevant only if [[c]] is set, otherwise it is always false. The flag [[hd]] tells that only diagonal entries in helicity should be kept. If [[h]] is set, [[hd]] is irrelevant and will be kept [[.false.]] <>= public :: quantum_numbers_mask_t <>= type :: quantum_numbers_mask_t private logical :: f = .false. logical :: c = .false. logical :: cg = .false. logical :: h = .false. logical :: hd = .false. integer :: sub = 0 contains <> end type quantum_numbers_mask_t @ %def quantum_number_t @ Define a quantum number mask: Constructor form <>= public :: quantum_numbers_mask <>= elemental function quantum_numbers_mask & (mask_f, mask_c, mask_h, mask_cg, mask_hd) result (mask) type(quantum_numbers_mask_t) :: mask logical, intent(in) :: mask_f, mask_c, mask_h logical, intent(in), optional :: mask_cg logical, intent(in), optional :: mask_hd call quantum_numbers_mask_init & (mask, mask_f, mask_c, mask_h, mask_cg, mask_hd) end function quantum_numbers_mask @ %def new_quantum_numbers_mask @ Define quantum numbers: Initializer form <>= procedure :: init => quantum_numbers_mask_init <>= elemental subroutine quantum_numbers_mask_init & (mask, mask_f, mask_c, mask_h, mask_cg, mask_hd) class(quantum_numbers_mask_t), intent(inout) :: mask logical, intent(in) :: mask_f, mask_c, mask_h logical, intent(in), optional :: mask_cg, mask_hd mask%f = mask_f mask%c = mask_c mask%h = mask_h mask%cg = .false. if (present (mask_cg)) then if (mask%c) mask%cg = mask_cg else mask%cg = mask_c end if mask%hd = .false. if (present (mask_hd)) then if (.not. mask%h) mask%hd = mask_hd end if end subroutine quantum_numbers_mask_init @ %def quantum_numbers_mask_init @ Write a quantum numbers mask. We need the stand-alone subroutine for the array case. <>= public :: quantum_numbers_mask_write <>= interface quantum_numbers_mask_write module procedure quantum_numbers_mask_write_single module procedure quantum_numbers_mask_write_array end interface <>= procedure :: write => quantum_numbers_mask_write_single <>= subroutine quantum_numbers_mask_write_single (mask, unit) class(quantum_numbers_mask_t), intent(in) :: mask integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit); if (u < 0) return write (u, "(A)", advance="no") "[" write (u, "(L1)", advance="no") mask%f write (u, "(L1)", advance="no") mask%c if (.not.mask%cg) write (u, "('g')", advance="no") write (u, "(L1)", advance="no") mask%h if (mask%hd) write (u, "('d')", advance="no") write (u, "(A)", advance="no") "]" end subroutine quantum_numbers_mask_write_single subroutine quantum_numbers_mask_write_array (mask, unit) type(quantum_numbers_mask_t), dimension(:), intent(in) :: mask integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit); if (u < 0) return write (u, "(A)", advance="no") "[" do i = 1, size (mask) if (i > 1) write (u, "(A)", advance="no") "/" write (u, "(L1)", advance="no") mask(i)%f write (u, "(L1)", advance="no") mask(i)%c if (.not.mask(i)%cg) write (u, "('g')", advance="no") write (u, "(L1)", advance="no") mask(i)%h if (mask(i)%hd) write (u, "('d')", advance="no") end do write (u, "(A)", advance="no") "]" end subroutine quantum_numbers_mask_write_array @ %def quantum_numbers_mask_write @ \subsection{Setting mask components} <>= procedure :: set_flavor => quantum_numbers_mask_set_flavor procedure :: set_color => quantum_numbers_mask_set_color procedure :: set_helicity => quantum_numbers_mask_set_helicity procedure :: set_sub => quantum_numbers_mask_set_sub <>= elemental subroutine quantum_numbers_mask_set_flavor (mask, mask_f) class(quantum_numbers_mask_t), intent(inout) :: mask logical, intent(in) :: mask_f mask%f = mask_f end subroutine quantum_numbers_mask_set_flavor elemental subroutine quantum_numbers_mask_set_color (mask, mask_c, mask_cg) class(quantum_numbers_mask_t), intent(inout) :: mask logical, intent(in) :: mask_c logical, intent(in), optional :: mask_cg mask%c = mask_c if (present (mask_cg)) then if (mask%c) mask%cg = mask_cg else mask%cg = mask_c end if end subroutine quantum_numbers_mask_set_color elemental subroutine quantum_numbers_mask_set_helicity (mask, mask_h, mask_hd) class(quantum_numbers_mask_t), intent(inout) :: mask logical, intent(in) :: mask_h logical, intent(in), optional :: mask_hd mask%h = mask_h if (present (mask_hd)) then if (.not. mask%h) mask%hd = mask_hd end if end subroutine quantum_numbers_mask_set_helicity elemental subroutine quantum_numbers_mask_set_sub (mask, sub) class(quantum_numbers_mask_t), intent(inout) :: mask integer, intent(in) :: sub mask%sub = sub end subroutine quantum_numbers_mask_set_sub @ %def quantum_numbers_mask_set_flavor @ %def quantum_numbers_mask_set_color @ %def quantum_numbers_mask_set_helicity @ %def quantum_numbers_mask_set_sub @ The following routines assign part of a mask, depending on the flags given. <>= procedure :: assign => quantum_numbers_mask_assign <>= elemental subroutine quantum_numbers_mask_assign & (mask, mask_in, flavor, color, helicity) class(quantum_numbers_mask_t), intent(inout) :: mask class(quantum_numbers_mask_t), intent(in) :: mask_in logical, intent(in), optional :: flavor, color, helicity if (present (flavor)) then if (flavor) then mask%f = mask_in%f end if end if if (present (color)) then if (color) then mask%c = mask_in%c mask%cg = mask_in%cg end if end if if (present (helicity)) then if (helicity) then mask%h = mask_in%h mask%hd = mask_in%hd end if end if end subroutine quantum_numbers_mask_assign @ %def quantum_numbers_mask_assign @ \subsection{Mask predicates} Return true if either one of the entries is set: <>= public :: any <>= interface any module procedure quantum_numbers_mask_any end interface <>= function quantum_numbers_mask_any (mask) result (match) logical :: match type(quantum_numbers_mask_t), intent(in) :: mask match = mask%f .or. mask%c .or. mask%h .or. mask%hd end function quantum_numbers_mask_any @ %def any @ \subsection{Operators} The OR operation is applied to all components. <>= generic :: operator(.or.) => quantum_numbers_mask_or procedure, private :: quantum_numbers_mask_or @ %def .or. <>= elemental function quantum_numbers_mask_or (mask1, mask2) result (mask) type(quantum_numbers_mask_t) :: mask class(quantum_numbers_mask_t), intent(in) :: mask1, mask2 mask%f = mask1%f .or. mask2%f mask%c = mask1%c .or. mask2%c if (mask%c) mask%cg = mask1%cg .or. mask2%cg mask%h = mask1%h .or. mask2%h if (.not. mask%h) mask%hd = mask1%hd .or. mask2%hd end function quantum_numbers_mask_or @ %def quantum_numbers_mask_or @ \subsection{Mask comparisons} Return true if the two masks are equivalent / differ: <>= generic :: operator(.eqv.) => quantum_numbers_mask_eqv generic :: operator(.neqv.) => quantum_numbers_mask_neqv procedure, private :: quantum_numbers_mask_eqv procedure, private :: quantum_numbers_mask_neqv <>= elemental function quantum_numbers_mask_eqv (mask1, mask2) result (eqv) logical :: eqv class(quantum_numbers_mask_t), intent(in) :: mask1, mask2 eqv = (mask1%f .eqv. mask2%f) .and. & (mask1%c .eqv. mask2%c) .and. & (mask1%cg .eqv. mask2%cg) .and. & (mask1%h .eqv. mask2%h) .and. & (mask1%hd .eqv. mask2%hd) end function quantum_numbers_mask_eqv elemental function quantum_numbers_mask_neqv (mask1, mask2) result (neqv) logical :: neqv class(quantum_numbers_mask_t), intent(in) :: mask1, mask2 neqv = (mask1%f .neqv. mask2%f) .or. & (mask1%c .neqv. mask2%c) .or. & (mask1%cg .neqv. mask2%cg) .or. & (mask1%h .neqv. mask2%h) .or. & (mask1%hd .neqv. mask2%hd) end function quantum_numbers_mask_neqv @ %def .eqv. .neqv. @ \subsection{Apply a mask} Applying a mask to the quantum number object means undefining those entries where the mask is set. The others remain unaffected. The [[hd]] mask has the special property that it ``diagonalizes'' helicity, i.e., the second helicity entry is dropped and the result is a diagonal helicity quantum number. <>= procedure :: undefine => quantum_numbers_undefine procedure :: undefined => quantum_numbers_undefined0 <>= public :: quantum_numbers_undefined <>= interface quantum_numbers_undefined module procedure quantum_numbers_undefined0 module procedure quantum_numbers_undefined1 module procedure quantum_numbers_undefined11 end interface <>= elemental subroutine quantum_numbers_undefine (qn, mask) class(quantum_numbers_t), intent(inout) :: qn type(quantum_numbers_mask_t), intent(in) :: mask if (mask%f) call qn%f%undefine () if (mask%c) call qn%c%undefine (undefine_ghost = mask%cg) if (mask%h) then call qn%h%undefine () else if (mask%hd) then if (.not. qn%h%is_diagonal ()) then call qn%h%diagonalize () end if end if if (mask%sub > 0) qn%sub = 0 end subroutine quantum_numbers_undefine function quantum_numbers_undefined0 (qn, mask) result (qn_new) class(quantum_numbers_t), intent(in) :: qn type(quantum_numbers_mask_t), intent(in) :: mask type(quantum_numbers_t) :: qn_new select type (qn) type is (quantum_numbers_t); qn_new = qn end select call quantum_numbers_undefine (qn_new, mask) end function quantum_numbers_undefined0 function quantum_numbers_undefined1 (qn, mask) result (qn_new) type(quantum_numbers_t), dimension(:), intent(in) :: qn type(quantum_numbers_mask_t), intent(in) :: mask type(quantum_numbers_t), dimension(size(qn)) :: qn_new qn_new = qn call quantum_numbers_undefine (qn_new, mask) end function quantum_numbers_undefined1 function quantum_numbers_undefined11 (qn, mask) result (qn_new) type(quantum_numbers_t), dimension(:), intent(in) :: qn type(quantum_numbers_mask_t), dimension(:), intent(in) :: mask type(quantum_numbers_t), dimension(size(qn)) :: qn_new qn_new = qn call quantum_numbers_undefine (qn_new, mask) end function quantum_numbers_undefined11 @ %def quantum_numbers_undefine @ %def quantum_numbers_undefined @ Return true if the input quantum number set has entries that would be removed by the applied mask, e.g., if polarization is defined but [[mask%h]] is set: <>= procedure :: are_redundant => quantum_numbers_are_redundant <>= elemental function quantum_numbers_are_redundant (qn, mask) & result (redundant) logical :: redundant class(quantum_numbers_t), intent(in) :: qn type(quantum_numbers_mask_t), intent(in) :: mask redundant = .false. if (mask%f) then redundant = qn%f%is_defined () end if if (mask%c) then redundant = qn%c%is_defined () end if if (mask%h) then redundant = qn%h%is_defined () else if (mask%hd) then redundant = .not. qn%h%is_diagonal () end if if (mask%sub > 0) redundant = qn%sub >= mask%sub end function quantum_numbers_are_redundant @ %def quantum_numbers_are_redundant @ Return true if the helicity flag is set or the diagonal-helicity flag is set. <>= procedure :: diagonal_helicity => quantum_numbers_mask_diagonal_helicity <>= elemental function quantum_numbers_mask_diagonal_helicity (mask) & result (flag) logical :: flag class(quantum_numbers_mask_t), intent(in) :: mask flag = mask%h .or. mask%hd end function quantum_numbers_mask_diagonal_helicity @ %def quantum_numbers_mask_diagonal_helicity @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Transition Matrices and Evaluation} The modules in this chapter implement transition matrices and calculations. The functionality is broken down in three modules \begin{description} \item[state\_matrices] represent state and transition density matrices built from particle quantum numbers (helicity, color, flavor) \item[interactions] extend state matrices with the record of particle momenta. They also distinguish in- and out-particles and store parent-child relations. \item[evaluators] These objects extend interaction objects by the information how to calculate matrix elements from products and squares of other interactions. They implement the methods to actually compute those matrix elements. \end{description} \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{State matrices} This module deals with the internal state of a particle system, i.e., with its density matrix in flavor, color, and helicity space. <<[[state_matrices.f90]]>>= <> module state_matrices <> use constants, only: zero use io_units use format_utils, only: pac_fmt use format_defs, only: FMT_17, FMT_19 use diagnostics use sorting use model_data use flavors use colors use helicities use quantum_numbers <> <> <> <> <> contains <> end module state_matrices @ %def state_matrices @ \subsection{Nodes of the quantum state trie} A quantum state object represents an unnormalized density matrix, i.e., an array of possibilities for flavor, color, and helicity indices with associated complex values. Physically, the trace of this matrix is the summed squared matrix element for an interaction, and the matrix elements divided by this value correspond to the flavor-color-helicity density matrix. (Flavor and color are diagonal.) We store density matrices as tries, that is, as trees where each branching represents the possible quantum numbers of a particle. The first branching is the first particle in the system. A leaf (the node corresponding to the last particle) contains the value of the matrix element. Each node contains a flavor, color, and helicity entry. Note that each of those entries may be actually undefined, so we can also represent, e.g., unpolarized particles. The value is meaningful only for leaves, which have no child nodes. There is a pointer to the parent node which allows for following the trie downwards from a leaf, it is null for a root node. The child nodes are implemented as a list, so there is a pointer to the first and last child, and each node also has a [[next]] pointer to the next sibling. The root node does not correspond to a particle, only its children do. The quantum numbers of the root node are irrelevant and will not be set. However, we use a common type for the three classes (root, branch, leaf); they may easily be distinguished by the association status of parent and child. \subsubsection{Node type} The node is linked in all directions: the parent, the first and last in the list of children, and the previous and next sibling. This allows us for adding and removing nodes and whole branches anywhere in the trie. (Circular links are not allowed, however.). The node holds its associated set of quantum numbers. The integer index, which is set only for leaf nodes, is the index of the corresponding matrix element value within the state matrix. Temporarily, matrix-element values may be stored within a leaf node. This is used during state-matrix factorization. When the state matrix is [[freeze]]d, these values are transferred to the matrix-element array within the host state matrix. <>= type :: node_t private type(quantum_numbers_t) :: qn type(node_t), pointer :: parent => null () type(node_t), pointer :: child_first => null () type(node_t), pointer :: child_last => null () type(node_t), pointer :: next => null () type(node_t), pointer :: previous => null () integer :: me_index = 0 integer, dimension(:), allocatable :: me_count complex(default) :: me = 0 end type node_t @ %def node_t @ \subsubsection{Operations on nodes} Recursively deallocate all children of the current node. This includes any values associated with the children. <>= pure recursive subroutine node_delete_offspring (node) type(node_t), pointer :: node type(node_t), pointer :: child child => node%child_first do while (associated (child)) node%child_first => node%child_first%next call node_delete_offspring (child) deallocate (child) child => node%child_first end do node%child_last => null () end subroutine node_delete_offspring @ %def node_delete_offspring @ Remove a node including its offspring. Adjust the pointers of parent and siblings, if necessary. <>= pure subroutine node_delete (node) type(node_t), pointer :: node call node_delete_offspring (node) if (associated (node%previous)) then node%previous%next => node%next else if (associated (node%parent)) then node%parent%child_first => node%next end if if (associated (node%next)) then node%next%previous => node%previous else if (associated (node%parent)) then node%parent%child_last => node%previous end if deallocate (node) end subroutine node_delete @ %def node_delete @ Append a child node <>= subroutine node_append_child (node, child) type(node_t), target, intent(inout) :: node type(node_t), pointer :: child allocate (child) if (associated (node%child_last)) then node%child_last%next => child child%previous => node%child_last else node%child_first => child end if node%child_last => child child%parent => node end subroutine node_append_child @ %def node_append_child @ \subsubsection{I/O} Output of a single node, no recursion. We print the quantum numbers in square brackets, then the value (if any). <>= subroutine node_write (node, me_array, verbose, unit, col_verbose, testflag) type(node_t), intent(in) :: node complex(default), dimension(:), intent(in), optional :: me_array logical, intent(in), optional :: verbose, col_verbose, testflag integer, intent(in), optional :: unit logical :: verb integer :: u character(len=7) :: fmt call pac_fmt (fmt, FMT_19, FMT_17, testflag) verb = .false.; if (present (verbose)) verb = verbose u = given_output_unit (unit); if (u < 0) return call node%qn%write (u, col_verbose) if (node%me_index /= 0) then write (u, "(A,I0,A)", advance="no") " => ME(", node%me_index, ")" if (present (me_array)) then write (u, "(A)", advance="no") " = " write (u, "('('," // fmt // ",','," // fmt // ",')')", & advance="no") pacify_complex (me_array(node%me_index)) end if end if write (u, *) if (verb) then call ptr_write ("parent ", node%parent) call ptr_write ("child_first", node%child_first) call ptr_write ("child_last ", node%child_last) call ptr_write ("next ", node%next) call ptr_write ("previous ", node%previous) end if contains subroutine ptr_write (label, node) character(*), intent(in) :: label type(node_t), pointer :: node if (associated (node)) then write (u, "(10x,A,1x,'->',1x)", advance="no") label call node%qn%write (u, col_verbose) write (u, *) end if end subroutine ptr_write end subroutine node_write @ %def node_write @ Recursive output of a node: <>= recursive subroutine node_write_rec (node, me_array, verbose, & indent, unit, col_verbose, testflag) type(node_t), intent(in), target :: node complex(default), dimension(:), intent(in), optional :: me_array logical, intent(in), optional :: verbose, col_verbose, testflag integer, intent(in), optional :: indent integer, intent(in), optional :: unit type(node_t), pointer :: current logical :: verb integer :: i, u verb = .false.; if (present (verbose)) verb = verbose i = 0; if (present (indent)) i = indent u = given_output_unit (unit); if (u < 0) return current => node%child_first do while (associated (current)) write (u, "(A)", advance="no") repeat (" ", i) call node_write (current, me_array, verbose = verb, & unit = u, col_verbose = col_verbose, testflag = testflag) call node_write_rec (current, me_array, verbose = verb, & indent = i + 2, unit = u, col_verbose = col_verbose, testflag = testflag) current => current%next end do end subroutine node_write_rec @ %def node_write_rec @ Binary I/O. Matrix elements are written only for leaf nodes. <>= recursive subroutine node_write_raw_rec (node, u) type(node_t), intent(in), target :: node integer, intent(in) :: u logical :: associated_child_first, associated_next call node%qn%write_raw (u) associated_child_first = associated (node%child_first) write (u) associated_child_first associated_next = associated (node%next) write (u) associated_next if (associated_child_first) then call node_write_raw_rec (node%child_first, u) else write (u) node%me_index write (u) node%me end if if (associated_next) then call node_write_raw_rec (node%next, u) end if end subroutine node_write_raw_rec recursive subroutine node_read_raw_rec (node, u, parent, iostat) type(node_t), intent(out), target :: node integer, intent(in) :: u type(node_t), intent(in), optional, target :: parent integer, intent(out), optional :: iostat logical :: associated_child_first, associated_next type(node_t), pointer :: child call node%qn%read_raw (u, iostat=iostat) read (u, iostat=iostat) associated_child_first read (u, iostat=iostat) associated_next if (present (parent)) node%parent => parent if (associated_child_first) then allocate (child) node%child_first => child node%child_last => null () call node_read_raw_rec (child, u, node, iostat=iostat) do while (associated (child)) child%previous => node%child_last node%child_last => child child => child%next end do else read (u, iostat=iostat) node%me_index read (u, iostat=iostat) node%me end if if (associated_next) then allocate (node%next) call node_read_raw_rec (node%next, u, parent, iostat=iostat) end if end subroutine node_read_raw_rec @ %def node_write_raw @ \subsection{State matrix} \subsubsection{Definition} The quantum state object is a container that keeps and hides the root node. For direct accessibility of values, they are stored in a separate array. The leaf nodes of the quantum-number tree point to those values, once the state matrix is finalized. The [[norm]] component is redefined if a common factor is extracted from all nodes. <>= public :: state_matrix_t <>= type :: state_matrix_t private type(node_t), pointer :: root => null () integer :: depth = 0 integer :: n_matrix_elements = 0 logical :: leaf_nodes_store_values = .false. integer :: n_counters = 0 complex(default), dimension(:), allocatable :: me real(default) :: norm = 1 integer :: n_sub = -1 contains <> end type state_matrix_t @ %def state_matrix_t @ This initializer allocates the root node but does not fill anything. We declare whether values are stored within the nodes during state-matrix construction, and how many counters should be maintained (default: none). <>= procedure :: init => state_matrix_init <>= subroutine state_matrix_init (state, store_values, n_counters) class(state_matrix_t), intent(out) :: state logical, intent(in), optional :: store_values integer, intent(in), optional :: n_counters allocate (state%root) if (present (store_values)) & state%leaf_nodes_store_values = store_values if (present (n_counters)) state%n_counters = n_counters end subroutine state_matrix_init @ %def state_matrix_init @ This recursively deletes all children of the root node, restoring the initial state. The matrix element array is not finalized, since it does not contain physical entries, just pointers. <>= procedure :: final => state_matrix_final <>= subroutine state_matrix_final (state) class(state_matrix_t), intent(inout) :: state if (allocated (state%me)) deallocate (state%me) if (associated (state%root)) call node_delete (state%root) state%depth = 0 state%n_matrix_elements = 0 end subroutine state_matrix_final @ %def state_matrix_final @ Output: Present the tree as a nested list with appropriate indentation. <>= procedure :: write => state_matrix_write <>= subroutine state_matrix_write (state, unit, write_value_list, & verbose, col_verbose, testflag) class(state_matrix_t), intent(in) :: state logical, intent(in), optional :: write_value_list, verbose, col_verbose logical, intent(in), optional :: testflag integer, intent(in), optional :: unit complex(default) :: me_dum character(len=7) :: fmt integer :: u integer :: i call pac_fmt (fmt, FMT_19, FMT_17, testflag) u = given_output_unit (unit); if (u < 0) return write (u, "(1x,A," // fmt // ")") "State matrix: norm = ", state%norm if (associated (state%root)) then if (allocated (state%me)) then call node_write_rec (state%root, state%me, verbose = verbose, & indent = 1, unit = u, col_verbose = col_verbose, & testflag = testflag) else call node_write_rec (state%root, verbose = verbose, indent = 1, & unit = u, col_verbose = col_verbose, testflag = testflag) end if end if if (present (write_value_list)) then if (write_value_list .and. allocated (state%me)) then do i = 1, size (state%me) write (u, "(1x,I0,A)", advance="no") i, ":" me_dum = state%me(i) if (real(state%me(i)) == -real(state%me(i))) then me_dum = & cmplx (0._default, aimag(me_dum), kind=default) end if if (aimag(me_dum) == -aimag(me_dum)) then me_dum = & cmplx (real(me_dum), 0._default, kind=default) end if write (u, "('('," // fmt // ",','," // fmt // & ",')')") me_dum end do end if end if end subroutine state_matrix_write @ %def state_matrix_write @ Binary I/O. The auxiliary matrix-element array is not written, but reconstructed after reading the tree. Note: To be checked. Might be broken, don't use (unless trivial). <>= procedure :: write_raw => state_matrix_write_raw procedure :: read_raw => state_matrix_read_raw <>= subroutine state_matrix_write_raw (state, u) class(state_matrix_t), intent(in), target :: state integer, intent(in) :: u logical :: is_defined integer :: depth, j type(state_iterator_t) :: it type(quantum_numbers_t), dimension(:), allocatable :: qn is_defined = state%is_defined () write (u) is_defined if (is_defined) then write (u) state%get_norm () write (u) state%get_n_leaves () depth = state%get_depth () write (u) depth allocate (qn (depth)) call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () do j = 1, depth call qn(j)%write_raw (u) end do write (u) it%get_me_index () write (u) it%get_matrix_element () call it%advance () end do end if end subroutine state_matrix_write_raw subroutine state_matrix_read_raw (state, u, iostat) class(state_matrix_t), intent(out) :: state integer, intent(in) :: u integer, intent(out) :: iostat logical :: is_defined real(default) :: norm integer :: n_leaves, depth, i, j type(quantum_numbers_t), dimension(:), allocatable :: qn integer :: me_index complex(default) :: me read (u, iostat=iostat) is_defined if (iostat /= 0) goto 1 if (is_defined) then call state%init (store_values = .true.) read (u, iostat=iostat) norm if (iostat /= 0) goto 1 call state_matrix_set_norm (state, norm) read (u) n_leaves if (iostat /= 0) goto 1 read (u) depth if (iostat /= 0) goto 1 allocate (qn (depth)) do i = 1, n_leaves do j = 1, depth call qn(j)%read_raw (u, iostat=iostat) if (iostat /= 0) goto 1 end do read (u, iostat=iostat) me_index if (iostat /= 0) goto 1 read (u, iostat=iostat) me if (iostat /= 0) goto 1 call state%add_state (qn, index = me_index, value = me) end do call state_matrix_freeze (state) end if return ! Clean up on error 1 continue call state%final () end subroutine state_matrix_read_raw @ %def state_matrix_write_raw state_matrix_read_raw @ Assign a model pointer to all flavor entries. This will become necessary when we have read a state matrix from file. <>= procedure :: set_model => state_matrix_set_model <>= subroutine state_matrix_set_model (state, model) class(state_matrix_t), intent(inout), target :: state class(model_data_t), intent(in), target :: model type(state_iterator_t) :: it call it%init (state) do while (it%is_valid ()) call it%set_model (model) call it%advance () end do end subroutine state_matrix_set_model @ %def state_matrix_set_model @ Iterate over [[state]], get the quantum numbers array [[qn]] for each iteration, and tag all array elements of [[qn]] with the indizes given by [[tag]] as part of the hard interaction. Then add them to [[tagged_state]] and return it. If no [[tag]] is given, tag all [[qn]] as part of the hard process. <>= procedure :: tag_hard_process => state_matrix_tag_hard_process <>= subroutine state_matrix_tag_hard_process (state, tagged_state, tag) class(state_matrix_t), intent(in), target :: state type(state_matrix_t), intent(out) :: tagged_state integer, dimension(:), intent(in), optional :: tag type(state_iterator_t) :: it type(quantum_numbers_t), dimension(:), allocatable :: qn complex(default) :: value integer :: i call tagged_state%init (store_values = .true.) call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () value = it%get_matrix_element () if (present (tag)) then do i = 1, size (tag) call qn(tag(i))%tag_hard_process () end do else call qn%tag_hard_process () end if call tagged_state%add_state (qn, index = it%get_me_index (), value = value) call it%advance () end do call tagged_state%freeze () end subroutine state_matrix_tag_hard_process @ %def state_matrix_tag_hard_process \subsubsection{Properties of the quantum state} A state is defined if its root is allocated: <>= procedure :: is_defined => state_matrix_is_defined <>= elemental function state_matrix_is_defined (state) result (defined) logical :: defined class(state_matrix_t), intent(in) :: state defined = associated (state%root) end function state_matrix_is_defined @ %def state_matrix_is_defined @ A state is empty if its depth is zero: <>= procedure :: is_empty => state_matrix_is_empty <>= elemental function state_matrix_is_empty (state) result (flag) logical :: flag class(state_matrix_t), intent(in) :: state flag = state%depth == 0 end function state_matrix_is_empty @ %def state_matrix_is_empty @ Return the number of matrix-element values. <>= generic :: get_n_matrix_elements => get_n_matrix_elements_all, get_n_matrix_elements_mask procedure :: get_n_matrix_elements_all => state_matrix_get_n_matrix_elements_all procedure :: get_n_matrix_elements_mask => state_matrix_get_n_matrix_elements_mask <>= pure function state_matrix_get_n_matrix_elements_all (state) result (n) integer :: n class(state_matrix_t), intent(in) :: state n = state%n_matrix_elements end function state_matrix_get_n_matrix_elements_all @ %def state_matrix_get_n_matrix_elements_all @ <>= function state_matrix_get_n_matrix_elements_mask (state, qn_mask) result (n) integer :: n class(state_matrix_t), intent(in) :: state type(quantum_numbers_mask_t), intent(in), dimension(:) :: qn_mask type(state_iterator_t) :: it type(quantum_numbers_t), dimension(size(qn_mask)) :: qn type(state_matrix_t) :: state_tmp call state_tmp%init () call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () call qn%undefine (qn_mask) call state_tmp%add_state (qn) call it%advance () end do n = state_tmp%n_matrix_elements call state_tmp%final () end function state_matrix_get_n_matrix_elements_mask @ %def state_matrix_get_n_matrix_elments_mask @ Return the size of the [[me]]-array for debugging purposes. <>= procedure :: get_me_size => state_matrix_get_me_size <>= pure function state_matrix_get_me_size (state) result (n) integer :: n class(state_matrix_t), intent(in) :: state if (allocated (state%me)) then n = size (state%me) else n = 0 end if end function state_matrix_get_me_size @ %def state_matrix_get_me_size @ <>= procedure :: compute_n_sub => state_matrix_compute_n_sub <>= function state_matrix_compute_n_sub (state) result (n_sub) integer :: n_sub class(state_matrix_t), intent(in) :: state type(state_iterator_t) :: it type(quantum_numbers_t), dimension(state%depth) :: qn integer :: sub, sub_pos n_sub = 0 call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () sub = 0 sub_pos = qn_array_sub_pos () if (sub_pos > 0) sub = qn(sub_pos)%get_sub () if (sub > n_sub) n_sub = sub call it%advance () end do contains function qn_array_sub_pos () result (pos) integer :: pos integer :: i pos = 0 do i = 1, state%depth if (qn(i)%get_sub () > 0) then pos = i exit end if end do end function qn_array_sub_pos end function state_matrix_compute_n_sub @ %def state_matrix_compute_n_sub @ <>= procedure :: set_n_sub => state_matrix_set_n_sub <>= subroutine state_matrix_set_n_sub (state) class(state_matrix_t), intent(inout) :: state state%n_sub = state%compute_n_sub () end subroutine state_matrix_set_n_sub @ %def state_matrix_set_n_sub @ Return number of subtractions. <>= procedure :: get_n_sub => state_matrix_get_n_sub <>= function state_matrix_get_n_sub (state) result (n_sub) integer :: n_sub class(state_matrix_t), intent(in) :: state if (state%n_sub < 0) then call msg_bug ("[state_matrix_get_n_sub] number of subtractions not set.") end if n_sub = state%n_sub end function state_matrix_get_n_sub @ %def state_matrix_get_n_sub @ Return the number of leaves. This can be larger than the number of independent matrix elements. <>= procedure :: get_n_leaves => state_matrix_get_n_leaves <>= function state_matrix_get_n_leaves (state) result (n) integer :: n class(state_matrix_t), intent(in) :: state type(state_iterator_t) :: it n = 0 call it%init (state) do while (it%is_valid ()) n = n + 1 call it%advance () end do end function state_matrix_get_n_leaves @ %def state_matrix_get_n_leaves @ Return the depth: <>= procedure :: get_depth => state_matrix_get_depth <>= pure function state_matrix_get_depth (state) result (depth) integer :: depth class(state_matrix_t), intent(in) :: state depth = state%depth end function state_matrix_get_depth @ %def state_matrix_get_depth @ Return the norm: <>= procedure :: get_norm => state_matrix_get_norm <>= pure function state_matrix_get_norm (state) result (norm) real(default) :: norm class(state_matrix_t), intent(in) :: state norm = state%norm end function state_matrix_get_norm @ %def state_matrix_get_norm @ \subsubsection{Retrieving contents} Return the quantum number array, using an index. We have to scan the state matrix since there is no shortcut. <>= procedure :: get_quantum_number => & state_matrix_get_quantum_number <>= function state_matrix_get_quantum_number (state, i, by_me_index) result (qn) class(state_matrix_t), intent(in), target :: state integer, intent(in) :: i logical, intent(in), optional :: by_me_index logical :: opt_by_me_index type(quantum_numbers_t), dimension(state%depth) :: qn type(state_iterator_t) :: it integer :: k opt_by_me_index = .false. if (present (by_me_index)) opt_by_me_index = by_me_index k = 0 call it%init (state) do while (it%is_valid ()) if (opt_by_me_index) then k = it%get_me_index () else k = k + 1 end if if (k == i) then qn = it%get_quantum_numbers () exit end if call it%advance () end do end function state_matrix_get_quantum_number @ %def state_matrix_get_quantum_number <>= generic :: get_quantum_numbers => get_quantum_numbers_all, get_quantum_numbers_mask procedure :: get_quantum_numbers_all => state_matrix_get_quantum_numbers_all procedure :: get_quantum_numbers_mask => state_matrix_get_quantum_numbers_mask <>= subroutine state_matrix_get_quantum_numbers_all (state, qn) class(state_matrix_t), intent(in), target :: state type(quantum_numbers_t), intent(out), dimension(:,:), allocatable :: qn integer :: i allocate (qn (state%get_n_matrix_elements (), & state%get_depth())) do i = 1, state%get_n_matrix_elements () qn (i, :) = state%get_quantum_number (i) end do end subroutine state_matrix_get_quantum_numbers_all @ %def state_matrix_get_quantum_numbers_all @ <>= subroutine state_matrix_get_quantum_numbers_mask (state, qn_mask, qn) class(state_matrix_t), intent(in), target :: state type(quantum_numbers_mask_t), intent(in), dimension(:) :: qn_mask type(quantum_numbers_t), intent(out), dimension(:,:), allocatable :: qn type(quantum_numbers_t), dimension(:), allocatable :: qn_tmp type(state_matrix_t) :: state_tmp type(state_iterator_t) :: it integer :: i, n n = state%get_n_matrix_elements (qn_mask) allocate (qn (n, state%get_depth ())) allocate (qn_tmp (state%get_depth ())) call it%init (state) call state_tmp%init () do while (it%is_valid ()) qn_tmp = it%get_quantum_numbers () call qn_tmp%undefine (qn_mask) call state_tmp%add_state (qn_tmp) call it%advance () end do do i = 1, n qn (i, :) = state_tmp%get_quantum_number (i) end do call state_tmp%final () end subroutine state_matrix_get_quantum_numbers_mask @ %def state_matrix_get_quantum_numbers_mask @ <>= procedure :: get_flavors => state_matrix_get_flavors <>= subroutine state_matrix_get_flavors (state, only_elementary, qn_mask, flv) class(state_matrix_t), intent(in), target :: state logical, intent(in) :: only_elementary type(quantum_numbers_mask_t), intent(in), dimension(:), optional :: qn_mask integer, intent(out), dimension(:,:), allocatable :: flv type(quantum_numbers_t), dimension(:,:), allocatable :: qn integer :: i_flv, n_partons type(flavor_t), dimension(:), allocatable :: flv_flv if (present (qn_mask)) then call state%get_quantum_numbers (qn_mask, qn) else call state%get_quantum_numbers (qn) end if allocate (flv_flv (size (qn, dim=2))) if (only_elementary) then flv_flv = qn(1, :)%get_flavor () n_partons = count (is_elementary (flv_flv%get_pdg ())) end if allocate (flv (n_partons, size (qn, dim=1))) associate (n_flv => size (qn, dim=1)) do i_flv = 1, size (qn, dim=1) flv_flv = qn(i_flv, :)%get_flavor () flv(:, i_flv) = pack (flv_flv%get_pdg (), is_elementary(flv_flv%get_pdg())) end do end associate contains elemental function is_elementary (pdg) logical :: is_elementary integer, intent(in) :: pdg is_elementary = abs(pdg) /= 2212 .and. abs(pdg) /= 92 .and. abs(pdg) /= 93 end function is_elementary end subroutine state_matrix_get_flavors @ %def state_matrix_get_flavors @ Return a single matrix element using its index. Works only if the shortcut array is allocated. <>= generic :: get_matrix_element => get_matrix_element_single generic :: get_matrix_element => get_matrix_element_array procedure :: get_matrix_element_single => & state_matrix_get_matrix_element_single procedure :: get_matrix_element_array => & state_matrix_get_matrix_element_array <>= elemental function state_matrix_get_matrix_element_single (state, i) result (me) complex(default) :: me class(state_matrix_t), intent(in) :: state integer, intent(in) :: i if (allocated (state%me)) then me = state%me(i) else me = 0 end if end function state_matrix_get_matrix_element_single @ %def state_matrix_get_matrix_element_single @ <>= function state_matrix_get_matrix_element_array (state) result (me) complex(default), dimension(:), allocatable :: me class(state_matrix_t), intent(in) :: state if (allocated (state%me)) then allocate (me (size (state%me))) me = state%me else me = 0 end if end function state_matrix_get_matrix_element_array @ %def state_matrix_get_matrix_element_array @ Return the color index with maximum absolute value that is present within the state matrix. <>= procedure :: get_max_color_value => state_matrix_get_max_color_value <>= function state_matrix_get_max_color_value (state) result (cmax) integer :: cmax class(state_matrix_t), intent(in) :: state if (associated (state%root)) then cmax = node_get_max_color_value (state%root) else cmax = 0 end if contains recursive function node_get_max_color_value (node) result (cmax) integer :: cmax type(node_t), intent(in), target :: node type(node_t), pointer :: current cmax = quantum_numbers_get_max_color_value (node%qn) current => node%child_first do while (associated (current)) cmax = max (cmax, node_get_max_color_value (current)) current => current%next end do end function node_get_max_color_value end function state_matrix_get_max_color_value @ %def state_matrix_get_max_color_value @ \subsubsection{Building the quantum state} The procedure generates a branch associated to the input array of quantum numbers. If the branch exists already, it is used. Optionally, we set the matrix-element index, a value (which may be added to the previous one), and increment one of the possible counters. We may also return the matrix element index of the current node. <>= procedure :: add_state => state_matrix_add_state <>= subroutine state_matrix_add_state (state, qn, index, value, & sum_values, counter_index, ignore_sub_for_qn, me_index) class(state_matrix_t), intent(inout) :: state type(quantum_numbers_t), dimension(:), intent(in) :: qn integer, intent(in), optional :: index complex(default), intent(in), optional :: value logical, intent(in), optional :: sum_values integer, intent(in), optional :: counter_index logical, intent(in), optional :: ignore_sub_for_qn integer, intent(out), optional :: me_index logical :: set_index, get_index, add set_index = present (index) get_index = present (me_index) add = .false.; if (present (sum_values)) add = sum_values if (state%depth == 0) then state%depth = size (qn) else if (state%depth /= size (qn)) then call state%write () call msg_bug ("State matrix: depth mismatch") end if if (size (qn) > 0) call node_make_branch (state%root, qn) contains recursive subroutine node_make_branch (parent, qn) type(node_t), pointer :: parent type(quantum_numbers_t), dimension(:), intent(in) :: qn type(node_t), pointer :: child logical :: match match = .false. child => parent%child_first SCAN_CHILDREN: do while (associated (child)) if (present (ignore_sub_for_qn)) then if (ignore_sub_for_qn) then match = quantum_numbers_eq_wo_sub (child%qn, qn(1)) else match = child%qn == qn(1) end if else match = child%qn == qn(1) end if if (match) exit SCAN_CHILDREN child => child%next end do SCAN_CHILDREN if (.not. match) then call node_append_child (parent, child) child%qn = qn(1) end if select case (size (qn)) case (1) if (.not. match) then state%n_matrix_elements = state%n_matrix_elements + 1 child%me_index = state%n_matrix_elements end if if (set_index) then child%me_index = index end if if (get_index) then me_index = child%me_index end if if (present (counter_index)) then if (.not. allocated (child%me_count)) then allocate (child%me_count (state%n_counters)) child%me_count = 0 end if child%me_count(counter_index) = child%me_count(counter_index) + 1 end if if (present (value)) then if (add) then child%me = child%me + value else child%me = value end if end if case (2:) call node_make_branch (child, qn(2:)) end select end subroutine node_make_branch end subroutine state_matrix_add_state @ %def state_matrix_add_state @ Remove irrelevant flavor/color/helicity labels and the corresponding branchings. The masks indicate which particles are affected; the masks length should coincide with the depth of the trie (without the root node). Recursively scan the whole tree, starting from the leaf nodes and working up to the root node. If a mask entry is set for the current tree level, scan the children there. For each child within that level make a new empty branch where the masked quantum number is undefined. Then recursively combine all following children with matching quantum number into this new node and move on. <>= procedure :: collapse => state_matrix_collapse <>= subroutine state_matrix_collapse (state, mask) class(state_matrix_t), intent(inout) :: state type(quantum_numbers_mask_t), dimension(:), intent(in) :: mask type(state_matrix_t) :: red_state if (state%is_defined ()) then call state%reduce (mask, red_state) call state%final () state = red_state end if end subroutine state_matrix_collapse @ %def state_matrix_collapse @ Transform the given state matrix into a reduced state matrix where some quantum numbers are removed, as indicated by the mask. The procedure creates a new state matrix, so the old one can be deleted after this if it is no longer used. It is said that the matrix element ordering is lost afterwards. We allow to keep the original matrix element index in the new state matrix. If the matrix element indices are kept, we do not freeze the state matrix. After reordering the matrix element indices by [[state_matrix_reorder_me]], the state matrix can be frozen. <>= procedure :: reduce => state_matrix_reduce <>= subroutine state_matrix_reduce (state, mask, red_state, keep_me_index) class(state_matrix_t), intent(in), target :: state type(quantum_numbers_mask_t), dimension(:), intent(in) :: mask type(state_matrix_t), intent(out) :: red_state logical, optional, intent(in) :: keep_me_index logical :: opt_keep_me_index type(state_iterator_t) :: it type(quantum_numbers_t), dimension(size(mask)) :: qn opt_keep_me_index = .false. if (present (keep_me_index)) opt_keep_me_index = keep_me_index call red_state%init () call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () call qn%undefine (mask) if (opt_keep_me_index) then call red_state%add_state (qn, index = it%get_me_index ()) else call red_state%add_state (qn) end if call it%advance () end do if (.not. opt_keep_me_index) then call red_state%freeze () end if end subroutine state_matrix_reduce @ %def state_matrix_reduce @ Reorder the matrix elements -- not the tree itself. The procedure is necessary in case the matrix element indices were kept when reducing over quantum numbers and one wants to reintroduce the previous order of the matrix elements. <>= procedure :: reorder_me => state_matrix_reorder_me <>= subroutine state_matrix_reorder_me (state, ordered_state) class(state_matrix_t), intent(in), target :: state type(state_matrix_t), intent(out) :: ordered_state type(state_iterator_t) :: it type(quantum_numbers_t), dimension(state%depth) :: qn integer, dimension(:), allocatable :: me_index integer :: i call ordered_state%init () call get_me_index_sorted (state, me_index) i = 1; call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () call ordered_state%add_state (qn, index = me_index(i)) i = i + 1; call it%advance () end do call ordered_state%freeze () contains subroutine get_me_index_sorted (state, me_index) class(state_matrix_t), intent(in), target :: state integer, dimension(:), allocatable, intent(out) :: me_index type(state_iterator_t) :: it integer :: i, j integer, dimension(:), allocatable :: me_index_unsorted, me_index_sorted associate (n_matrix_elements => state%get_n_matrix_elements ()) allocate (me_index(n_matrix_elements), source = 0) allocate (me_index_sorted(n_matrix_elements), source = 0) allocate (me_index_unsorted(n_matrix_elements), source = 0) i = 1; call it%init (state) do while (it%is_valid ()) me_index_unsorted(i) = it%get_me_index () i = i + 1 call it%advance () end do me_index_sorted = sort (me_index_unsorted) ! We do not care about efficiency at this point. UNSORTED: do i = 1, n_matrix_elements SORTED: do j = 1, n_matrix_elements if (me_index_unsorted(i) == me_index_sorted(j)) then me_index(i) = j cycle UNSORTED end if end do SORTED end do UNSORTED end associate end subroutine get_me_index_sorted end subroutine state_matrix_reorder_me @ %def state_matrix_order_by_flavors @ Sets all matrix elements whose flavor structure is a duplicate of another flavor structure to zero. We need this for the real finite to ignore duplicate flavor structures while keeping the indices identical to the singular real component. When comparing the flavor structures, we take into account permutations of final- state particles. To do this properly, we keep only the non-hard flavors and the initial-state flavors, i.e. the first two hard flavors fixed. <>= procedure :: set_duplicate_flv_zero => state_matrix_set_duplicate_flv_zero <>= subroutine state_matrix_set_duplicate_flv_zero (state) class(state_matrix_t), intent(inout), target :: state type(quantum_numbers_t), dimension(state%depth) :: qn type(flavor_t) :: flv class(state_flv_content_t), allocatable :: state_flv logical, dimension(:), allocatable :: hard_mask, sort_mask, duplicate_mask integer :: i, j, n_in, n_flvs n_flvs = state%get_depth () n_in = 2 !!! TODO (PS-28-07-21) n_in should not be hard coded to work for decays !!! This assumes that the positions of the non-hard flavors are the same for all flavor structures. qn = state%get_quantum_number(1) allocate (hard_mask(n_flvs)) do i = 1, n_flvs flv = qn(i)%get_flavor() hard_mask(i) = flv%is_hard_process () end do allocate (sort_mask(n_flvs)) sort_mask = hard_mask j = 0 do i = 1, n_flvs if (j == n_in) exit if (sort_mask(i)) then sort_mask(i) = .false. j = j + 1 end if end do allocate (state_flv) call state_flv%fill (state, sort_mask) call state_flv%find_duplicates (duplicate_mask) do i = 1, state%get_n_matrix_elements () if (duplicate_mask(i)) then call state%set_matrix_element_single(i, cmplx(zero, zero, default)) end if end do end subroutine state_matrix_set_duplicate_flv_zero @ %def state_matrix_set_duplicate_flv_zero @ This subroutine sets up the matrix-element array. The leaf nodes aquire the index values that point to the appropriate matrix-element entry. We recursively scan the trie. Once we arrive at a leaf node, the index is increased and associated to that node. Finally, we allocate the matrix-element array with the appropriate size. If matrix element values are temporarily stored within the leaf nodes, we scan the state again and transfer them to the matrix-element array. <>= procedure :: freeze => state_matrix_freeze <>= subroutine state_matrix_freeze (state) class(state_matrix_t), intent(inout), target :: state type(state_iterator_t) :: it if (associated (state%root)) then if (allocated (state%me)) deallocate (state%me) allocate (state%me (state%n_matrix_elements)) state%me = 0 call state%set_n_sub () end if if (state%leaf_nodes_store_values) then call it%init (state) do while (it%is_valid ()) state%me(it%get_me_index ()) = it%get_matrix_element () call it%advance () end do state%leaf_nodes_store_values = .false. end if end subroutine state_matrix_freeze @ %def state_matrix_freeze @ \subsubsection{Direct access to the value array} Several methods for setting a value directly are summarized in this generic: <>= generic :: set_matrix_element => set_matrix_element_qn generic :: set_matrix_element => set_matrix_element_all generic :: set_matrix_element => set_matrix_element_array generic :: set_matrix_element => set_matrix_element_single generic :: set_matrix_element => set_matrix_element_clone procedure :: set_matrix_element_qn => state_matrix_set_matrix_element_qn procedure :: set_matrix_element_all => state_matrix_set_matrix_element_all procedure :: set_matrix_element_array => & state_matrix_set_matrix_element_array procedure :: set_matrix_element_single => & state_matrix_set_matrix_element_single procedure :: set_matrix_element_clone => & state_matrix_set_matrix_element_clone @ %def state_matrix_set_matrix_element @ Set a value that corresponds to a quantum number array: <>= subroutine state_matrix_set_matrix_element_qn (state, qn, value) class(state_matrix_t), intent(inout), target :: state type(quantum_numbers_t), dimension(:), intent(in) :: qn complex(default), intent(in) :: value type(state_iterator_t) :: it if (.not. allocated (state%me)) then allocate (state%me (size(qn))) end if call it%init (state) call it%go_to_qn (qn) call it%set_matrix_element (value) end subroutine state_matrix_set_matrix_element_qn @ %def state_matrix_set_matrix_element_qn @ Set all matrix elements to a single value <>= subroutine state_matrix_set_matrix_element_all (state, value) class(state_matrix_t), intent(inout) :: state complex(default), intent(in) :: value if (.not. allocated (state%me)) then allocate (state%me (state%n_matrix_elements)) end if state%me = value end subroutine state_matrix_set_matrix_element_all @ %def state_matrix_set_matrix_element_all @ Set the matrix-element array directly. <>= subroutine state_matrix_set_matrix_element_array (state, value, range) class(state_matrix_t), intent(inout) :: state complex(default), intent(in), dimension(:) :: value integer, intent(in), dimension(:), optional :: range if (present (range)) then state%me(range) = value else if (.not. allocated (state%me)) & allocate (state%me (size (value))) state%me(:) = value end if end subroutine state_matrix_set_matrix_element_array @ %def state_matrix_set_matrix_element_array @ Set a matrix element at position [[i]] to [[value]]. <>= pure subroutine state_matrix_set_matrix_element_single (state, i, value) class(state_matrix_t), intent(inout) :: state integer, intent(in) :: i complex(default), intent(in) :: value if (.not. allocated (state%me)) then allocate (state%me (state%n_matrix_elements)) end if state%me(i) = value end subroutine state_matrix_set_matrix_element_single @ %def state_matrix_set_matrix_element_single @ Clone the matrix elements from another (matching) state matrix. <>= subroutine state_matrix_set_matrix_element_clone (state, state1) class(state_matrix_t), intent(inout) :: state type(state_matrix_t), intent(in) :: state1 if (.not. allocated (state1%me)) return if (.not. allocated (state%me)) allocate (state%me (size (state1%me))) state%me = state1%me end subroutine state_matrix_set_matrix_element_clone @ %def state_matrix_set_matrix_element_clone @ Add a value to a matrix element <>= procedure :: add_to_matrix_element => state_matrix_add_to_matrix_element <>= subroutine state_matrix_add_to_matrix_element (state, qn, value, match_only_flavor) class(state_matrix_t), intent(inout), target :: state type(quantum_numbers_t), dimension(:), intent(in) :: qn complex(default), intent(in) :: value logical, intent(in), optional :: match_only_flavor type(state_iterator_t) :: it call it%init (state) call it%go_to_qn (qn, match_only_flavor) if (it%is_valid ()) then call it%add_to_matrix_element (value) else call msg_fatal ("Cannot add to matrix element - it%node not allocated") end if end subroutine state_matrix_add_to_matrix_element @ %def state_matrix_add_to_matrix_element @ \subsection{State iterators} Accessing the quantum state from outside is best done using a specialized iterator, i.e., a pointer to a particular branch of the quantum state trie. Technically, the iterator contains a pointer to a leaf node, but via parent pointers it allows to access the whole branch where the leaf is attached. For quick access, we also keep the branch depth (which is assumed to be universal for a quantum state). <>= public :: state_iterator_t <>= type :: state_iterator_t private integer :: depth = 0 type(state_matrix_t), pointer :: state => null () type(node_t), pointer :: node => null () contains <> end type state_iterator_t @ %def state_iterator @ The initializer: Point at the first branch. Note that this cannot be pure, thus not be elemental, because the iterator can be used to manipulate data in the state matrix. <>= procedure :: init => state_iterator_init <>= subroutine state_iterator_init (it, state) class(state_iterator_t), intent(out) :: it type(state_matrix_t), intent(in), target :: state it%state => state it%depth = state%depth if (state%is_defined ()) then it%node => state%root do while (associated (it%node%child_first)) it%node => it%node%child_first end do else it%node => null () end if end subroutine state_iterator_init @ %def state_iterator_init @ Go forward. Recursively programmed: if the next node does not exist, go back to the parent node and look at its successor (if present), etc. There is a possible pitfall in the implementation: If the dummy pointer argument to the [[find_next]] routine is used directly, we still get the correct result for the iterator, but calling the recursion on [[node%parent]] means that we manipulate a parent pointer in the original state in addition to the iterator. Making a local copy of the pointer avoids this. Using pointer intent would be helpful, but we do not yet rely on this F2003 feature. <>= procedure :: advance => state_iterator_advance <>= subroutine state_iterator_advance (it) class(state_iterator_t), intent(inout) :: it call find_next (it%node) contains recursive subroutine find_next (node_in) type(node_t), intent(in), target :: node_in type(node_t), pointer :: node node => node_in if (associated (node%next)) then node => node%next do while (associated (node%child_first)) node => node%child_first end do it%node => node else if (associated (node%parent)) then call find_next (node%parent) else it%node => null () end if end subroutine find_next end subroutine state_iterator_advance @ %def state_iterator_advance @ If all has been scanned, the iterator is at an undefined state. Check for this: <>= procedure :: is_valid => state_iterator_is_valid <>= function state_iterator_is_valid (it) result (defined) logical :: defined class(state_iterator_t), intent(in) :: it defined = associated (it%node) end function state_iterator_is_valid @ %def state_iterator_is_valid @ Return the matrix-element index that corresponds to the current node <>= procedure :: get_me_index => state_iterator_get_me_index <>= function state_iterator_get_me_index (it) result (n) integer :: n class(state_iterator_t), intent(in) :: it n = it%node%me_index end function state_iterator_get_me_index @ %def state_iterator_get_me_index @ Return the number of times this quantum-number state has been added (noting that it is physically inserted only the first time). Note that for each state, there is an array of counters. <>= procedure :: get_me_count => state_iterator_get_me_count <>= function state_iterator_get_me_count (it) result (n) integer, dimension(:), allocatable :: n class(state_iterator_t), intent(in) :: it if (allocated (it%node%me_count)) then allocate (n (size (it%node%me_count))) n = it%node%me_count else allocate (n (0)) end if end function state_iterator_get_me_count @ %def state_iterator_get_me_count @ <>= procedure :: get_depth => state_iterator_get_depth <>= pure function state_iterator_get_depth (state_iterator) result (depth) integer :: depth class(state_iterator_t), intent(in) :: state_iterator depth = state_iterator%depth end function state_iterator_get_depth @ %def state_iterator_get_depth @ Proceed to the state associated with the quantum numbers [[qn]]. <>= procedure :: go_to_qn => state_iterator_go_to_qn <>= subroutine state_iterator_go_to_qn (it, qn, match_only_flavor) class(state_iterator_t), intent(inout) :: it type(quantum_numbers_t), dimension(:), intent(in) :: qn logical, intent(in), optional :: match_only_flavor type(quantum_numbers_t), dimension(:), allocatable :: qn_hard, qn_tmp logical :: match_flv match_flv = .false.; if (present (match_only_flavor)) match_flv = .true. do while (it%is_valid ()) if (match_flv) then qn_tmp = it%get_quantum_numbers () qn_hard = pack (qn_tmp, qn_tmp%are_hard_process ()) if (all (qn .fmatch. qn_hard)) then return else call it%advance () end if else if (all (qn == it%get_quantum_numbers ())) then return else call it%advance () end if end if end do end subroutine state_iterator_go_to_qn @ %def state_iterator_go_to_qn @ Use the iterator to retrieve quantum-number information: <>= generic :: get_quantum_numbers => get_qn_multi, get_qn_slice, & get_qn_range, get_qn_single generic :: get_flavor => get_flv_multi, get_flv_slice, & get_flv_range, get_flv_single generic :: get_color => get_col_multi, get_col_slice, & get_col_range, get_col_single generic :: get_helicity => get_hel_multi, get_hel_slice, & get_hel_range, get_hel_single <>= procedure :: get_qn_multi => state_iterator_get_qn_multi procedure :: get_qn_slice => state_iterator_get_qn_slice procedure :: get_qn_range => state_iterator_get_qn_range procedure :: get_qn_single => state_iterator_get_qn_single procedure :: get_flv_multi => state_iterator_get_flv_multi procedure :: get_flv_slice => state_iterator_get_flv_slice procedure :: get_flv_range => state_iterator_get_flv_range procedure :: get_flv_single => state_iterator_get_flv_single procedure :: get_col_multi => state_iterator_get_col_multi procedure :: get_col_slice => state_iterator_get_col_slice procedure :: get_col_range => state_iterator_get_col_range procedure :: get_col_single => state_iterator_get_col_single procedure :: get_hel_multi => state_iterator_get_hel_multi procedure :: get_hel_slice => state_iterator_get_hel_slice procedure :: get_hel_range => state_iterator_get_hel_range procedure :: get_hel_single => state_iterator_get_hel_single @ These versions return the whole quantum number array <>= function state_iterator_get_qn_multi (it) result (qn) class(state_iterator_t), intent(in) :: it type(quantum_numbers_t), dimension(it%depth) :: qn type(node_t), pointer :: node integer :: i node => it%node do i = it%depth, 1, -1 qn(i) = node%qn node => node%parent end do end function state_iterator_get_qn_multi function state_iterator_get_flv_multi (it) result (flv) class(state_iterator_t), intent(in) :: it type(flavor_t), dimension(it%depth) :: flv flv = quantum_numbers_get_flavor & (it%get_quantum_numbers ()) end function state_iterator_get_flv_multi function state_iterator_get_col_multi (it) result (col) class(state_iterator_t), intent(in) :: it type(color_t), dimension(it%depth) :: col col = quantum_numbers_get_color & (it%get_quantum_numbers ()) end function state_iterator_get_col_multi function state_iterator_get_hel_multi (it) result (hel) class(state_iterator_t), intent(in) :: it type(helicity_t), dimension(it%depth) :: hel hel = quantum_numbers_get_helicity & (it%get_quantum_numbers ()) end function state_iterator_get_hel_multi @ An array slice (derived from the above). <>= function state_iterator_get_qn_slice (it, index) result (qn) class(state_iterator_t), intent(in) :: it integer, dimension(:), intent(in) :: index type(quantum_numbers_t), dimension(size(index)) :: qn type(quantum_numbers_t), dimension(it%depth) :: qn_tmp qn_tmp = state_iterator_get_qn_multi (it) qn = qn_tmp(index) end function state_iterator_get_qn_slice function state_iterator_get_flv_slice (it, index) result (flv) class(state_iterator_t), intent(in) :: it integer, dimension(:), intent(in) :: index type(flavor_t), dimension(size(index)) :: flv flv = quantum_numbers_get_flavor & (it%get_quantum_numbers (index)) end function state_iterator_get_flv_slice function state_iterator_get_col_slice (it, index) result (col) class(state_iterator_t), intent(in) :: it integer, dimension(:), intent(in) :: index type(color_t), dimension(size(index)) :: col col = quantum_numbers_get_color & (it%get_quantum_numbers (index)) end function state_iterator_get_col_slice function state_iterator_get_hel_slice (it, index) result (hel) class(state_iterator_t), intent(in) :: it integer, dimension(:), intent(in) :: index type(helicity_t), dimension(size(index)) :: hel hel = quantum_numbers_get_helicity & (it%get_quantum_numbers (index)) end function state_iterator_get_hel_slice @ An array range (implemented directly). <>= function state_iterator_get_qn_range (it, k1, k2) result (qn) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k1, k2 type(quantum_numbers_t), dimension(k2-k1+1) :: qn type(node_t), pointer :: node integer :: i node => it%node SCAN: do i = it%depth, 1, -1 if (k1 <= i .and. i <= k2) then qn(i-k1+1) = node%qn else node => node%parent end if end do SCAN end function state_iterator_get_qn_range function state_iterator_get_flv_range (it, k1, k2) result (flv) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k1, k2 type(flavor_t), dimension(k2-k1+1) :: flv flv = quantum_numbers_get_flavor & (it%get_quantum_numbers (k1, k2)) end function state_iterator_get_flv_range function state_iterator_get_col_range (it, k1, k2) result (col) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k1, k2 type(color_t), dimension(k2-k1+1) :: col col = quantum_numbers_get_color & (it%get_quantum_numbers (k1, k2)) end function state_iterator_get_col_range function state_iterator_get_hel_range (it, k1, k2) result (hel) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k1, k2 type(helicity_t), dimension(k2-k1+1) :: hel hel = quantum_numbers_get_helicity & (it%get_quantum_numbers (k1, k2)) end function state_iterator_get_hel_range @ Just a specific single element <>= function state_iterator_get_qn_single (it, k) result (qn) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k type(quantum_numbers_t) :: qn type(node_t), pointer :: node integer :: i node => it%node SCAN: do i = it%depth, 1, -1 if (i == k) then qn = node%qn exit SCAN else node => node%parent end if end do SCAN end function state_iterator_get_qn_single function state_iterator_get_flv_single (it, k) result (flv) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k type(flavor_t) :: flv flv = quantum_numbers_get_flavor & (it%get_quantum_numbers (k)) end function state_iterator_get_flv_single function state_iterator_get_col_single (it, k) result (col) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k type(color_t) :: col col = quantum_numbers_get_color & (it%get_quantum_numbers (k)) end function state_iterator_get_col_single function state_iterator_get_hel_single (it, k) result (hel) class(state_iterator_t), intent(in) :: it integer, intent(in) :: k type(helicity_t) :: hel hel = quantum_numbers_get_helicity & (it%get_quantum_numbers (k)) end function state_iterator_get_hel_single @ %def state_iterator_get_quantum_numbers @ %def state_iterator_get_flavor @ %def state_iterator_get_color @ %def state_iterator_get_helicity @ Assign a model pointer to the current flavor entries. <>= procedure :: set_model => state_iterator_set_model <>= subroutine state_iterator_set_model (it, model) class(state_iterator_t), intent(inout) :: it class(model_data_t), intent(in), target :: model type(node_t), pointer :: node integer :: i node => it%node do i = it%depth, 1, -1 call node%qn%set_model (model) node => node%parent end do end subroutine state_iterator_set_model @ %def state_iterator_set_model @ Modify the hard-interaction tag of the current flavor entries at a specific position, in-place. <>= procedure :: retag_hard_process => state_iterator_retag_hard_process <>= subroutine state_iterator_retag_hard_process (it, i, hard) class(state_iterator_t), intent(inout) :: it integer, intent(in) :: i logical, intent(in) :: hard type(node_t), pointer :: node integer :: j node => it%node do j = 1, it%depth-i node => node%parent end do call node%qn%tag_hard_process (hard) end subroutine state_iterator_retag_hard_process @ %def state_iterator_retag_hard_process @ Retrieve the matrix element value associated with the current node. <>= procedure :: get_matrix_element => state_iterator_get_matrix_element <>= function state_iterator_get_matrix_element (it) result (me) complex(default) :: me class(state_iterator_t), intent(in) :: it if (it%state%leaf_nodes_store_values) then me = it%node%me else if (it%node%me_index /= 0) then me = it%state%me(it%node%me_index) else me = 0 end if end function state_iterator_get_matrix_element @ %def state_iterator_get_matrix_element @ Set the matrix element value using the state iterator. <>= procedure :: set_matrix_element => state_iterator_set_matrix_element <>= subroutine state_iterator_set_matrix_element (it, value) class(state_iterator_t), intent(inout) :: it complex(default), intent(in) :: value if (it%node%me_index /= 0) it%state%me(it%node%me_index) = value end subroutine state_iterator_set_matrix_element @ %def state_iterator_set_matrix_element @ <>= procedure :: add_to_matrix_element => state_iterator_add_to_matrix_element <>= subroutine state_iterator_add_to_matrix_element (it, value) class(state_iterator_t), intent(inout) :: it complex(default), intent(in) :: value if (it%node%me_index /= 0) & it%state%me(it%node%me_index) = it%state%me(it%node%me_index) + value end subroutine state_iterator_add_to_matrix_element @ %def state_iterator_add_to_matrix_element @ \subsection{Operations on quantum states} Return a deep copy of a state matrix. <>= public :: assignment(=) <>= interface assignment(=) module procedure state_matrix_assign end interface <>= subroutine state_matrix_assign (state_out, state_in) type(state_matrix_t), intent(out) :: state_out type(state_matrix_t), intent(in), target :: state_in type(state_iterator_t) :: it if (.not. state_in%is_defined ()) return call state_out%init () call it%init (state_in) do while (it%is_valid ()) call state_out%add_state (it%get_quantum_numbers (), & it%get_me_index ()) call it%advance () end do if (allocated (state_in%me)) then allocate (state_out%me (size (state_in%me))) state_out%me = state_in%me end if state_out%n_sub = state_in%n_sub end subroutine state_matrix_assign @ %def state_matrix_assign @ Determine the indices of all diagonal matrix elements. <>= procedure :: get_diagonal_entries => state_matrix_get_diagonal_entries <>= subroutine state_matrix_get_diagonal_entries (state, i) class(state_matrix_t), intent(in) :: state integer, dimension(:), allocatable, intent(out) :: i integer, dimension(state%n_matrix_elements) :: tmp integer :: n type(state_iterator_t) :: it type(quantum_numbers_t), dimension(:), allocatable :: qn n = 0 call it%init (state) allocate (qn (it%depth)) do while (it%is_valid ()) qn = it%get_quantum_numbers () if (all (qn%are_diagonal ())) then n = n + 1 tmp(n) = it%get_me_index () end if call it%advance () end do allocate (i(n)) if (n > 0) i = tmp(:n) end subroutine state_matrix_get_diagonal_entries @ %def state_matrices_get_diagonal_entries @ Normalize all matrix elements, i.e., multiply by a common factor. Assuming that the factor is nonzero, of course. <>= procedure :: renormalize => state_matrix_renormalize <>= subroutine state_matrix_renormalize (state, factor) class(state_matrix_t), intent(inout) :: state complex(default), intent(in) :: factor state%me = state%me * factor end subroutine state_matrix_renormalize @ %def state_matrix_renormalize @ Renormalize the state matrix by its trace, if nonzero. The renormalization is reflected in the state-matrix norm. <>= procedure :: normalize_by_trace => state_matrix_normalize_by_trace <>= subroutine state_matrix_normalize_by_trace (state) class(state_matrix_t), intent(inout) :: state real(default) :: trace trace = state%trace () if (trace /= 0) then state%me = state%me / trace state%norm = state%norm * trace end if end subroutine state_matrix_normalize_by_trace @ %def state_matrix_renormalize_by_trace @ Analogous, but renormalize by maximal (absolute) value. <>= procedure :: normalize_by_max => state_matrix_normalize_by_max <>= subroutine state_matrix_normalize_by_max (state) class(state_matrix_t), intent(inout) :: state real(default) :: m m = maxval (abs (state%me)) if (m /= 0) then state%me = state%me / m state%norm = state%norm * m end if end subroutine state_matrix_normalize_by_max @ %def state_matrix_renormalize_by_max @ Explicitly set the norm of a state matrix. <>= procedure :: set_norm => state_matrix_set_norm <>= subroutine state_matrix_set_norm (state, norm) class(state_matrix_t), intent(inout) :: state real(default), intent(in) :: norm state%norm = norm end subroutine state_matrix_set_norm @ %def state_matrix_set_norm @ Return the sum of all matrix element values. <>= procedure :: sum => state_matrix_sum <>= pure function state_matrix_sum (state) result (value) complex(default) :: value class(state_matrix_t), intent(in) :: state value = sum (state%me) end function state_matrix_sum @ %def state_matrix_sum @ Return the trace of a state matrix, i.e., the sum over all diagonal values. If [[qn_in]] is provided, only branches that match this quantum-numbers array in flavor and helicity are considered. (This mode is used for selecting a color state.) <>= procedure :: trace => state_matrix_trace <>= function state_matrix_trace (state, qn_in) result (trace) complex(default) :: trace class(state_matrix_t), intent(in), target :: state type(quantum_numbers_t), dimension(:), intent(in), optional :: qn_in type(quantum_numbers_t), dimension(:), allocatable :: qn type(state_iterator_t) :: it allocate (qn (state%get_depth ())) trace = 0 call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () if (present (qn_in)) then if (.not. all (qn .fhmatch. qn_in)) then call it%advance (); cycle end if end if if (all (qn%are_diagonal ())) then trace = trace + it%get_matrix_element () end if call it%advance () end do end function state_matrix_trace @ %def state_matrix_trace @ Append new states which are color-contracted versions of the existing states. The matrix element index of each color contraction coincides with the index of its origin, so no new matrix elements are generated. After this operation, no [[freeze]] must be performed anymore. <>= procedure :: add_color_contractions => state_matrix_add_color_contractions <>= subroutine state_matrix_add_color_contractions (state) class(state_matrix_t), intent(inout), target :: state type(state_iterator_t) :: it type(quantum_numbers_t), dimension(:,:), allocatable :: qn type(quantum_numbers_t), dimension(:,:), allocatable :: qn_con integer, dimension(:), allocatable :: me_index integer :: depth, n_me, i, j depth = state%get_depth () n_me = state%get_n_matrix_elements () allocate (qn (depth, n_me)) allocate (me_index (n_me)) i = 0 call it%init (state) do while (it%is_valid ()) i = i + 1 qn(:,i) = it%get_quantum_numbers () me_index(i) = it%get_me_index () call it%advance () end do do i = 1, n_me call quantum_number_array_make_color_contractions (qn(:,i), qn_con) do j = 1, size (qn_con, 2) call state%add_state (qn_con(:,j), index = me_index(i)) end do end do end subroutine state_matrix_add_color_contractions @ %def state_matrix_add_color_contractions @ This procedure merges two state matrices of equal depth. For each quantum number (flavor, color, helicity), we take the entry from the first argument where defined, otherwise the second one. (If both are defined, we get an off-diagonal matrix.) The resulting trie combines the information of the input tries in all possible ways. Note that values are ignored, all values in the result are zero. <>= public :: merge_state_matrices <>= subroutine merge_state_matrices (state1, state2, state3) type(state_matrix_t), intent(in), target :: state1, state2 type(state_matrix_t), intent(out) :: state3 type(state_iterator_t) :: it1, it2 type(quantum_numbers_t), dimension(state1%depth) :: qn1, qn2 if (state1%depth /= state2%depth) then call state1%write () call state2%write () call msg_bug ("State matrices merge impossible: incompatible depths") end if call state3%init () call it1%init (state1) do while (it1%is_valid ()) qn1 = it1%get_quantum_numbers () call it2%init (state2) do while (it2%is_valid ()) qn2 = it2%get_quantum_numbers () call state3%add_state (qn1 .merge. qn2) call it2%advance () end do call it1%advance () end do call state3%freeze () end subroutine merge_state_matrices @ %def merge_state_matrices @ Multiply matrix elements from two state matrices. Choose the elements as given by the integer index arrays, multiply them and store the sum of products in the indicated matrix element. The suffixes mean: c=conjugate first factor; f=include weighting factor. Note that the [[dot_product]] intrinsic function conjugates its first complex argument. This is intended for the [[c]] suffix case, but must be reverted for the plain-product case. We provide analogous subroutines for just summing over state matrix entries. The [[evaluate_sum]] variant includes the state-matrix norm in the evaluation, the [[evaluate_me_sum]] takes into account just the matrix elements proper. <>= procedure :: evaluate_product => state_matrix_evaluate_product procedure :: evaluate_product_cf => state_matrix_evaluate_product_cf procedure :: evaluate_square_c => state_matrix_evaluate_square_c procedure :: evaluate_sum => state_matrix_evaluate_sum procedure :: evaluate_me_sum => state_matrix_evaluate_me_sum <>= pure subroutine state_matrix_evaluate_product & (state, i, state1, state2, index1, index2) class(state_matrix_t), intent(inout) :: state integer, intent(in) :: i type(state_matrix_t), intent(in) :: state1, state2 integer, dimension(:), intent(in) :: index1, index2 state%me(i) = & dot_product (conjg (state1%me(index1)), state2%me(index2)) state%norm = state1%norm * state2%norm end subroutine state_matrix_evaluate_product pure subroutine state_matrix_evaluate_product_cf & (state, i, state1, state2, index1, index2, factor) class(state_matrix_t), intent(inout) :: state integer, intent(in) :: i type(state_matrix_t), intent(in) :: state1, state2 integer, dimension(:), intent(in) :: index1, index2 complex(default), dimension(:), intent(in) :: factor state%me(i) = & dot_product (state1%me(index1), factor * state2%me(index2)) state%norm = state1%norm * state2%norm end subroutine state_matrix_evaluate_product_cf pure subroutine state_matrix_evaluate_square_c (state, i, state1, index1) class(state_matrix_t), intent(inout) :: state integer, intent(in) :: i type(state_matrix_t), intent(in) :: state1 integer, dimension(:), intent(in) :: index1 state%me(i) = & dot_product (state1%me(index1), state1%me(index1)) state%norm = abs (state1%norm) ** 2 end subroutine state_matrix_evaluate_square_c pure subroutine state_matrix_evaluate_sum (state, i, state1, index1) class(state_matrix_t), intent(inout) :: state integer, intent(in) :: i type(state_matrix_t), intent(in) :: state1 integer, dimension(:), intent(in) :: index1 state%me(i) = & sum (state1%me(index1)) * state1%norm end subroutine state_matrix_evaluate_sum pure subroutine state_matrix_evaluate_me_sum (state, i, state1, index1) class(state_matrix_t), intent(inout) :: state integer, intent(in) :: i type(state_matrix_t), intent(in) :: state1 integer, dimension(:), intent(in) :: index1 state%me(i) = sum (state1%me(index1)) end subroutine state_matrix_evaluate_me_sum @ %def state_matrix_evaluate_product @ %def state_matrix_evaluate_product_cf @ %def state_matrix_evaluate_square_c @ %def state_matrix_evaluate_sum @ %def state_matrix_evaluate_me_sum @ Outer product (of states and matrix elements): <>= public :: outer_multiply <>= interface outer_multiply module procedure outer_multiply_pair module procedure outer_multiply_array end interface @ %def outer_multiply @ This procedure constructs the outer product of two state matrices. <>= subroutine outer_multiply_pair (state1, state2, state3) type(state_matrix_t), intent(in), target :: state1, state2 type(state_matrix_t), intent(out) :: state3 type(state_iterator_t) :: it1, it2 type(quantum_numbers_t), dimension(state1%depth) :: qn1 type(quantum_numbers_t), dimension(state2%depth) :: qn2 type(quantum_numbers_t), dimension(state1%depth+state2%depth) :: qn3 complex(default) :: val1, val2 call state3%init (store_values = .true.) call it1%init (state1) do while (it1%is_valid ()) qn1 = it1%get_quantum_numbers () val1 = it1%get_matrix_element () call it2%init (state2) do while (it2%is_valid ()) qn2 = it2%get_quantum_numbers () val2 = it2%get_matrix_element () qn3(:state1%depth) = qn1 qn3(state1%depth+1:) = qn2 call state3%add_state (qn3, value=val1 * val2) call it2%advance () end do call it1%advance () end do call state3%freeze () end subroutine outer_multiply_pair @ %def outer_multiply_state_pair @ This executes the above routine iteratively for an arbitrary number of state matrices. <>= subroutine outer_multiply_array (state_in, state_out) type(state_matrix_t), dimension(:), intent(in), target :: state_in type(state_matrix_t), intent(out) :: state_out type(state_matrix_t), dimension(:), allocatable, target :: state_tmp integer :: i, n n = size (state_in) select case (n) case (0) call state_out%init () case (1) state_out = state_in(1) case (2) call outer_multiply_pair (state_in(1), state_in(2), state_out) case default allocate (state_tmp (n-2)) call outer_multiply_pair (state_in(1), state_in(2), state_tmp(1)) do i = 2, n - 2 call outer_multiply_pair (state_tmp(i-1), state_in(i+1), state_tmp(i)) end do call outer_multiply_pair (state_tmp(n-2), state_in(n), state_out) do i = 1, size(state_tmp) call state_tmp(i)%final () end do end select end subroutine outer_multiply_array @ %def outer_multiply_pair @ %def outer_multiply_array @ \subsection{Factorization} In physical events, the state matrix is factorized into single-particle state matrices. This is essentially a measurement. In a simulation, we select one particular branch of the state matrix with a probability that is determined by the matrix elements at the leaves. (This makes sense only if the state matrix represents a squared amplitude.) The selection is based on a (random) value [[x]] between 0 and one that is provided as the third argument. For flavor and color, we select a unique value for each particle. For polarization, we have three options (modes). Option 1 is to drop helicity information altogether and sum over all diagonal helicities. Option 2 is to select a unique diagonal helicity in the same way as flavor and color. Option 3 is, for each particle, to trace over all remaining helicities in order to obtain an array of independent single-particle helicity matrices. Only branches that match the given quantum-number array [[qn_in]], if present, are considered. For this array, color is ignored. If the optional [[correlated_state]] is provided, it is assigned the correlated density matrix for the selected flavor-color branch, so multi-particle spin correlations remain available even if they are dropped in the single-particle density matrices. This should be done by the caller for the choice [[FM_CORRELATED_HELICITY]], which otherwise is handled as [[FM_IGNORE_HELICITY]]. The algorithm is as follows: First, we determine the normalization by summing over all diagonal matrix elements. In a second scan, we select one of the diagonal matrix elements by a cumulative comparison with the normalized random number. In the corresponding quantum number array, we undefine the helicity entries. Then, we scan the third time. For each branch that matches the selected quantum number array (i.e., definite flavor and color, arbitrary helicity), we determine its contribution to any of the single-particle state matrices. The matrix-element value is added if all other quantum numbers are diagonal, while the helicity of the chosen particle may be arbitrary; this helicity determines the branch in the single-particle state. As a result, flavor and color quantum numbers are selected with the correct probability. Within this subset of states, each single-particle state matrix results from tracing over all other particles. Note that the single-particle state matrices are not normalized. The flag [[ok]] is set to false if the matrix element sum is zero, so factorization is not possible. This can happen if an event did not pass cuts. <>= integer, parameter, public :: FM_IGNORE_HELICITY = 1 integer, parameter, public :: FM_SELECT_HELICITY = 2 integer, parameter, public :: FM_FACTOR_HELICITY = 3 integer, parameter, public :: FM_CORRELATED_HELICITY = 4 @ %def FM_IGNORE_HELICITY FM_SELECT_HELICITY FM_FACTOR_HELICITY @ %def FM_CORRELATED_HELICITY <>= procedure :: factorize => state_matrix_factorize <>= subroutine state_matrix_factorize & (state, mode, x, ok, single_state, correlated_state, qn_in) class(state_matrix_t), intent(in), target :: state integer, intent(in) :: mode real(default), intent(in) :: x logical, intent(out) :: ok type(state_matrix_t), & dimension(:), allocatable, intent(out) :: single_state type(state_matrix_t), intent(out), optional :: correlated_state type(quantum_numbers_t), dimension(:), intent(in), optional :: qn_in type(state_iterator_t) :: it real(default) :: s, xt complex(default) :: value integer :: i, depth type(quantum_numbers_t), dimension(:), allocatable :: qn, qn1 type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask logical, dimension(:), allocatable :: diagonal logical, dimension(:,:), allocatable :: mask ok = .true. if (x /= 0) then xt = x * abs (state%trace (qn_in)) else xt = 0 end if s = 0 depth = state%get_depth () allocate (qn (depth), qn1 (depth), diagonal (depth)) call it%init (state) do while (it%is_valid ()) qn = it%get_quantum_numbers () if (present (qn_in)) then if (.not. all (qn .fhmatch. qn_in)) then call it%advance (); cycle end if end if if (all (qn%are_diagonal ())) then value = abs (it%get_matrix_element ()) s = s + value if (s > xt) exit end if call it%advance () end do if (.not. it%is_valid ()) then if (s == 0) ok = .false. call it%init (state) end if allocate (single_state (depth)) do i = 1, depth call single_state(i)%init (store_values = .true.) end do if (present (correlated_state)) & call correlated_state%init (store_values = .true.) qn = it%get_quantum_numbers () select case (mode) case (FM_SELECT_HELICITY) ! single branch selected; shortcut do i = 1, depth call single_state(i)%add_state ([qn(i)], value=value) end do if (.not. present (correlated_state)) then do i = 1, size(single_state) call single_state(i)%freeze () end do return end if end select allocate (qn_mask (depth)) call qn_mask%init (.false., .false., .false., .true.) call qn%undefine (qn_mask) select case (mode) case (FM_FACTOR_HELICITY) allocate (mask (depth, depth)) mask = .false. forall (i = 1:depth) mask(i,i) = .true. end select call it%init (state) do while (it%is_valid ()) qn1 = it%get_quantum_numbers () if (all (qn .match. qn1)) then diagonal = qn1%are_diagonal () value = it%get_matrix_element () select case (mode) case (FM_IGNORE_HELICITY, FM_CORRELATED_HELICITY) !!! trace over diagonal states that match qn if (all (diagonal)) then do i = 1, depth call single_state(i)%add_state & ([qn(i)], value=value, sum_values=.true.) end do end if case (FM_FACTOR_HELICITY) !!! trace over all other particles do i = 1, depth if (all (diagonal .or. mask(:,i))) then call single_state(i)%add_state & ([qn1(i)], value=value, sum_values=.true.) end if end do end select if (present (correlated_state)) & call correlated_state%add_state (qn1, value=value) end if call it%advance () end do do i = 1, depth call single_state(i)%freeze () end do if (present (correlated_state)) & call correlated_state%freeze () end subroutine state_matrix_factorize @ %def state_matrix_factorize @ \subsubsection{Auxiliary functions} <>= procedure :: get_polarization_density_matrix & => state_matrix_get_polarization_density_matrix <>= function state_matrix_get_polarization_density_matrix (state) result (pol_matrix) real(default), dimension(:,:), allocatable :: pol_matrix class(state_matrix_t), intent(in) :: state type(node_t), pointer :: current => null () !!! What's the generic way to allocate the matrix? allocate (pol_matrix (4,4)); pol_matrix = 0 if (associated (state%root%child_first)) then current => state%root%child_first do while (associated (current)) call current%qn%write () current => current%next end do else call msg_fatal ("Polarization state not allocated!") end if end function state_matrix_get_polarization_density_matrix @ %def state_matrix_get_polarization_density_matrix @ \subsubsection{Quantum-number matching} This feature allows us to check whether a given string of PDG values matches, in any ordering, any of the flavor combinations that the state matrix provides. We will also request the permutation of the successful match. This type provides an account of the state's flavor content. We store all flavor combinations, as [[pdg]] values, in an array, assuming that the length is uniform. We check only the entries selected by [[mask_match]]. Among those, only the entries selected by [[mask_sort]] are sorted and thus matched without respecting array element order. The entries that correspond to a true value in the associated [[mask]] are sorted. The mapping from the original state to the sorted state is given by the index array [[map]]. <>= public :: state_flv_content_t <>= type :: state_flv_content_t private integer, dimension(:,:), allocatable :: pdg integer, dimension(:,:), allocatable :: map logical, dimension(:), allocatable :: mask contains <> end type state_flv_content_t @ %def state_matrix_flavor_content @ Output (debugging aid). <>= procedure :: write => state_flv_content_write <>= subroutine state_flv_content_write (state_flv, unit) class(state_flv_content_t), intent(in), target :: state_flv integer, intent(in), optional :: unit integer :: u, n, d, i, j u = given_output_unit (unit) d = size (state_flv%pdg, 1) n = size (state_flv%pdg, 2) do i = 1, n write (u, "(2x,'PDG =')", advance="no") do j = 1, d write (u, "(1x,I0)", advance="no") state_flv%pdg(j,i) end do write (u, "(' :: map = (')", advance="no") do j = 1, d write (u, "(1x,I0)", advance="no") state_flv%map(j,i) end do write (u, "(' )')") end do end subroutine state_flv_content_write @ %def state_flv_content_write @ Initialize with table length and mask. Each row of the [[map]] array, of length $d$, is initialized with $(0,1,\ldots,d)$. <>= procedure :: init => state_flv_content_init <>= subroutine state_flv_content_init (state_flv, n, mask) class(state_flv_content_t), intent(out) :: state_flv integer, intent(in) :: n logical, dimension(:), intent(in) :: mask integer :: d, i d = size (mask) allocate (state_flv%pdg (d, n), source = 0) allocate (state_flv%map (d, n), source = spread ([(i, i = 1, d)], 2, n)) allocate (state_flv%mask (d), source = mask) end subroutine state_flv_content_init @ %def state_flv_content_init @ Manually fill the entries, one flavor set and mapping at a time. <>= procedure :: set_entry => state_flv_content_set_entry <>= subroutine state_flv_content_set_entry (state_flv, i, pdg, map) class(state_flv_content_t), intent(inout) :: state_flv integer, intent(in) :: i integer, dimension(:), intent(in) :: pdg, map state_flv%pdg(:,i) = pdg where (map /= 0) state_flv%map(:,i) = map end where end subroutine state_flv_content_set_entry @ %def state_flv_content_set_entry @ Given a state matrix, determine the flavor content. That is, scan the state matrix and extract flavor only, build a new state matrix from that. <>= procedure :: fill => state_flv_content_fill <>= subroutine state_flv_content_fill & (state_flv, state_full, mask) class(state_flv_content_t), intent(out) :: state_flv type(state_matrix_t), intent(in), target :: state_full logical, dimension(:), intent(in) :: mask type(state_matrix_t), target :: state_tmp type(state_iterator_t) :: it type(flavor_t), dimension(:), allocatable :: flv integer, dimension(:), allocatable :: pdg, pdg_subset integer, dimension(:), allocatable :: idx, map_subset, idx_subset, map type(quantum_numbers_t), dimension(:), allocatable :: qn integer :: n, d, c, i call state_tmp%init () d = state_full%get_depth () allocate (flv (d), qn (d), pdg (d), idx (d), map (d)) idx = [(i, i = 1, d)] c = count (mask) allocate (pdg_subset (c), map_subset (c), idx_subset (c)) call it%init (state_full) do while (it%is_valid ()) flv = it%get_flavor () call qn%init (flv) call state_tmp%add_state (qn) call it%advance () end do n = state_tmp%get_n_leaves () call state_flv%init (n, mask) i = 0 call it%init (state_tmp) do while (it%is_valid ()) i = i + 1 flv = it%get_flavor () pdg = flv%get_pdg () idx_subset = pack (idx, mask) pdg_subset = pack (pdg, mask) map_subset = order_abs (pdg_subset) map = unpack (idx_subset (map_subset), mask, idx) call state_flv%set_entry (i, & unpack (pdg_subset(map_subset), mask, pdg), & order (map)) call it%advance () end do call state_tmp%final () end subroutine state_flv_content_fill @ %def state_flv_content_fill @ Match a given flavor string against the flavor content. We sort the input string and check whether it matches any of the stored strings. If yes, return the mapping. Only PDG entries under the preset mask are sorted before matching. The other entries must match exactly (i.e., without reordering). A zero entry matches anything. In any case, the length of the PDG string must be equal to the length $d$ of the individual flavor-state entries. <>= procedure :: match => state_flv_content_match <>= subroutine state_flv_content_match (state_flv, pdg, success, map) class(state_flv_content_t), intent(in) :: state_flv integer, dimension(:), intent(in) :: pdg logical, intent(out) :: success integer, dimension(:), intent(out) :: map integer, dimension(:), allocatable :: pdg_subset, pdg_sorted, map1, map2 integer, dimension(:), allocatable :: idx, map_subset, idx_subset integer :: i, n, c, d c = count (state_flv%mask) d = size (state_flv%pdg, 1) n = size (state_flv%pdg, 2) allocate (idx (d), source = [(i, i = 1, d)]) allocate (idx_subset (c), pdg_subset (c), map_subset (c)) allocate (pdg_sorted (d), map1 (d), map2 (d)) idx_subset = pack (idx, state_flv%mask) pdg_subset = pack (pdg, state_flv%mask) map_subset = order_abs (pdg_subset) pdg_sorted = unpack (pdg_subset(map_subset), state_flv%mask, pdg) success = .false. do i = 1, n if (all (pdg_sorted == state_flv%pdg(:,i) & .or. pdg_sorted == 0)) then success = .true. exit end if end do if (success) then map1 = state_flv%map(:,i) map2 = unpack (idx_subset(map_subset), state_flv%mask, idx) map = map2(map1) where (pdg == 0) map = 0 end if end subroutine state_flv_content_match @ %def state_flv_content_match @ Check if a given PDG code occurs anywhere in the table. <>= procedure :: contains => state_flv_content_contains <>= function state_flv_content_contains (state_flv, pdg) result (success) class(state_flv_content_t), intent(in) :: state_flv integer, intent(in) :: pdg logical :: success success = any (state_flv%pdg == pdg) end function state_flv_content_contains @ %def state_flv_content_contains @ <>= elemental function pacify_complex (c_in) result (c_pac) complex(default), intent(in) :: c_in complex(default) :: c_pac c_pac = c_in if (real(c_pac) == -real(c_pac)) then c_pac = & cmplx (0._default, aimag(c_pac), kind=default) end if if (aimag(c_pac) == -aimag(c_pac)) then c_pac = & cmplx (real(c_pac), 0._default, kind=default) end if end function pacify_complex @ %def pacify_complex @ Looks for flavor structures that only differ by a permutation of the masked flavors. The result is returned in form of a mask which is [[.true.]] at the positions of a duplicate flavor structure from the second encounter on. This routine implements the naive approach: We go through all flavor structures and compare each one with each preceeding one. This works but is $\mathcal{O}(n^2)$ in the number of flavor structures. Using a table to remember which flavor structure has already been encountered, if would be possible to find the duplicates in $\mathcal{O}(n)$. <>= procedure :: find_duplicates => state_flv_content_find_duplicates <>= subroutine state_flv_content_find_duplicates (state_flv, duplicate_mask) class(state_flv_content_t), intent(in) :: state_flv logical, dimension(:), allocatable, intent(out) :: duplicate_mask integer, dimension(:), allocatable :: flvst integer :: i1, i2, n_flvsts logical :: found_once n_flvsts = size (state_flv%pdg, 2) allocate (duplicate_mask (n_flvsts)) duplicate_mask = .false. do i1 = 1, n_flvsts found_once = .false. flvst = state_flv%pdg(:,i1) do i2 = 1, i1 if (all(flvst == state_flv%pdg(:,i2))) then if (found_once) then duplicate_mask(i1) = .true. exit else found_once = .true. end if end if end do end do end subroutine state_flv_content_find_duplicates @ %def state_flv_content_find_duplicates @ \subsection{Unit tests} Test module, followed by the corresponding implementation module. <<[[state_matrices_ut.f90]]>>= <> module state_matrices_ut use unit_tests use state_matrices_uti <> <> contains <> end module state_matrices_ut @ %def state_matrices_ut @ <<[[state_matrices_uti.f90]]>>= <> module state_matrices_uti <> use io_units use format_defs, only: FMT_19 use flavors use colors use helicities use quantum_numbers use state_matrices <> <> contains <> end module state_matrices_uti @ %def state_matrices_ut @ API: driver for the unit tests below. <>= public :: state_matrix_test <>= subroutine state_matrix_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine state_matrix_test @ %def state_matrix_test @ Create two quantum states of equal depth and merge them. <>= call test (state_matrix_1, "state_matrix_1", & "check merge of quantum states of equal depth", & u, results) <>= public :: state_matrix_1 <>= subroutine state_matrix_1 (u) integer, intent(in) :: u type(state_matrix_t) :: state1, state2, state3 type(flavor_t), dimension(3) :: flv type(color_t), dimension(3) :: col type(quantum_numbers_t), dimension(3) :: qn write (u, "(A)") "* Test output: state_matrix_1" write (u, "(A)") "* Purpose: create and merge two quantum states" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") write (u, "(A)") "* State matrix 1" write (u, "(A)") call state1%init () call flv%init ([1, 2, 11]) call qn%init (flv, helicity ([ 1, 1, 1])) call state1%add_state (qn) call qn%init (flv, helicity ([ 1, 1, 1], [-1, 1, -1])) call state1%add_state (qn) call state1%freeze () call state1%write (u) write (u, "(A)") write (u, "(A)") "* State matrix 2" write (u, "(A)") call state2%init () call col(1)%init ([501]) call col(2)%init ([-501]) call col(3)%init ([0]) call qn%init (col, helicity ([-1, -1, 0])) call state2%add_state (qn) call col(3)%init ([99]) call qn%init (col, helicity ([-1, -1, 0])) call state2%add_state (qn) call state2%freeze () call state2%write (u) write (u, "(A)") write (u, "(A)") "* Merge the state matrices" write (u, "(A)") call merge_state_matrices (state1, state2, state3) call state3%write (u) write (u, "(A)") write (u, "(A)") "* Collapse the state matrix" write (u, "(A)") call state3%collapse (quantum_numbers_mask (.false., .false., & [.true.,.false.,.false.])) call state3%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" write (u, "(A)") call state1%final () call state2%final () call state3%final () write (u, "(A)") write (u, "(A)") "* Test output end: state_matrix_1" write (u, "(A)") end subroutine state_matrix_1 @ %def state_matrix_1 @ Create a correlated three-particle state matrix and factorize it. <>= call test (state_matrix_2, "state_matrix_2", & "check factorizing 3-particle state matrix", & u, results) <>= public :: state_matrix_2 <>= subroutine state_matrix_2 (u) integer, intent(in) :: u type(state_matrix_t) :: state type(state_matrix_t), dimension(:), allocatable :: single_state type(state_matrix_t) :: correlated_state integer :: f, h11, h12, h21, h22, i, mode type(flavor_t), dimension(2) :: flv type(color_t), dimension(2) :: col type(helicity_t), dimension(2) :: hel type(quantum_numbers_t), dimension(2) :: qn logical :: ok write (u, "(A)") write (u, "(A)") "* Test output: state_matrix_2" write (u, "(A)") "* Purpose: factorize correlated 3-particle state" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call state%init () do f = 1, 2 do h11 = -1, 1, 2 do h12 = -1, 1, 2 do h21 = -1, 1, 2 do h22 = -1, 1, 2 call flv%init ([f, -f]) call col(1)%init ([1]) call col(2)%init ([-1]) call hel%init ([h11,h12], [h21, h22]) call qn%init (flv, col, hel) call state%add_state (qn) end do end do end do end do end do call state%freeze () call state%write (u) write (u, "(A)") write (u, "(A,'('," // FMT_19 // ",','," // FMT_19 // ",')')") & "* Trace = ", state%trace () write (u, "(A)") do mode = 1, 3 write (u, "(A)") write (u, "(A,I1)") "* Mode = ", mode call state%factorize & (mode, 0.15_default, ok, single_state, correlated_state) do i = 1, size (single_state) write (u, "(A)") call single_state(i)%write (u) write (u, "(A,'('," // FMT_19 // ",','," // FMT_19 // ",')')") & "Trace = ", single_state(i)%trace () end do write (u, "(A)") call correlated_state%write (u) write (u, "(A,'('," // FMT_19 // ",','," // FMT_19 // ",')')") & "Trace = ", correlated_state%trace () do i = 1, size(single_state) call single_state(i)%final () end do call correlated_state%final () end do write (u, "(A)") write (u, "(A)") "* Cleanup" call state%final () write (u, "(A)") write (u, "(A)") "* Test output end: state_matrix_2" end subroutine state_matrix_2 @ %def state_matrix_2 @ Create a colored state matrix and add color contractions. <>= call test (state_matrix_3, "state_matrix_3", & "check factorizing 3-particle state matrix", & u, results) <>= public :: state_matrix_3 <>= subroutine state_matrix_3 (u) use physics_defs, only: HADRON_REMNANT_TRIPLET, HADRON_REMNANT_OCTET integer, intent(in) :: u type(state_matrix_t) :: state type(flavor_t), dimension(4) :: flv type(color_t), dimension(4) :: col type(quantum_numbers_t), dimension(4) :: qn write (u, "(A)") "* Test output: state_matrix_3" write (u, "(A)") "* Purpose: add color connections to colored state" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call state%init () call flv%init ([ 1, -HADRON_REMNANT_TRIPLET, -1, HADRON_REMNANT_TRIPLET ]) call col(1)%init ([17]) call col(2)%init ([-17]) call col(3)%init ([-19]) call col(4)%init ([19]) call qn%init (flv, col) call state%add_state (qn) call flv%init ([ 1, -HADRON_REMNANT_TRIPLET, 21, HADRON_REMNANT_OCTET ]) call col(1)%init ([17]) call col(2)%init ([-17]) call col(3)%init ([3, -5]) call col(4)%init ([5, -3]) call qn%init (flv, col) call state%add_state (qn) call state%freeze () write (u, "(A)") "* State:" write (u, "(A)") call state%write (u) call state%add_color_contractions () write (u, "(A)") "* State with contractions:" write (u, "(A)") call state%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call state%final () write (u, "(A)") write (u, "(A)") "* Test output end: state_matrx_3" end subroutine state_matrix_3 @ %def state_matrix_3 @ Create a correlated three-particle state matrix, write it to file and read again. <>= call test (state_matrix_4, "state_matrix_4", & "check raw I/O", & u, results) <>= public :: state_matrix_4 <>= subroutine state_matrix_4 (u) integer, intent(in) :: u type(state_matrix_t), allocatable :: state integer :: f, h11, h12, h21, h22, i type(flavor_t), dimension(2) :: flv type(color_t), dimension(2) :: col type(helicity_t), dimension(2) :: hel type(quantum_numbers_t), dimension(2) :: qn integer :: unit, iostat write (u, "(A)") write (u, "(A)") "* Test output: state_matrix_4" write (u, "(A)") "* Purpose: raw I/O for correlated 3-particle state" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") allocate (state) call state%init () do f = 1, 2 do h11 = -1, 1, 2 do h12 = -1, 1, 2 do h21 = -1, 1, 2 do h22 = -1, 1, 2 call flv%init ([f, -f]) call col(1)%init ([1]) call col(2)%init ([-1]) call hel%init ([h11, h12], [h21, h22]) call qn%init (flv, col, hel) call state%add_state (qn) end do end do end do end do end do call state%freeze () call state%set_norm (3._default) do i = 1, state%get_n_leaves () call state%set_matrix_element (i, cmplx (2 * i, 2 * i + 1, default)) end do call state%write (u) write (u, "(A)") write (u, "(A)") "* Write to file and read again " write (u, "(A)") unit = free_unit () open (unit, action="readwrite", form="unformatted", status="scratch") call state%write_raw (unit) call state%final () deallocate (state) allocate(state) rewind (unit) call state%read_raw (unit, iostat=iostat) close (unit) call state%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call state%final () deallocate (state) write (u, "(A)") write (u, "(A)") "* Test output end: state_matrix_4" end subroutine state_matrix_4 @ %def state_matrix_4 @ Create a flavor-content object for a given state matrix and match it against trial flavor (i.e., PDG) strings. <>= call test (state_matrix_5, "state_matrix_5", & "check flavor content", & u, results) <>= public :: state_matrix_5 <>= subroutine state_matrix_5 (u) integer, intent(in) :: u type(state_matrix_t), allocatable, target :: state type(state_iterator_t) :: it type(state_flv_content_t), allocatable :: state_flv type(flavor_t), dimension(4) :: flv1, flv2, flv3, flv4 type(color_t), dimension(4) :: col1, col2 type(helicity_t), dimension(4) :: hel1, hel2, hel3 type(quantum_numbers_t), dimension(4) :: qn logical, dimension(4) :: mask write (u, "(A)") "* Test output: state_matrix_5" write (u, "(A)") "* Purpose: check flavor-content state" write (u, "(A)") write (u, "(A)") "* Set up arbitrary state matrix" write (u, "(A)") call flv1%init ([1, 4, 2, 7]) call flv2%init ([1, 3,-3, 8]) call flv3%init ([5, 6, 3, 7]) call flv4%init ([6, 3, 5, 8]) call hel1%init ([0, 1, -1, 0]) call hel2%init ([0, 1, 1, 1]) call hel3%init ([1, 0, 0, 0]) call col1(1)%init ([0]) call col1(2)%init ([0]) call col1(3)%init ([0]) call col1(4)%init ([0]) call col2(1)%init ([5, -6]) call col2(2)%init ([0]) call col2(3)%init ([6, -5]) call col2(4)%init ([0]) allocate (state) call state%init () call qn%init (flv1, col1, hel1) call state%add_state (qn) call qn%init (flv1, col1, hel2) call state%add_state (qn) call qn%init (flv3, col1, hel3) call state%add_state (qn) call qn%init (flv4, col1, hel3) call state%add_state (qn) call qn%init (flv1, col2, hel3) call state%add_state (qn) call qn%init (flv2, col2, hel2) call state%add_state (qn) call qn%init (flv2, col2, hel1) call state%add_state (qn) call qn%init (flv2, col1, hel1) call state%add_state (qn) call qn%init (flv3, col1, hel1) call state%add_state (qn) call qn%init (flv3, col2, hel3) call state%add_state (qn) call qn%init (flv1, col1, hel1) call state%add_state (qn) write (u, "(A)") "* Quantum number content" write (u, "(A)") call it%init (state) do while (it%is_valid ()) call quantum_numbers_write (it%get_quantum_numbers (), u) write (u, *) call it%advance () end do write (u, "(A)") write (u, "(A)") "* Extract the flavor content" write (u, "(A)") mask = [.true., .true., .true., .false.] allocate (state_flv) call state_flv%fill (state, mask) call state_flv%write (u) write (u, "(A)") write (u, "(A)") "* Match trial sets" write (u, "(A)") call check ([1, 2, 3, 0]) call check ([1, 4, 2, 0]) call check ([4, 2, 1, 0]) call check ([1, 3, -3, 0]) call check ([1, -3, 3, 0]) call check ([6, 3, 5, 0]) write (u, "(A)") write (u, "(A)") "* Determine the flavor content with mask" write (u, "(A)") mask = [.false., .true., .true., .false.] call state_flv%fill (state, mask) call state_flv%write (u) write (u, "(A)") write (u, "(A)") "* Match trial sets" write (u, "(A)") call check ([1, 2, 3, 0]) call check ([1, 4, 2, 0]) call check ([4, 2, 1, 0]) call check ([1, 3, -3, 0]) call check ([1, -3, 3, 0]) call check ([6, 3, 5, 0]) write (u, "(A)") write (u, "(A)") "* Cleanup" deallocate (state_flv) call state%final () deallocate (state) write (u, "(A)") write (u, "(A)") "* Test output end: state_matrix_5" contains subroutine check (pdg) integer, dimension(4), intent(in) :: pdg integer, dimension(4) :: map logical :: success call state_flv%match (pdg, success, map) write (u, "(2x,4(1x,I0),':',1x,L1)", advance="no") pdg, success if (success) then write (u, "(2x,'map = (',4(1x,I0),' )')") map else write (u, *) end if end subroutine check end subroutine state_matrix_5 @ %def state_matrix_5 @ Create a state matrix with full flavor, color and helicity information. Afterwards, reduce such that it is only differential in flavor and initial-state helicities. This is used when preparing states for beam- polarized computations with external matrix element providers. <>= call test (state_matrix_6, "state_matrix_6", & "check state matrix reduction", & u, results) <>= public :: state_matrix_6 <>= subroutine state_matrix_6 (u) integer, intent(in) :: u type(state_matrix_t), allocatable :: state_orig, state_reduced type(flavor_t), dimension(4) :: flv type(helicity_t), dimension(4) :: hel type(color_t), dimension(4) :: col type(quantum_numbers_t), dimension(4) :: qn type(quantum_numbers_mask_t), dimension(4) :: qn_mask integer :: h1, h2, h3 , h4 integer :: n_states = 0 write (u, "(A)") "* Test output: state_matrix_6" write (u, "(A)") "* Purpose: Check state matrix reduction" write (u, "(A)") write (u, "(A)") "* Set up helicity-diagonal state matrix" write (u, "(A)") allocate (state_orig) call state_orig%init () call flv%init ([11, -11, 1, -1]) call col(3)%init ([1]) call col(4)%init ([-1]) do h1 = -1, 1, 2 do h2 = -1, 1, 2 do h3 = -1, 1, 2 do h4 = -1, 1, 2 n_states = n_states + 1 call hel%init ([h1, h2, h3, h4], [h1, h2, h3, h4]) call qn%init (flv, col, hel) call state_orig%add_state (qn) end do end do end do end do call state_orig%freeze () write (u, "(A)") "* Original state: " write (u, "(A)") call state_orig%write (u) write (u, "(A)") write (u, "(A)") "* Setup quantum mask: " call qn_mask%init ([.false., .false., .false., .false.], & [.true., .true., .true., .true.], & [.false., .false., .true., .true.]) call quantum_numbers_mask_write (qn_mask, u) write (u, "(A)") write (u, "(A)") "* Reducing the state matrix using above mask" write (u, "(A)") allocate (state_reduced) call state_orig%reduce (qn_mask, state_reduced) write (u, "(A)") "* Reduced state matrix: " call state_reduced%write (u) write (u, "(A)") "* Test output end: state_matrix_6" end subroutine state_matrix_6 @ %def state_matrix_6 @ Create a state matrix with full flavor, color and helicity information. Afterwards, reduce such that it is only differential in flavor and initial-state helicities, and keeping old indices. Afterwards reorder the reduced state matrix in accordance to the original state matrix. <>= call test (state_matrix_7, "state_matrix_7", & "check ordered state matrix reduction", & u, results) <>= public :: state_matrix_7 <>= subroutine state_matrix_7 (u) integer, intent(in) :: u type(state_matrix_t), allocatable :: state_orig, state_reduced, & state_ordered type(flavor_t), dimension(4) :: flv type(helicity_t), dimension(4) :: hel type(color_t), dimension(4) :: col type(quantum_numbers_t), dimension(4) :: qn type(quantum_numbers_mask_t), dimension(4) :: qn_mask integer :: h1, h2, h3 , h4 integer :: n_states = 0 write (u, "(A)") "* Test output: state_matrix_7" write (u, "(A)") "* Purpose: Check ordered state matrix reduction" write (u, "(A)") write (u, "(A)") "* Set up helicity-diagonal state matrix" write (u, "(A)") allocate (state_orig) call state_orig%init () call flv%init ([11, -11, 1, -1]) call col(3)%init ([1]) call col(4)%init ([-1]) do h1 = -1, 1, 2 do h2 = -1, 1, 2 do h3 = -1, 1, 2 do h4 = -1, 1, 2 n_states = n_states + 1 call hel%init ([h1, h2, h3, h4], [h1, h2, h3, h4]) call qn%init (flv, col, hel) call state_orig%add_state (qn) end do end do end do end do call state_orig%freeze () write (u, "(A)") "* Original state: " write (u, "(A)") call state_orig%write (u) write (u, "(A)") write (u, "(A)") "* Setup quantum mask: " call qn_mask%init ([.false., .false., .false., .false.], & [.true., .true., .true., .true.], & [.false., .false., .true., .true.]) call quantum_numbers_mask_write (qn_mask, u) write (u, "(A)") write (u, "(A)") "* Reducing the state matrix using above mask and keeping the old indices:" write (u, "(A)") allocate (state_reduced) call state_orig%reduce (qn_mask, state_reduced, keep_me_index = .true.) write (u, "(A)") "* Reduced state matrix with kept indices: " call state_reduced%write (u) write (u, "(A)") write (u, "(A)") "* Reordering reduced state matrix:" write (u, "(A)") allocate (state_ordered) call state_reduced%reorder_me (state_ordered) write (u, "(A)") "* Reduced and ordered state matrix:" call state_ordered%write (u) write (u, "(A)") "* Test output end: state_matrix_6" end subroutine state_matrix_7 @ %def state_matrix_7 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Interactions} This module defines the [[interaction_t]] type. It is an extension of the [[state_matrix_t]] type. The state matrix is a representation of a multi-particle density matrix. It implements all possible flavor, color, and quantum-number assignments of the entries in a generic density matrix, and it can hold a complex matrix element for each entry. (Note that this matrix can hold non-diagonal entries in color and helicity space.) The [[interaction_t]] object associates this with a list of momenta, such that the whole object represents a multi-particle state. The [[interaction_t]] holds information about which particles are incoming, virtual (i.e., kept for the records), or outgoing. Each particle can be associated to a source within another interaction. This allows us to automatically fill those interaction momenta which have been computed or defined elsewhere. It also contains internal parent-child relations and flags for (virtual) particles which are to be treated as resonances. A quantum-number mask array summarizes, for each particle within the interaction, the treatment of flavor, color, or helicity (expose or ignore). A list of locks states which particles are bound to have an identical quantum-number mask. This is useful when the mask is changed at one place. <<[[interactions.f90]]>>= <> module interactions <> use io_units use diagnostics use sorting use lorentz use flavors use colors use helicities use quantum_numbers use state_matrices <> <> <> <> contains <> end module interactions @ %def interactions @ Given an ordered list of quantum numbers (without any subtraction index) map this list to a state matrix, such that each list index corresponds to an index of a set of quantum numbers in the state matrix, hence, the matrix element. The (unphysical) subtraction index is not a genuine quantum number and as such handled specially. <>= public :: qn_index_map_t <>= type :: qn_index_map_t private type(quantum_numbers_t), dimension(:, :), allocatable :: qn_flv type(quantum_numbers_t), dimension(:, :), allocatable :: qn_hel logical :: flip_hel = .false. integer :: n_flv = 0, n_hel = 0, n_sub = 0 integer, dimension(:, :, :), allocatable :: index integer, dimension(:,:), allocatable :: sf_index_born, sf_index_real contains <> end type qn_index_map_t @ %def qn_index_map_t @ Construct a mapping from interaction to an array of (sorted) quantum numbers. We strip all non-elementary particles (like beam) from the quantum numbers which we retrieve from the interaction. We consider helicity matrix elements only, when [[qn_hel]] is allocated. Else the helicity index is handled trivially as [[1]]. For the rescaling of the structure functions in the real subtraction and DGLAP components we need a mapping (initialized by [[qn_index_map_init_sf]]) from the real and born flavor structure indices to the structure function chain interaction matrix element with the correct initial state quantum numbers. This is stored in [[sf_index_born]] and [[sf_index_real]]. The array [[index]] is only needed for the initialisation of the Born and real index arrays and is therefore deallocated again. <>= generic :: init => init_trivial, & init_involved, & init_sf procedure, private :: init_trivial => qn_index_map_init_trivial procedure, private :: init_involved => qn_index_map_init_involved procedure, private :: init_sf => qn_index_map_init_sf <>= subroutine qn_index_map_init_trivial (self, int) class(qn_index_map_t), intent(out) :: self class(interaction_t), intent(in) :: int integer :: qn self%n_flv = int%get_n_matrix_elements () self%n_hel = 1 self%n_sub = 0 allocate (self%index(self%n_flv, self%n_hel, 0:self%n_sub), source = 0) do qn = 1, self%n_flv self%index(qn, 1, 0) = qn end do end subroutine qn_index_map_init_trivial subroutine qn_index_map_init_involved (self, int, qn_flv, n_sub, qn_hel) class(qn_index_map_t), intent(out) :: self type(interaction_t), intent(in) :: int type(quantum_numbers_t), dimension(:, :), intent(in) :: qn_flv integer, intent(in) :: n_sub type(quantum_numbers_t), dimension(:, :), intent(in), optional :: qn_hel type(quantum_numbers_t), dimension(:), allocatable :: qn, qn_int integer :: i, i_flv, i_hel, i_sub self%qn_flv = qn_flv self%n_flv = size (qn_flv, dim=2) self%n_sub = n_sub if (present (qn_hel)) then if (size (qn_flv, dim=1) /= size (qn_hel, dim=1)) then call msg_bug ("[qn_index_map_init] number of particles does not match.") end if self%qn_hel = qn_hel self%n_hel = size (qn_hel, dim=2) else self%n_hel = 1 end if allocate (self%index (self%n_flv, self%n_hel, 0:self%n_sub), source=0) associate (n_me => int%get_n_matrix_elements ()) do i = 1, n_me qn_int = int%get_quantum_numbers (i, by_me_index = .true.) qn = pack (qn_int, qn_int%are_hard_process ()) i_flv = find_flv_index (self, qn) i_hel = 1; if (allocated (self%qn_hel)) & i_hel = find_hel_index (self, qn) i_sub = find_sub_index (self, qn) self%index(i_flv, i_hel, i_sub) = i end do end associate contains integer function find_flv_index (self, qn) result (i_flv) type(qn_index_map_t), intent(in) :: self type(quantum_numbers_t), dimension(:), intent(in) :: qn integer :: j i_flv = 0 do j = 1, self%n_flv if (.not. all (qn .fmatch. self%qn_flv(:, j))) cycle i_flv = j exit end do if (i_flv < 1) then call msg_message ("QN:") call quantum_numbers_write (qn) call msg_message ("") call msg_message ("QN_FLV:") do j = 1, self%n_flv call quantum_numbers_write (self%qn_flv(:, j)) call msg_message ("") end do call msg_bug ("[find_flv_index] could not find flv in qn_flv.") end if end function find_flv_index integer function find_hel_index (self, qn) result (i_hel) type(qn_index_map_t), intent(in) :: self type(quantum_numbers_t), dimension(:), intent(in) :: qn integer :: j i_hel = 0 do j = 1, self%n_hel if (.not. all (qn .hmatch. self%qn_hel(:, j))) cycle i_hel = j exit end do if (i_hel < 1) then call msg_message ("QN:") call quantum_numbers_write (qn) call msg_message ("") call msg_message ("QN_HEL:") do j = 1, self%n_hel call quantum_numbers_write (self%qn_hel(:, j)) call msg_message ("") end do call msg_bug ("[find_hel_index] could not find hel in qn_hel.") end if end function find_hel_index integer function find_sub_index (self, qn) result (i_sub) type(qn_index_map_t), intent(in) :: self type(quantum_numbers_t), dimension(:), intent(in) :: qn integer :: s i_sub = -1 do s = 0, self%n_sub if ((all (pack(qn%get_sub (), qn%get_sub () > 0) == s)) & .or. (all (qn%get_sub () == 0) .and. s == 0)) then i_sub = s exit end if end do if (i_sub < 0) then call msg_message ("QN:") call quantum_numbers_write (qn) call msg_bug ("[find_sub_index] could not find sub in qn.") end if end function find_sub_index end subroutine qn_index_map_init_involved subroutine qn_index_map_init_sf (self, int, qn_flv, n_flv_born, n_flv_real) class(qn_index_map_t), intent(out) :: self type(interaction_t), intent(in) :: int integer, intent(in) :: n_flv_born, n_flv_real type(quantum_numbers_t), dimension(:,:), intent(in) :: qn_flv type(quantum_numbers_t), dimension(:,:), allocatable :: qn_int type(quantum_numbers_t), dimension(:), allocatable :: qn_int_tmp integer :: i, i_sub, n_flv, n_hard n_flv = int%get_n_matrix_elements () qn_int_tmp = int%get_quantum_numbers (1, by_me_index = .true.) n_hard = count (qn_int_tmp%are_hard_process ()) allocate (qn_int(n_hard, n_flv)) do i = 1, n_flv qn_int_tmp = int%get_quantum_numbers (i, by_me_index = .true.) qn_int(:, i) = pack (qn_int_tmp, qn_int_tmp%are_hard_process ()) end do call self%init (int, qn_int, int%get_n_sub ()) allocate (self%sf_index_born(n_flv_born, 0:self%n_sub)) allocate (self%sf_index_real(n_flv_real, 0:self%n_sub)) do i_sub = 0, self%n_sub do i = 1, n_flv_born self%sf_index_born(i, i_sub) = self%get_index_by_qn (qn_flv(:,i), i_sub) end do do i = 1, n_flv_real self%sf_index_real(i, i_sub) = & self%get_index_by_qn (qn_flv(:,n_flv_born + i), i_sub) end do end do deallocate (self%index) end subroutine qn_index_map_init_sf @ %def qn_index_map_init_trivial @ %def qn_index_map_init_involved @ %def qn_index_map_init_sf @ Write the index map to unit. <>= procedure :: write => qn_index_map_write <>= subroutine qn_index_map_write (self, unit) class(qn_index_map_t), intent(in) :: self integer, intent(in), optional :: unit integer :: u, i_flv, i_hel, i_sub u = given_output_unit (unit); if (u < 0) return write (u, *) "flip_hel: ", self%flip_hel do i_flv = 1, self%n_flv if (allocated (self%qn_flv)) & call quantum_numbers_write (self%qn_flv(:, i_flv)) write (u, *) do i_hel = 1, self%n_hel if (allocated (self%qn_hel)) then call quantum_numbers_write (self%qn_hel(:, i_hel)) write (u, *) end if do i_sub = 0, self%n_sub write (u, *) & "(", i_flv, ",", i_hel, ",", i_sub, ") => ", self%index(i_flv, i_hel, i_sub) end do end do end do end subroutine qn_index_map_write @ %def qn_index_map_write @ Set helicity convention. If [[flip]], then we flip the helicities of anti-particles and we remap the indices accordingly. <>= procedure :: set_helicity_flip => qn_index_map_set_helicity_flip <>= subroutine qn_index_map_set_helicity_flip (self, yorn) class(qn_index_map_t), intent(inout) :: self logical, intent(in) :: yorn integer :: i, i_flv, i_hel, i_hel_new type(quantum_numbers_t), dimension(:, :), allocatable :: qn_hel_flip integer, dimension(:, :, :), allocatable :: index if (.not. allocated (self%qn_hel)) then call msg_bug ("[qn_index_map_set_helicity_flip] & &cannot flip not-given helicity.") end if allocate (index (self%n_flv, self%n_hel, 0:self%n_sub), & source=self%index) self%flip_hel = yorn if (self%flip_hel) then do i_flv = 1, self%n_flv qn_hel_flip = self%qn_hel do i_hel = 1, self%n_hel do i = 1, size (self%qn_flv, dim=1) if (is_anti_particle (self%qn_flv(i, i_flv))) then call qn_hel_flip(i, i_hel)%flip_helicity () end if end do end do do i_hel = 1, self%n_hel i_hel_new = find_hel_index (qn_hel_flip, self%qn_hel(:, i_hel)) self%index(i_flv, i_hel_new, :) = index(i_flv, i_hel, :) end do end do end if contains logical function is_anti_particle (qn) result (yorn) type(quantum_numbers_t), intent(in) :: qn type(flavor_t) :: flv flv = qn%get_flavor () yorn = flv%get_pdg () < 0 end function is_anti_particle integer function find_hel_index (qn_sort, qn) result (i_hel) type(quantum_numbers_t), dimension(:, :), intent(in) :: qn_sort type(quantum_numbers_t), dimension(:), intent(in) :: qn integer :: j do j = 1, size(qn_sort, dim=2) if (.not. all (qn .hmatch. qn_sort(:, j))) cycle i_hel = j exit end do end function find_hel_index end subroutine qn_index_map_set_helicity_flip @ %def qn_index_map_set_helicity_flip @ Map from the previously given quantum number and subtraction index (latter ranging from 0 to [[n_sub]]) to the (interaction) matrix element. <>= procedure :: get_index => qn_index_map_get_index <>= integer function qn_index_map_get_index (self, i_flv, i_hel, i_sub) result (index) class(qn_index_map_t), intent(in) :: self integer, intent(in) :: i_flv integer, intent(in), optional :: i_hel integer, intent(in), optional :: i_sub integer :: i_sub_opt, i_hel_opt i_sub_opt = 0; if (present (i_sub)) & i_sub_opt = i_sub i_hel_opt = 1; if (present (i_hel)) & i_hel_opt = i_hel index = 0 if (.not. allocated (self%index)) then call msg_bug ("[qn_index_map_get_index] The index map is not allocated.") end if index = self%index(i_flv, i_hel_opt, i_sub_opt) if (index <= 0) then call self%write () call msg_bug ("[qn_index_map_get_index] The index for the given quantum numbers could not be retrieved.") end if end function qn_index_map_get_index @ %def qn_index_map_get_i_flv @ Get [[n_flv]]. <>= procedure :: get_n_flv => qn_index_map_get_n_flv <>= integer function qn_index_map_get_n_flv (self) result (n_flv) class(qn_index_map_t), intent(in) :: self n_flv = self%n_flv end function qn_index_map_get_n_flv @ %def qn_index_map_get_n_flv @ Get [[n_hel]]. <>= procedure :: get_n_hel => qn_index_map_get_n_hel <>= integer function qn_index_map_get_n_hel (self) result (n_hel) class(qn_index_map_t), intent(in) :: self n_hel = self%n_hel end function qn_index_map_get_n_hel @ %def qn_index_map_get_n_flv @ Get [[n_sub]]. <>= procedure :: get_n_sub => qn_index_map_get_n_sub <>= integer function qn_index_map_get_n_sub (self) result (n_sub) class(qn_index_map_t), intent(in) :: self n_sub = self%n_sub end function qn_index_map_get_n_sub @ %def qn_index_map_get_n_sub @ Gets the index for the matrix element corresponding to a set of quantum numbers. So far, it ignores helicity (and color) indices. <>= procedure :: get_index_by_qn => qn_index_map_get_index_by_qn <>= integer function qn_index_map_get_index_by_qn (self, qn, i_sub) result (index) class(qn_index_map_t), intent(in) :: self type(quantum_numbers_t), dimension(:), intent(in) :: qn integer, intent(in), optional :: i_sub integer :: i_qn if (size (qn) /= size (self%qn_flv, dim = 1)) & call msg_bug ("[qn_index_map_get_index_by_qn] number of particles does not match.") do i_qn = 1, self%n_flv if (all (qn .fmatch. self%qn_flv(:, i_qn))) then index = self%get_index (i_qn, i_sub = i_sub) return end if end do call self%write () call msg_bug ("[qn_index_map_get_index_by_qn] The index for the given quantum & & numbers could not be retrieved.") end function qn_index_map_get_index_by_qn @ %def qn_index_map_get_index_by_qn @ <>= procedure :: get_sf_index_born => qn_index_map_get_sf_index_born <>= integer function qn_index_map_get_sf_index_born (self, i_born, i_sub) result (index) class(qn_index_map_t), intent(in) :: self integer, intent(in) :: i_born, i_sub index = self%sf_index_born(i_born, i_sub) end function qn_index_map_get_sf_index_born @ %def qn_index_map_get_sf_index_born @ <>= procedure :: get_sf_index_real => qn_index_map_get_sf_index_real <>= integer function qn_index_map_get_sf_index_real (self, i_real, i_sub) result (index) class(qn_index_map_t), intent(in) :: self integer, intent(in) :: i_real, i_sub index = self%sf_index_real(i_real, i_sub) end function qn_index_map_get_sf_index_real @ %def qn_index_map_get_sf_index_real @ \subsection{External interaction links} Each particle in an interaction can have a link to a corresponding particle in another interaction. This allows to fetch the momenta of incoming or virtual particles from the interaction where they are defined. The link object consists of a pointer to the interaction and an index. <>= type :: external_link_t private type(interaction_t), pointer :: int => null () integer :: i end type external_link_t @ %def external_link_t @ Set an external link. <>= subroutine external_link_set (link, int, i) type(external_link_t), intent(out) :: link type(interaction_t), target, intent(in) :: int integer, intent(in) :: i if (i /= 0) then link%int => int link%i = i end if end subroutine external_link_set @ %def external_link_set @ Reassign an external link to a new interaction (which should be an image of the original target). <>= subroutine external_link_reassign (link, int_src, int_target) type(external_link_t), intent(inout) :: link type(interaction_t), intent(in) :: int_src type(interaction_t), intent(in), target :: int_target if (associated (link%int)) then if (link%int%tag == int_src%tag) link%int => int_target end if end subroutine external_link_reassign @ %def external_link_reassign @ Return true if the link is set <>= function external_link_is_set (link) result (flag) logical :: flag type(external_link_t), intent(in) :: link flag = associated (link%int) end function external_link_is_set @ %def external_link_is_set @ Return the interaction pointer. <>= public :: external_link_get_ptr <>= function external_link_get_ptr (link) result (int) type(interaction_t), pointer :: int type(external_link_t), intent(in) :: link int => link%int end function external_link_get_ptr @ %def external_link_get_ptr @ Return the index within that interaction <>= public :: external_link_get_index <>= function external_link_get_index (link) result (i) integer :: i type(external_link_t), intent(in) :: link i = link%i end function external_link_get_index @ %def external_link_get_index @ Return a pointer to the momentum of the corresponding particle. If there is no association, return a null pointer. <>= function external_link_get_momentum_ptr (link) result (p) type(vector4_t), pointer :: p type(external_link_t), intent(in) :: link if (associated (link%int)) then p => link%int%p(link%i) else p => null () end if end function external_link_get_momentum_ptr @ %def external_link_get_momentum_ptr @ \subsection{Internal relations} In addition to the external links, particles within the interaction have parent-child relations. Here, more than one link is possible, and we set up an array. <>= type :: internal_link_list_t private integer :: length = 0 integer, dimension(:), allocatable :: link contains <> end type internal_link_list_t @ %def internal_link_t internal_link_list_t @ Output, non-advancing. <>= procedure :: write => internal_link_list_write <>= subroutine internal_link_list_write (object, unit) class(internal_link_list_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) do i = 1, object%length write (u, "(1x,I0)", advance="no") object%link(i) end do end subroutine internal_link_list_write @ %def internal_link_list_write @ Append an item. Start with an array size of 2 and double the size if necessary. Make sure that the indices are stored in ascending order. To this end, shift the existing entries right, starting from the end, as long as they are larger than the new entry. <>= procedure :: append => internal_link_list_append <>= subroutine internal_link_list_append (link_list, link) class(internal_link_list_t), intent(inout) :: link_list integer, intent(in) :: link integer :: l, j integer, dimension(:), allocatable :: tmp l = link_list%length if (allocated (link_list%link)) then if (l == size (link_list%link)) then allocate (tmp (2 * l)) tmp(:l) = link_list%link call move_alloc (from = tmp, to = link_list%link) end if else allocate (link_list%link (2)) end if link_list%link(l+1) = link SHIFT_LINK_IN_PLACE: do j = l, 1, -1 if (link >= link_list%link(j)) then exit SHIFT_LINK_IN_PLACE else link_list%link(j+1) = link_list%link(j) link_list%link(j) = link end if end do SHIFT_LINK_IN_PLACE link_list%length = l + 1 end subroutine internal_link_list_append @ %def internal_link_list_append @ Return true if the link list is nonempty: <>= procedure :: has_entries => internal_link_list_has_entries <>= function internal_link_list_has_entries (link_list) result (flag) class(internal_link_list_t), intent(in) :: link_list logical :: flag flag = link_list%length > 0 end function internal_link_list_has_entries @ %def internal_link_list_has_entries @ Return the list length <>= procedure :: get_length => internal_link_list_get_length <>= function internal_link_list_get_length (link_list) result (length) class(internal_link_list_t), intent(in) :: link_list integer :: length length = link_list%length end function internal_link_list_get_length @ %def internal_link_list_get_length @ Return an entry. <>= procedure :: get_link => internal_link_list_get_link <>= function internal_link_list_get_link (link_list, i) result (link) class(internal_link_list_t), intent(in) :: link_list integer, intent(in) :: i integer :: link if (i <= link_list%length) then link = link_list%link(i) else call msg_bug ("Internal link list: out of bounds") end if end function internal_link_list_get_link @ %def internal_link_list_get_link @ \subsection{The interaction type} An interaction is an entangled system of particles. Thus, the interaction object consists of two parts: the subevent, and the quantum state which technically is a trie. The subnode levels beyond the trie root node are in correspondence to the subevent, so both should be traversed in parallel. The subevent is implemented as an allocatable array of four-momenta. The first [[n_in]] particles are incoming, [[n_vir]] particles in-between can be kept for bookkeeping, and the last [[n_out]] particles are outgoing. Distinct interactions are linked by their particles: for each particle, we have the possibility of links to corresponding particles in other interactions. Furthermore, for bookkeeping purposes we have a self-link array [[relations]] where the parent-child relations are kept, and a flag array [[resonant]] which is set for an intermediate resonance. Each momentum is associated with masks for flavor, color, and helicity. If a mask entry is set, the associated quantum number is to be ignored for that particle. If any mask has changed, the flag [[update]] is set. We can have particle pairs locked together. If this is the case, the corresponding mask entries are bound to be equal. This is useful for particles that go through the interaction. The interaction tag serves bookkeeping purposes. In particular, it identifies links in printout. <>= public :: interaction_t <>= type :: interaction_t private integer :: tag = 0 type(state_matrix_t) :: state_matrix integer :: n_in = 0 integer :: n_vir = 0 integer :: n_out = 0 integer :: n_tot = 0 logical, dimension(:), allocatable :: p_is_known type(vector4_t), dimension(:), allocatable :: p type(external_link_t), dimension(:), allocatable :: source type(internal_link_list_t), dimension(:), allocatable :: parents type(internal_link_list_t), dimension(:), allocatable :: children logical, dimension(:), allocatable :: resonant type(quantum_numbers_mask_t), dimension(:), allocatable :: mask integer, dimension(:), allocatable :: hel_lock logical :: update_state_matrix = .false. logical :: update_values = .false. type(qn_index_map_t) :: qn_index contains <> end type interaction_t @ %def interaction_particle_p interaction_t @ Initialize the particle array with a fixed size. The first [[n_in]] particles are incoming, the rest outgoing. Masks are optional. There is also an optional tag. The interaction still needs fixing the values, but that is to be done after all branches have been added. Interaction tags are assigned consecutively, using a [[save]]d variable local to this procedure. If desired, we can provide a seed for the interaction tags. Such a seed should be positive. The default seed is one. [[tag=0]] indicates an empty interaction. If [[set_relations]] is set and true, we establish parent-child relations for all incoming and outgoing particles. Virtual particles are skipped; this option is normally used only for interations without virtual particles. <>= procedure :: basic_init => interaction_init <>= subroutine interaction_init & (int, n_in, n_vir, n_out, & tag, resonant, mask, hel_lock, set_relations, store_values) class(interaction_t), intent(out) :: int integer, intent(in) :: n_in, n_vir, n_out integer, intent(in), optional :: tag logical, dimension(:), intent(in), optional :: resonant type(quantum_numbers_mask_t), dimension(:), intent(in), optional :: mask integer, dimension(:), intent(in), optional :: hel_lock logical, intent(in), optional :: set_relations, store_values logical :: set_rel integer :: i, j set_rel = .false.; if (present (set_relations)) set_rel = set_relations call interaction_set_tag (int, tag) call int%state_matrix%init (store_values) int%n_in = n_in int%n_vir = n_vir int%n_out = n_out int%n_tot = n_in + n_vir + n_out allocate (int%p_is_known (int%n_tot)) int%p_is_known = .false. allocate (int%p (int%n_tot)) allocate (int%source (int%n_tot)) allocate (int%parents (int%n_tot)) allocate (int%children (int%n_tot)) allocate (int%resonant (int%n_tot)) if (present (resonant)) then int%resonant = resonant else int%resonant = .false. end if allocate (int%mask (int%n_tot)) allocate (int%hel_lock (int%n_tot)) if (present (mask)) then int%mask = mask end if if (present (hel_lock)) then int%hel_lock = hel_lock else int%hel_lock = 0 end if int%update_state_matrix = .false. int%update_values = .true. if (set_rel) then do i = 1, n_in do j = 1, n_out call int%relate (i, n_in + j) end do end do end if end subroutine interaction_init @ %def interaction_init @ <>= generic :: init_qn_index => init_qn_index_trivial, & init_qn_index_involved, & init_qn_index_sf procedure :: init_qn_index_trivial => interaction_init_qn_index_trivial procedure :: init_qn_index_involved => interaction_init_qn_index_involved procedure :: init_qn_index_sf => interaction_init_qn_index_sf <>= subroutine interaction_init_qn_index_trivial (int) class(interaction_t), intent(inout) :: int call int%qn_index%init (int) end subroutine interaction_init_qn_index_trivial subroutine interaction_init_qn_index_involved (int, qn_flv, n_sub, qn_hel) class(interaction_t), intent(inout) :: int type(quantum_numbers_t), dimension(:, :), intent(in) :: qn_flv integer, intent(in) :: n_sub type(quantum_numbers_t), dimension(:, :), intent(in), optional :: qn_hel call int%qn_index%init (int, qn_flv, n_sub, qn_hel) end subroutine interaction_init_qn_index_involved subroutine interaction_init_qn_index_sf (int, qn_flv, n_flv_born, n_flv_real) class(interaction_t), intent(inout) :: int integer, intent(in) :: n_flv_born, n_flv_real type(quantum_numbers_t), dimension(:,:), intent(in) :: qn_flv call int%qn_index%init (int, qn_flv, n_flv_born, n_flv_real) end subroutine interaction_init_qn_index_sf @ %def interaction_init_qn_index_trivial @ %def interaction_init_qn_index @ %def interaction_init_qn_index_sf @ <>= procedure :: set_qn_index_helicity_flip => interaction_set_qn_index_helicity_flip <>= subroutine interaction_set_qn_index_helicity_flip (int, yorn) class(interaction_t), intent(inout) :: int logical, intent(in) :: yorn call int%qn_index%set_helicity_flip (yorn) end subroutine interaction_set_qn_index_helicity_flip @ %def interaction_get_qn_index_n_flv @ <>= procedure :: get_qn_index => interaction_get_qn_index procedure :: get_sf_qn_index_born => interaction_get_sf_qn_index_born procedure :: get_sf_qn_index_real => interaction_get_sf_qn_index_real <>= integer function interaction_get_qn_index (int, i_flv, i_hel, i_sub) result (index) class(interaction_t), intent(in) :: int integer, intent(in) :: i_flv integer, intent(in), optional :: i_hel integer, intent(in), optional :: i_sub index = int%qn_index%get_index (i_flv, i_hel, i_sub) end function interaction_get_qn_index integer function interaction_get_sf_qn_index_born (int, i_born, i_sub) result (index) class(interaction_t), intent(in) :: int integer, intent(in) :: i_born, i_sub index = int%qn_index%get_sf_index_born (i_born, i_sub) end function interaction_get_sf_qn_index_born integer function interaction_get_sf_qn_index_real (int, i_real, i_sub) result (index) class(interaction_t), intent(in) :: int integer, intent(in) :: i_real, i_sub index = int%qn_index%get_sf_index_real (i_real, i_sub) end function interaction_get_sf_qn_index_real @ %def interaction_get_qn_index @ %def interaction_get_sf_qn_index_born @ %def interaction_get_sf_qn_index_real @ <>= procedure :: get_qn_index_n_flv => interaction_get_qn_index_n_flv procedure :: get_qn_index_n_hel => interaction_get_qn_index_n_hel procedure :: get_qn_index_n_sub => interaction_get_qn_index_n_sub <>= integer function interaction_get_qn_index_n_flv (int) result (index) class(interaction_t), intent(in) :: int index = int%qn_index%get_n_flv () end function interaction_get_qn_index_n_flv integer function interaction_get_qn_index_n_hel (int) result (index) class(interaction_t), intent(in) :: int index = int%qn_index%get_n_hel () end function interaction_get_qn_index_n_hel integer function interaction_get_qn_index_n_sub (int) result (index) class(interaction_t), intent(in) :: int index = int%qn_index%get_n_sub () end function interaction_get_qn_index_n_sub @ %def interaction_get_qn_index_n_flv @ %def interaction_get_qn_index_n_hel @ %def interaction_get_qn_index_n_sub @ Set or create a unique tag for the interaction. Without interaction, reset the tag counter. <>= subroutine interaction_set_tag (int, tag) type(interaction_t), intent(inout), optional :: int integer, intent(in), optional :: tag integer, save :: stored_tag = 1 if (present (int)) then if (present (tag)) then int%tag = tag else int%tag = stored_tag stored_tag = stored_tag + 1 end if else if (present (tag)) then stored_tag = tag else stored_tag = 1 end if end subroutine interaction_set_tag @ %def interaction_set_tag @ The public interface for the previous procedure only covers the reset functionality. <>= public :: reset_interaction_counter <>= subroutine reset_interaction_counter (tag) integer, intent(in), optional :: tag call interaction_set_tag (tag=tag) end subroutine reset_interaction_counter @ %def reset_interaction_counter @ Finalizer: The state-matrix object contains pointers. <>= procedure :: final => interaction_final <>= subroutine interaction_final (object) class(interaction_t), intent(inout) :: object call object%state_matrix%final () end subroutine interaction_final @ %def interaction_final @ Output. The [[verbose]] option refers to the state matrix output. <>= procedure :: basic_write => interaction_write <>= subroutine interaction_write & (int, unit, verbose, show_momentum_sum, show_mass, show_state, & col_verbose, testflag) class(interaction_t), intent(in) :: int integer, intent(in), optional :: unit logical, intent(in), optional :: verbose, show_momentum_sum, show_mass logical, intent(in), optional :: show_state, col_verbose, testflag integer :: u integer :: i, index_link type(interaction_t), pointer :: int_link logical :: show_st u = given_output_unit (unit); if (u < 0) return show_st = .true.; if (present (show_state)) show_st = show_state if (int%tag /= 0) then write (u, "(1x,A,I0)") "Interaction: ", int%tag do i = 1, int%n_tot if (i == 1 .and. int%n_in > 0) then write (u, "(1x,A)") "Incoming:" else if (i == int%n_in + 1 .and. int%n_vir > 0) then write (u, "(1x,A)") "Virtual:" else if (i == int%n_in + int%n_vir + 1 .and. int%n_out > 0) then write (u, "(1x,A)") "Outgoing:" end if write (u, "(1x,A,1x,I0)", advance="no") "Particle", i if (allocated (int%resonant)) then if (int%resonant(i)) then write (u, "(A)") "[r]" else write (u, *) end if else write (u, *) end if if (allocated (int%p)) then if (int%p_is_known(i)) then call vector4_write (int%p(i), u, show_mass, testflag) else write (u, "(A)") " [momentum undefined]" end if else write (u, "(A)") " [momentum not allocated]" end if if (allocated (int%mask)) then write (u, "(1x,A)", advance="no") "mask [fch] = " call int%mask(i)%write (u) write (u, *) end if if (int%parents(i)%has_entries () & .or. int%children(i)%has_entries ()) then write (u, "(1x,A)", advance="no") "internal links:" call int%parents(i)%write (u) if (int%parents(i)%has_entries ()) & write (u, "(1x,A)", advance="no") "=>" write (u, "(1x,A)", advance="no") "X" if (int%children(i)%has_entries ()) & write (u, "(1x,A)", advance="no") "=>" call int%children(i)%write (u) write (u, *) end if if (allocated (int%hel_lock)) then if (int%hel_lock(i) /= 0) then write (u, "(1x,A,1x,I0)") "helicity lock:", int%hel_lock(i) end if end if if (external_link_is_set (int%source(i))) then write (u, "(1x,A)", advance="no") "source:" int_link => external_link_get_ptr (int%source(i)) index_link = external_link_get_index (int%source(i)) write (u, "(1x,'(',I0,')',I0)", advance="no") & int_link%tag, index_link write (u, *) end if end do if (present (show_momentum_sum)) then if (allocated (int%p) .and. show_momentum_sum) then write (u, "(1x,A)") "Incoming particles (sum):" call vector4_write & (sum (int%p(1 : int%n_in)), u, show_mass = show_mass) write (u, "(1x,A)") "Outgoing particles (sum):" call vector4_write & (sum (int%p(int%n_in + int%n_vir + 1 : )), & u, show_mass = show_mass) write (u, *) end if end if if (show_st) then call int%write_state_matrix (write_value_list = verbose, & verbose = verbose, unit = unit, col_verbose = col_verbose, & testflag = testflag) end if else write (u, "(1x,A)") "Interaction: [empty]" end if end subroutine interaction_write @ %def interaction_write @ <>= procedure :: write_state_matrix => interaction_write_state_matrix <>= subroutine interaction_write_state_matrix (int, unit, write_value_list, & verbose, col_verbose, testflag) class(interaction_t), intent(in) :: int logical, intent(in), optional :: write_value_list, verbose, col_verbose logical, intent(in), optional :: testflag integer, intent(in), optional :: unit call int%state_matrix%write (write_value_list = verbose, & verbose = verbose, unit = unit, col_verbose = col_verbose, & testflag = testflag) end subroutine interaction_write_state_matrix @ %def interaction_write_state_matrix @ Reduce the [[state_matrix]] over the quantum mask. During the reduce procedure the iterator does not conserve the order of the matrix element respective their quantum numbers. Setting the [[keep_order]] results in a reorder state matrix with reintroduced matrix element indices. <>= procedure :: reduce_state_matrix => interaction_reduce_state_matrix <>= subroutine interaction_reduce_state_matrix (int, qn_mask, keep_order) class(interaction_t), intent(inout) :: int type(quantum_numbers_mask_t), intent(in), dimension(:) :: qn_mask logical, optional, intent(in) :: keep_order type(state_matrix_t) :: state logical :: opt_keep_order opt_keep_order = .false. if (present (keep_order)) opt_keep_order = keep_order call int%state_matrix%reduce (qn_mask, state, keep_me_index = keep_order) int%state_matrix = state if (opt_keep_order) then call int%state_matrix%reorder_me (state) int%state_matrix = state end if end subroutine interaction_reduce_state_matrix @ %def interaction_reduce_state_matrix @ Assignment: We implement this as a deep copy. This applies, in particular, to the state-matrix and internal-link components. Furthermore, the new interaction acquires a new tag. <>= public :: assignment(=) <>= interface assignment(=) module procedure interaction_assign end interface <>= subroutine interaction_assign (int_out, int_in) type(interaction_t), intent(out) :: int_out type(interaction_t), intent(in), target :: int_in call interaction_set_tag (int_out) int_out%state_matrix = int_in%state_matrix int_out%n_in = int_in%n_in int_out%n_out = int_in%n_out int_out%n_vir = int_in%n_vir int_out%n_tot = int_in%n_tot if (allocated (int_in%p_is_known)) then allocate (int_out%p_is_known (size (int_in%p_is_known))) int_out%p_is_known = int_in%p_is_known end if if (allocated (int_in%p)) then allocate (int_out%p (size (int_in%p))) int_out%p = int_in%p end if if (allocated (int_in%source)) then allocate (int_out%source (size (int_in%source))) int_out%source = int_in%source end if if (allocated (int_in%parents)) then allocate (int_out%parents (size (int_in%parents))) int_out%parents = int_in%parents end if if (allocated (int_in%children)) then allocate (int_out%children (size (int_in%children))) int_out%children = int_in%children end if if (allocated (int_in%resonant)) then allocate (int_out%resonant (size (int_in%resonant))) int_out%resonant = int_in%resonant end if if (allocated (int_in%mask)) then allocate (int_out%mask (size (int_in%mask))) int_out%mask = int_in%mask end if if (allocated (int_in%hel_lock)) then allocate (int_out%hel_lock (size (int_in%hel_lock))) int_out%hel_lock = int_in%hel_lock end if int_out%update_state_matrix = int_in%update_state_matrix int_out%update_values = int_in%update_values end subroutine interaction_assign @ %def interaction_assign @ \subsection{Methods inherited from the state matrix member} Until F2003 is standard, we cannot implement inheritance directly. Therefore, we need wrappers for ``inherited'' methods. Make a new branch in the state matrix if it does not yet exist. This is not just a wrapper but it introduces the interaction mask: where a quantum number is masked, it is not transferred but set undefined. After this, the value array has to be updated. <>= procedure :: add_state => interaction_add_state <>= subroutine interaction_add_state & (int, qn, index, value, sum_values, counter_index, ignore_sub_for_qn, me_index) class(interaction_t), intent(inout) :: int type(quantum_numbers_t), dimension(:), intent(in) :: qn integer, intent(in), optional :: index complex(default), intent(in), optional :: value logical, intent(in), optional :: sum_values integer, intent(in), optional :: counter_index logical, intent(in), optional :: ignore_sub_for_qn integer, intent(out), optional :: me_index type(quantum_numbers_t), dimension(size(qn)) :: qn_tmp qn_tmp = qn call qn_tmp%undefine (int%mask) call int%state_matrix%add_state (qn_tmp, index, value, sum_values, & counter_index, ignore_sub_for_qn, me_index) int%update_values = .true. end subroutine interaction_add_state @ %def interaction_add_state @ <>= procedure :: set_duplicate_flv_zero => interaction_set_duplicate_flv_zero <>= subroutine interaction_set_duplicate_flv_zero (int) class(interaction_t), intent(inout) :: int call int%state_matrix%set_duplicate_flv_zero () end subroutine interaction_set_duplicate_flv_zero @ %def interaction_set_duplicate_flv_zero @ Freeze the quantum state: First collapse the quantum state, i.e., remove quantum numbers if any mask has changed, then fix the array of value pointers. <>= procedure :: freeze => interaction_freeze <>= subroutine interaction_freeze (int) class(interaction_t), intent(inout) :: int if (int%update_state_matrix) then call int%state_matrix%collapse (int%mask) int%update_state_matrix = .false. int%update_values = .true. end if if (int%update_values) then call int%state_matrix%freeze () int%update_values = .false. end if end subroutine interaction_freeze @ %def interaction_freeze @ Return true if the state matrix is empty. <>= procedure :: is_empty => interaction_is_empty <>= pure function interaction_is_empty (int) result (flag) logical :: flag class(interaction_t), intent(in) :: int flag = int%state_matrix%is_empty () end function interaction_is_empty @ %def interaction_is_empty @ Get the number of values stored in the state matrix: <>= procedure :: get_n_matrix_elements => & interaction_get_n_matrix_elements <>= pure function interaction_get_n_matrix_elements (int) result (n) integer :: n class(interaction_t), intent(in) :: int n = int%state_matrix%get_n_matrix_elements () end function interaction_get_n_matrix_elements @ %def interaction_get_n_matrix_elements @ <>= procedure :: get_state_depth => interaction_get_state_depth <>= function interaction_get_state_depth (int) result (n) integer :: n class(interaction_t), intent(in) :: int n = int%state_matrix%get_depth () end function interaction_get_state_depth @ %def interaction_get_state_depth @ <>= procedure :: get_n_in_helicities => interaction_get_n_in_helicities <>= function interaction_get_n_in_helicities (int) result (n_hel) integer :: n_hel class(interaction_t), intent(in) :: int type(interaction_t) :: int_copy type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask type(quantum_numbers_t), dimension(:,:), allocatable :: qn integer :: i allocate (qn_mask (int%n_tot)) do i = 1, int%n_tot if (i <= int%n_in) then call qn_mask(i)%init (.true., .true., .false.) else call qn_mask(i)%init (.true., .true., .true.) end if end do int_copy = int call int_copy%set_mask (qn_mask) call int_copy%freeze () allocate (qn (int_copy%state_matrix%get_n_matrix_elements (), & int_copy%state_matrix%get_depth ())) qn = int_copy%get_quantum_numbers () n_hel = 0 do i = 1, size (qn, dim=1) if (all (qn(:, i)%get_subtraction_index () == 0)) n_hel = n_hel + 1 end do call int_copy%final () deallocate (qn_mask) deallocate (qn) end function interaction_get_n_in_helicities @ %def interaction_get_n_in_helicities @ Get the size of the [[me]]-array of the associated state matrix for debugging purposes <>= procedure :: get_me_size => interaction_get_me_size <>= pure function interaction_get_me_size (int) result (n) integer :: n class(interaction_t), intent(in) :: int n = int%state_matrix%get_me_size () end function interaction_get_me_size @ %def interaction_get_me_size @ Get the norm of the state matrix (if the norm has been taken out, otherwise this would be unity). <>= procedure :: get_norm => interaction_get_norm <>= pure function interaction_get_norm (int) result (norm) real(default) :: norm class(interaction_t), intent(in) :: int norm = int%state_matrix%get_norm () end function interaction_get_norm @ %def interaction_get_norm @ <>= procedure :: get_n_sub => interaction_get_n_sub <>= function interaction_get_n_sub (int) result (n_sub) integer :: n_sub class(interaction_t), intent(in) :: int n_sub = int%state_matrix%get_n_sub () end function interaction_get_n_sub @ %def interaction_get_n_sub @ Get the quantum number array that corresponds to a given index. <>= generic :: get_quantum_numbers => get_quantum_numbers_single, & get_quantum_numbers_all, & get_quantum_numbers_all_qn_mask procedure :: get_quantum_numbers_single => & interaction_get_quantum_numbers_single procedure :: get_quantum_numbers_all => & interaction_get_quantum_numbers_all procedure :: get_quantum_numbers_all_qn_mask => & interaction_get_quantum_numbers_all_qn_mask <>= function interaction_get_quantum_numbers_single (int, i, by_me_index) result (qn) type(quantum_numbers_t), dimension(:), allocatable :: qn class(interaction_t), intent(in), target :: int integer, intent(in) :: i logical, intent(in), optional :: by_me_index allocate (qn (int%state_matrix%get_depth ())) qn = int%state_matrix%get_quantum_number (i, by_me_index) end function interaction_get_quantum_numbers_single function interaction_get_quantum_numbers_all (int) result (qn) type(quantum_numbers_t), dimension(:,:), allocatable :: qn class(interaction_t), intent(in), target :: int integer :: i <> <>= allocate (qn (int%state_matrix%get_depth(), & int%state_matrix%get_n_matrix_elements ())) do i = 1, int%state_matrix%get_n_matrix_elements () qn (:, i) = int%state_matrix%get_quantum_number (i) end do <>= end function interaction_get_quantum_numbers_all function interaction_get_quantum_numbers_all_qn_mask (int, qn_mask) & result (qn) type(quantum_numbers_t), dimension(:,:), allocatable :: qn class(interaction_t), intent(in) :: int type(quantum_numbers_mask_t), intent(in) :: qn_mask integer :: n_redundant, n_all, n_me integer :: i type(quantum_numbers_t), dimension(:,:), allocatable :: qn_all <> <>= call int%state_matrix%get_quantum_numbers (qn_all) n_redundant = count (qn_all%are_redundant (qn_mask)) n_all = size (qn_all) !!! Number of matrix elements = survivors / n_particles n_me = (n_all - n_redundant) / int%state_matrix%get_depth () allocate (qn (int%state_matrix%get_depth(), n_me)) do i = 1, n_me if (.not. any (qn_all(i, :)%are_redundant (qn_mask))) & qn (:, i) = qn_all (i, :) end do <>= end function interaction_get_quantum_numbers_all_qn_mask @ %def interaction_get_quantum_numbers_single @ %def interaction_get_quantum_numbers_all @ %def interaction_get_quantum_numbers_all_qn_mask @ @ <>= procedure :: get_quantum_numbers_all_sub => interaction_get_quantum_numbers_all_sub <>= subroutine interaction_get_quantum_numbers_all_sub (int, qn) class(interaction_t), intent(in) :: int type(quantum_numbers_t), dimension(:,:), allocatable, intent(out) :: qn integer :: i <> end subroutine interaction_get_quantum_numbers_all_sub @ %def interaction_get_quantum_numbers_all @ <>= procedure :: get_flavors => interaction_get_flavors <>= subroutine interaction_get_flavors (int, only_elementary, qn_mask, flv) class(interaction_t), intent(in), target :: int logical, intent(in) :: only_elementary type(quantum_numbers_mask_t), intent(in), dimension(:), optional :: qn_mask integer, intent(out), dimension(:,:), allocatable :: flv call int%state_matrix%get_flavors (only_elementary, qn_mask, flv) end subroutine interaction_get_flavors @ %def interaction_get_flavors @ <>= procedure :: get_quantum_numbers_mask => interaction_get_quantum_numbers_mask <>= subroutine interaction_get_quantum_numbers_mask (int, qn_mask, qn) class(interaction_t), intent(in) :: int type(quantum_numbers_mask_t), intent(in) :: qn_mask type(quantum_numbers_t), dimension(:,:), allocatable, intent(out) :: qn integer :: n_redundant, n_all, n_me integer :: i type(quantum_numbers_t), dimension(:,:), allocatable :: qn_all <> end subroutine interaction_get_quantum_numbers_mask @ %def interaction_get_quantum_numbers_mask @ Get the matrix element that corresponds to a set of quantum numbers, a given index, or return the whole array. <>= generic :: get_matrix_element => get_matrix_element_single generic :: get_matrix_element => get_matrix_element_array procedure :: get_matrix_element_single => & interaction_get_matrix_element_single procedure :: get_matrix_element_array => & interaction_get_matrix_element_array <>= elemental function interaction_get_matrix_element_single (int, i) result (me) complex(default) :: me class(interaction_t), intent(in) :: int integer, intent(in) :: i me = int%state_matrix%get_matrix_element (i) end function interaction_get_matrix_element_single @ %def interaction_get_matrix_element_single <>= function interaction_get_matrix_element_array (int) result (me) complex(default), dimension(:), allocatable :: me class(interaction_t), intent(in) :: int allocate (me (int%get_n_matrix_elements ())) me = int%state_matrix%get_matrix_element () end function interaction_get_matrix_element_array @ %def interaction_get_matrix_element_array @ Set the complex value(s) stored in the quantum state. <>= generic :: set_matrix_element => interaction_set_matrix_element_qn, & interaction_set_matrix_element_all, & interaction_set_matrix_element_array, & interaction_set_matrix_element_single, & interaction_set_matrix_element_clone procedure :: interaction_set_matrix_element_qn procedure :: interaction_set_matrix_element_all procedure :: interaction_set_matrix_element_array procedure :: interaction_set_matrix_element_single procedure :: interaction_set_matrix_element_clone @ %def interaction_set_matrix_element @ Indirect access via the quantum number array: <>= subroutine interaction_set_matrix_element_qn (int, qn, val) class(interaction_t), intent(inout) :: int type(quantum_numbers_t), dimension(:), intent(in) :: qn complex(default), intent(in) :: val call int%state_matrix%set_matrix_element (qn, val) end subroutine interaction_set_matrix_element_qn @ %def interaction_set_matrix_element @ Set all entries of the matrix-element array to a given value. <>= subroutine interaction_set_matrix_element_all (int, value) class(interaction_t), intent(inout) :: int complex(default), intent(in) :: value call int%state_matrix%set_matrix_element (value) end subroutine interaction_set_matrix_element_all @ %def interaction_set_matrix_element_all @ Set the matrix-element array directly. <>= subroutine interaction_set_matrix_element_array (int, value, range) class(interaction_t), intent(inout) :: int complex(default), intent(in), dimension(:) :: value integer, intent(in), dimension(:), optional :: range call int%state_matrix%set_matrix_element (value, range) end subroutine interaction_set_matrix_element_array pure subroutine interaction_set_matrix_element_single (int, i, value) class(interaction_t), intent(inout) :: int integer, intent(in) :: i complex(default), intent(in) :: value call int%state_matrix%set_matrix_element (i, value) end subroutine interaction_set_matrix_element_single @ %def interaction_set_matrix_element_array @ %def interaction_set_matrix_element_single @ Clone from another (matching) interaction. <>= subroutine interaction_set_matrix_element_clone (int, int1) class(interaction_t), intent(inout) :: int class(interaction_t), intent(in) :: int1 call int%state_matrix%set_matrix_element (int1%state_matrix) end subroutine interaction_set_matrix_element_clone @ %def interaction_set_matrix_element_clone @ <>= procedure :: set_only_matrix_element => interaction_set_only_matrix_element <>= subroutine interaction_set_only_matrix_element (int, i, value) class(interaction_t), intent(inout) :: int integer, intent(in) :: i complex(default), intent(in) :: value call int%set_matrix_element (cmplx (0, 0, default)) call int%set_matrix_element (i, value) end subroutine interaction_set_only_matrix_element @ %def interaction_set_only_matrix_element @ <>= procedure :: add_to_matrix_element => interaction_add_to_matrix_element <>= subroutine interaction_add_to_matrix_element (int, qn, value, match_only_flavor) class(interaction_t), intent(inout) :: int type(quantum_numbers_t), dimension(:), intent(in) :: qn complex(default), intent(in) :: value logical, intent(in), optional :: match_only_flavor call int%state_matrix%add_to_matrix_element (qn, value, match_only_flavor) end subroutine interaction_add_to_matrix_element @ %def interaction_add_to_matrix_element @ Get the indices of any diagonal matrix elements. <>= procedure :: get_diagonal_entries => interaction_get_diagonal_entries <>= subroutine interaction_get_diagonal_entries (int, i) class(interaction_t), intent(in) :: int integer, dimension(:), allocatable, intent(out) :: i call int%state_matrix%get_diagonal_entries (i) end subroutine interaction_get_diagonal_entries @ %def interaction_get_diagonal_entries @ Renormalize the state matrix by its trace, if nonzero. The renormalization is reflected in the state-matrix norm. <>= procedure :: normalize_by_trace => interaction_normalize_by_trace <>= subroutine interaction_normalize_by_trace (int) class(interaction_t), intent(inout) :: int call int%state_matrix%normalize_by_trace () end subroutine interaction_normalize_by_trace @ %def interaction_normalize_by_trace @ Analogous, but renormalize by maximal (absolute) value. <>= procedure :: normalize_by_max => interaction_normalize_by_max <>= subroutine interaction_normalize_by_max (int) class(interaction_t), intent(inout) :: int call int%state_matrix%normalize_by_max () end subroutine interaction_normalize_by_max @ %def interaction_normalize_by_max @ Explicitly set the norm value (of the state matrix). <>= procedure :: set_norm => interaction_set_norm <>= subroutine interaction_set_norm (int, norm) class(interaction_t), intent(inout) :: int real(default), intent(in) :: norm call int%state_matrix%set_norm (norm) end subroutine interaction_set_norm @ %def interaction_set_norm @ <>= procedure :: set_state_matrix => interaction_set_state_matrix <>= subroutine interaction_set_state_matrix (int, state) class(interaction_t), intent(inout) :: int type(state_matrix_t), intent(in) :: state int%state_matrix = state end subroutine interaction_set_state_matrix @ %def interaction_set_state_matrix @ Return the maximum absolute value of color indices. <>= procedure :: get_max_color_value => & interaction_get_max_color_value <>= function interaction_get_max_color_value (int) result (cmax) class(interaction_t), intent(in) :: int integer :: cmax cmax = int%state_matrix%get_max_color_value () end function interaction_get_max_color_value @ %def interaction_get_max_color_value @ Factorize the state matrix into single-particle state matrices, the branch selection depending on a (random) value between 0 and 1; optionally also return a correlated state matrix. <>= procedure :: factorize => interaction_factorize <>= subroutine interaction_factorize & (int, mode, x, ok, single_state, correlated_state, qn_in) class(interaction_t), intent(in), target :: int integer, intent(in) :: mode real(default), intent(in) :: x logical, intent(out) :: ok type(state_matrix_t), & dimension(:), allocatable, intent(out) :: single_state type(state_matrix_t), intent(out), optional :: correlated_state type(quantum_numbers_t), dimension(:), intent(in), optional :: qn_in call int%state_matrix%factorize & (mode, x, ok, single_state, correlated_state, qn_in) end subroutine interaction_factorize @ %def interaction_factorize @ Sum all matrix element values <>= procedure :: sum => interaction_sum <>= function interaction_sum (int) result (value) class(interaction_t), intent(in) :: int complex(default) :: value value = int%state_matrix%sum () end function interaction_sum @ %def interaction_sum @ Append new states which are color-contracted versions of the existing states. The matrix element index of each color contraction coincides with the index of its origin, so no new matrix elements are generated. After this operation, no [[freeze]] must be performed anymore. <>= procedure :: add_color_contractions => & interaction_add_color_contractions <>= subroutine interaction_add_color_contractions (int) class(interaction_t), intent(inout) :: int call int%state_matrix%add_color_contractions () end subroutine interaction_add_color_contractions @ %def interaction_add_color_contractions @ Multiply matrix elements from two interactions. Choose the elements as given by the integer index arrays, multiply them and store the sum of products in the indicated matrix element. The suffixes mean: c=conjugate first factor; f=include weighting factor. <>= procedure :: evaluate_product => interaction_evaluate_product procedure :: evaluate_product_cf => interaction_evaluate_product_cf procedure :: evaluate_square_c => interaction_evaluate_square_c procedure :: evaluate_sum => interaction_evaluate_sum procedure :: evaluate_me_sum => interaction_evaluate_me_sum <>= pure subroutine interaction_evaluate_product & (int, i, int1, int2, index1, index2) class(interaction_t), intent(inout) :: int integer, intent(in) :: i type(interaction_t), intent(in) :: int1, int2 integer, dimension(:), intent(in) :: index1, index2 call int%state_matrix%evaluate_product & (i, int1%state_matrix, int2%state_matrix, & index1, index2) end subroutine interaction_evaluate_product pure subroutine interaction_evaluate_product_cf & (int, i, int1, int2, index1, index2, factor) class(interaction_t), intent(inout) :: int integer, intent(in) :: i type(interaction_t), intent(in) :: int1, int2 integer, dimension(:), intent(in) :: index1, index2 complex(default), dimension(:), intent(in) :: factor call int%state_matrix%evaluate_product_cf & (i, int1%state_matrix, int2%state_matrix, & index1, index2, factor) end subroutine interaction_evaluate_product_cf pure subroutine interaction_evaluate_square_c (int, i, int1, index1) class(interaction_t), intent(inout) :: int integer, intent(in) :: i type(interaction_t), intent(in) :: int1 integer, dimension(:), intent(in) :: index1 call int%state_matrix%evaluate_square_c (i, int1%state_matrix, index1) end subroutine interaction_evaluate_square_c pure subroutine interaction_evaluate_sum (int, i, int1, index1) class(interaction_t), intent(inout) :: int integer, intent(in) :: i type(interaction_t), intent(in) :: int1 integer, dimension(:), intent(in) :: index1 call int%state_matrix%evaluate_sum (i, int1%state_matrix, index1) end subroutine interaction_evaluate_sum pure subroutine interaction_evaluate_me_sum (int, i, int1, index1) class(interaction_t), intent(inout) :: int integer, intent(in) :: i type(interaction_t), intent(in) :: int1 integer, dimension(:), intent(in) :: index1 call int%state_matrix%evaluate_me_sum (i, int1%state_matrix, index1) end subroutine interaction_evaluate_me_sum @ %def interaction_evaluate_product @ %def interaction_evaluate_product_cf @ %def interaction_evaluate_square_c @ %def interaction_evaluate_sum @ %def interaction_evaluate_me_sum @ Tag quantum numbers of the state matrix as part of the hard process, according to the indices specified in [[tag]]. If no [[tag]] is given, all quantum numbers are tagged as part of the hard process. <>= procedure :: tag_hard_process => interaction_tag_hard_process <>= subroutine interaction_tag_hard_process (int, tag) class(interaction_t), intent(inout) :: int integer, dimension(:), intent(in), optional :: tag type(state_matrix_t) :: state call int%state_matrix%tag_hard_process (state, tag) call int%state_matrix%final () int%state_matrix = state end subroutine interaction_tag_hard_process @ %def interaction_tag_hard_process @ Modify hard-interaction flags at the specified particle-position, in-place. <>= procedure :: retag_hard_process => interaction_retag_hard_process <>= subroutine interaction_retag_hard_process (int, i, hard) class(interaction_t), intent(inout), target :: int integer, intent(in) :: i logical, intent(in) :: hard type(state_iterator_t) :: it call it%init (int%get_state_matrix_ptr ()) do while (it%is_valid ()) call it%retag_hard_process (i, hard) call it%advance () end do end subroutine interaction_retag_hard_process @ %def interaction_retag_hard_process @ \subsection{Accessing contents} Return the integer tag. <>= procedure :: get_tag => interaction_get_tag <>= function interaction_get_tag (int) result (tag) class(interaction_t), intent(in) :: int integer :: tag tag = int%tag end function interaction_get_tag @ %def interaction_get_tag @ Return the number of particles. <>= procedure :: get_n_tot => interaction_get_n_tot procedure :: get_n_in => interaction_get_n_in procedure :: get_n_vir => interaction_get_n_vir procedure :: get_n_out => interaction_get_n_out <>= pure function interaction_get_n_tot (object) result (n_tot) class(interaction_t), intent(in) :: object integer :: n_tot n_tot = object%n_tot end function interaction_get_n_tot pure function interaction_get_n_in (object) result (n_in) class(interaction_t), intent(in) :: object integer :: n_in n_in = object%n_in end function interaction_get_n_in pure function interaction_get_n_vir (object) result (n_vir) class(interaction_t), intent(in) :: object integer :: n_vir n_vir = object%n_vir end function interaction_get_n_vir pure function interaction_get_n_out (object) result (n_out) class(interaction_t), intent(in) :: object integer :: n_out n_out = object%n_out end function interaction_get_n_out @ %def interaction_get_n_tot @ %def interaction_get_n_in interaction_get_n_vir interaction_get_n_out @ Return a momentum index. The flags specify whether to keep/drop incoming, virtual, or outgoing momenta. Check for illegal values. <>= function idx (int, i, outgoing) integer :: idx type(interaction_t), intent(in) :: int integer, intent(in) :: i logical, intent(in), optional :: outgoing logical :: in, vir, out if (present (outgoing)) then in = .not. outgoing vir = .false. out = outgoing else in = .true. vir = .true. out = .true. end if idx = 0 if (in) then if (vir) then if (out) then if (i <= int%n_tot) idx = i else if (i <= int%n_in + int%n_vir) idx = i end if else if (out) then if (i <= int%n_in) then idx = i else if (i <= int%n_in + int%n_out) then idx = int%n_vir + i end if else if (i <= int%n_in) idx = i end if else if (vir) then if (out) then if (i <= int%n_vir + int%n_out) idx = int%n_in + i else if (i <= int%n_vir) idx = int%n_in + i end if else if (out) then if (i <= int%n_out) idx = int%n_in + int%n_vir + i end if if (idx == 0) then call int%basic_write () print *, i, in, vir, out call msg_bug (" Momentum index is out of range for this interaction") end if end function idx @ %def idx @ Return all or just a specific four-momentum. <>= generic :: get_momenta => get_momenta_all, get_momenta_idx procedure :: get_momentum => interaction_get_momentum procedure :: get_momenta_all => interaction_get_momenta_all procedure :: get_momenta_idx => interaction_get_momenta_idx <>= function interaction_get_momenta_all (int, outgoing) result (p) class(interaction_t), intent(in) :: int type(vector4_t), dimension(:), allocatable :: p logical, intent(in), optional :: outgoing integer :: i if (present (outgoing)) then if (outgoing) then allocate (p (int%n_out)) else allocate (p (int%n_in)) end if else allocate (p (int%n_tot)) end if do i = 1, size (p) p(i) = int%p(idx (int, i, outgoing)) end do end function interaction_get_momenta_all function interaction_get_momenta_idx (int, jj) result (p) class(interaction_t), intent(in) :: int type(vector4_t), dimension(:), allocatable :: p integer, dimension(:), intent(in) :: jj allocate (p (size (jj))) p = int%p(jj) end function interaction_get_momenta_idx function interaction_get_momentum (int, i, outgoing) result (p) class(interaction_t), intent(in) :: int type(vector4_t) :: p integer, intent(in) :: i logical, intent(in), optional :: outgoing p = int%p(idx (int, i, outgoing)) end function interaction_get_momentum @ %def interaction_get_momenta interaction_get_momentum @ Return a shallow copy of the state matrix: <>= procedure :: get_state_matrix_ptr => & interaction_get_state_matrix_ptr <>= function interaction_get_state_matrix_ptr (int) result (state) class(interaction_t), intent(in), target :: int type(state_matrix_t), pointer :: state state => int%state_matrix end function interaction_get_state_matrix_ptr @ %def interaction_get_state_matrix_ptr @ Return the array of resonance flags <>= procedure :: get_resonance_flags => interaction_get_resonance_flags <>= function interaction_get_resonance_flags (int) result (resonant) class(interaction_t), intent(in) :: int logical, dimension(size(int%resonant)) :: resonant resonant = int%resonant end function interaction_get_resonance_flags @ %def interaction_get_resonance_flags @ Return the quantum-numbers mask (or part of it) <>= generic :: get_mask => get_mask_all, get_mask_slice procedure :: get_mask_all => interaction_get_mask_all procedure :: get_mask_slice => interaction_get_mask_slice <>= function interaction_get_mask_all (int) result (mask) class(interaction_t), intent(in) :: int type(quantum_numbers_mask_t), dimension(size(int%mask)) :: mask mask = int%mask end function interaction_get_mask_all function interaction_get_mask_slice (int, index) result (mask) class(interaction_t), intent(in) :: int integer, dimension(:), intent(in) :: index type(quantum_numbers_mask_t), dimension(size(index)) :: mask mask = int%mask(index) end function interaction_get_mask_slice @ %def interaction_get_mask @ Compute the invariant mass squared of the incoming particles (if any, otherwise outgoing). <>= public :: interaction_get_s <>= function interaction_get_s (int) result (s) real(default) :: s type(interaction_t), intent(in) :: int if (int%n_in /= 0) then s = sum (int%p(:int%n_in)) ** 2 else s = sum (int%p(int%n_vir + 1 : )) ** 2 end if end function interaction_get_s @ %def interaction_get_s @ Compute the Lorentz transformation that transforms the incoming particles from the center-of-mass frame to the lab frame where they are given. If the c.m. mass squared is negative or zero, return the identity. <>= public :: interaction_get_cm_transformation <>= function interaction_get_cm_transformation (int) result (lt) type(lorentz_transformation_t) :: lt type(interaction_t), intent(in) :: int type(vector4_t) :: p_cm real(default) :: s if (int%n_in /= 0) then p_cm = sum (int%p(:int%n_in)) else p_cm = sum (int%p(int%n_vir+1:)) end if s = p_cm ** 2 if (s > 0) then lt = boost (p_cm, sqrt (s)) else lt = identity end if end function interaction_get_cm_transformation @ %def interaction_get_cm_transformation @ Return flavor, momentum, and position of the first outgoing unstable particle present in the interaction. Note that we need not iterate through the state matrix; if there is an unstable particle, it will be present in all state-matrix entries. <>= public :: interaction_get_unstable_particle <>= subroutine interaction_get_unstable_particle (int, flv, p, i) type(interaction_t), intent(in), target :: int type(flavor_t), intent(out) :: flv type(vector4_t), intent(out) :: p integer, intent(out) :: i type(state_iterator_t) :: it type(flavor_t), dimension(int%n_tot) :: flv_array call it%init (int%state_matrix) flv_array = it%get_flavor () do i = int%n_in + int%n_vir + 1, int%n_tot if (.not. flv_array(i)%is_stable ()) then flv = flv_array(i) p = int%p(i) return end if end do end subroutine interaction_get_unstable_particle @ %def interaction_get_unstable_particle @ Return the complete set of \emph{outgoing} flavors, assuming that the flavor quantum number is not suppressed. <>= public :: interaction_get_flv_out <>= subroutine interaction_get_flv_out (int, flv) type(interaction_t), intent(in), target :: int type(flavor_t), dimension(:,:), allocatable, intent(out) :: flv type(state_iterator_t) :: it type(flavor_t), dimension(:), allocatable :: flv_state integer :: n_in, n_vir, n_out, n_tot, n_state, i n_in = int%get_n_in () n_vir = int%get_n_vir () n_out = int%get_n_out () n_tot = int%get_n_tot () n_state = int%get_n_matrix_elements () allocate (flv (n_out, n_state)) allocate (flv_state (n_tot)) i = 1 call it%init (int%get_state_matrix_ptr ()) do while (it%is_valid ()) flv_state = it%get_flavor () flv(:,i) = flv_state(n_in + n_vir + 1 : ) i = i + 1 call it%advance () end do end subroutine interaction_get_flv_out @ %def interaction_get_flv_out @ Determine the flavor content of the interaction. We analyze the state matrix for this, and we select the outgoing particles of the hard process only for the required mask, which indicates the particles that can appear in any order in a matching event record. We have to assume that any radiated particles (beam remnants) appear at the beginning of the particles marked as outgoing. <>= public :: interaction_get_flv_content <>= subroutine interaction_get_flv_content (int, state_flv, n_out_hard) type(interaction_t), intent(in), target :: int type(state_flv_content_t), intent(out) :: state_flv integer, intent(in) :: n_out_hard logical, dimension(:), allocatable :: mask integer :: n_tot n_tot = int%get_n_tot () allocate (mask (n_tot), source = .false.) mask(n_tot-n_out_hard + 1 : ) = .true. call state_flv%fill (int%get_state_matrix_ptr (), mask) end subroutine interaction_get_flv_content @ %def interaction_get_flv_content @ \subsection{Modifying contents} Set the quantum numbers mask. <>= procedure :: set_mask => interaction_set_mask <>= subroutine interaction_set_mask (int, mask) class(interaction_t), intent(inout) :: int type(quantum_numbers_mask_t), dimension(:), intent(in) :: mask if (size (int%mask) /= size (mask)) & call msg_fatal ("Attempting to set mask with unfitting size!") int%mask = mask int%update_state_matrix = .true. end subroutine interaction_set_mask @ %def interaction_set_mask @ Merge a particular mask entry, respecting a possible helicity lock for this entry. We apply an OR relation, which means that quantum numbers are summed over if either of the two masks requires it. <>= subroutine interaction_merge_mask_entry (int, i, mask) type(interaction_t), intent(inout) :: int integer, intent(in) :: i type(quantum_numbers_mask_t), intent(in) :: mask type(quantum_numbers_mask_t) :: mask_tmp integer :: ii ii = idx (int, i) if (int%mask(ii) .neqv. mask) then int%mask(ii) = int%mask(ii) .or. mask if (int%hel_lock(ii) /= 0) then call mask_tmp%assign (mask, helicity=.true.) int%mask(int%hel_lock(ii)) = int%mask(int%hel_lock(ii)) .or. mask_tmp end if end if int%update_state_matrix = .true. end subroutine interaction_merge_mask_entry @ %def interaction_merge_mask_entry @ Fill the momenta array, do not care about the quantum numbers of particles. <>= procedure :: reset_momenta => interaction_reset_momenta procedure :: set_momenta => interaction_set_momenta procedure :: set_momentum => interaction_set_momentum <>= subroutine interaction_reset_momenta (int) class(interaction_t), intent(inout) :: int int%p = vector4_null int%p_is_known = .true. end subroutine interaction_reset_momenta subroutine interaction_set_momenta (int, p, outgoing) class(interaction_t), intent(inout) :: int type(vector4_t), dimension(:), intent(in) :: p logical, intent(in), optional :: outgoing integer :: i, index do i = 1, size (p) index = idx (int, i, outgoing) int%p(index) = p(i) int%p_is_known(index) = .true. end do end subroutine interaction_set_momenta subroutine interaction_set_momentum (int, p, i, outgoing) class(interaction_t), intent(inout) :: int type(vector4_t), intent(in) :: p integer, intent(in) :: i logical, intent(in), optional :: outgoing integer :: index index = idx (int, i, outgoing) int%p(index) = p int%p_is_known(index) = .true. end subroutine interaction_set_momentum @ %def interaction_reset_momenta @ %def interaction_set_momenta interaction_set_momentum @ This more sophisticated version of setting values is used for structure functions, in particular if nontrivial flavor, color, and helicity may be present: set values selectively for the given flavors. If there is more than one flavor, scan the interaction and check for a matching flavor at the specified particle location. If it matches, insert the value that corresponds to this flavor. <>= public :: interaction_set_flavored_values <>= subroutine interaction_set_flavored_values (int, value, flv_in, pos) type(interaction_t), intent(inout) :: int complex(default), dimension(:), intent(in) :: value type(flavor_t), dimension(:), intent(in) :: flv_in integer, intent(in) :: pos type(state_iterator_t) :: it type(flavor_t) :: flv integer :: i if (size (value) == 1) then call int%set_matrix_element (value(1)) else call it%init (int%state_matrix) do while (it%is_valid ()) flv = it%get_flavor (pos) SCAN_FLV: do i = 1, size (value) if (flv == flv_in(i)) then call it%set_matrix_element (value(i)) exit SCAN_FLV end if end do SCAN_FLV call it%advance () end do end if end subroutine interaction_set_flavored_values @ %def interaction_set_flavored_values @ \subsection{Handling Linked interactions} Store relations between corresponding particles within one interaction. The first particle is the parent, the second one the child. Links are established in both directions. These relations have no effect on the propagation of momenta etc., they are rather used for mother-daughter relations in event output. <>= procedure :: relate => interaction_relate <>= subroutine interaction_relate (int, i1, i2) class(interaction_t), intent(inout), target :: int integer, intent(in) :: i1, i2 if (i1 /= 0 .and. i2 /= 0) then call int%children(i1)%append (i2) call int%parents(i2)%append (i1) end if end subroutine interaction_relate @ %def interaction_relate @ Transfer internal parent-child relations defined within interaction [[int1]] to a new interaction [[int]] where the particle indices are mapped to. Some particles in [[int1]] may have no image in [[int]]. In that case, a child entry maps to zero, and we skip this relation. Also transfer resonance flags. <>= procedure :: transfer_relations => interaction_transfer_relations <>= subroutine interaction_transfer_relations (int1, int2, map) class(interaction_t), intent(in) :: int1 class(interaction_t), intent(inout), target :: int2 integer, dimension(:), intent(in) :: map integer :: i, j, k do i = 1, size (map) do j = 1, int1%parents(i)%get_length () k = int1%parents(i)%get_link (j) call int2%relate (map(k), map(i)) end do if (map(i) /= 0) then int2%resonant(map(i)) = int1%resonant(i) end if end do end subroutine interaction_transfer_relations @ %def interaction_transfer_relations @ Make up internal parent-child relations for the particle(s) that are connected to a new interaction [[int]]. If [[resonant]] is defined and true, the connections are marked as resonant in the result interaction. Also, the children of the resonant connections are untagged if they were tagged with hard-interaction flags previously. <>= procedure :: relate_connections => interaction_relate_connections <>= subroutine interaction_relate_connections & (int, int_in, connection_index, & map, map_connections, resonant) class(interaction_t), intent(inout), target :: int class(interaction_t), intent(in) :: int_in integer, dimension(:), intent(in) :: connection_index integer, dimension(:), intent(in) :: map, map_connections logical, intent(in), optional :: resonant logical :: reson integer :: i, j, i2, k2 reson = .false.; if (present (resonant)) reson = resonant do i = 1, size (map_connections) k2 = connection_index(i) do j = 1, int_in%children(k2)%get_length () i2 = int_in%children(k2)%get_link (j) call int%relate (map_connections(i), map(i2)) if (reson) call int%retag_hard_process (map(i2), .false.) end do int%resonant(map_connections(i)) = reson end do end subroutine interaction_relate_connections @ %def interaction_relate_connections. @ Return the number of source/target links of the internal connections of particle [[i]]. <>= public :: interaction_get_n_children public :: interaction_get_n_parents <>= function interaction_get_n_children (int, i) result (n) integer :: n type(interaction_t), intent(in) :: int integer, intent(in) :: i n = int%children(i)%get_length () end function interaction_get_n_children function interaction_get_n_parents (int, i) result (n) integer :: n type(interaction_t), intent(in) :: int integer, intent(in) :: i n = int%parents(i)%get_length () end function interaction_get_n_parents @ %def interaction_get_n_children interaction_get_n_parents @ Return the source/target links of the internal connections of particle [[i]] as an array. <>= public :: interaction_get_children public :: interaction_get_parents <>= function interaction_get_children (int, i) result (idx) integer, dimension(:), allocatable :: idx type(interaction_t), intent(in) :: int integer, intent(in) :: i integer :: k, l l = int%children(i)%get_length () allocate (idx (l)) do k = 1, l idx(k) = int%children(i)%get_link (k) end do end function interaction_get_children function interaction_get_parents (int, i) result (idx) integer, dimension(:), allocatable :: idx type(interaction_t), intent(in) :: int integer, intent(in) :: i integer :: k, l l = int%parents(i)%get_length () allocate (idx (l)) do k = 1, l idx(k) = int%parents(i)%get_link (k) end do end function interaction_get_parents @ %def interaction_get_children interaction_get_parents @ Add a source link from an interaction to a corresponding particle within another interaction. These links affect the propagation of particles: the two linked particles are considered as the same particle, outgoing and incoming. <>= procedure :: set_source_link => interaction_set_source_link <>= subroutine interaction_set_source_link (int, i, int1, i1) class(interaction_t), intent(inout) :: int integer, intent(in) :: i class(interaction_t), intent(in), target :: int1 integer, intent(in) :: i1 if (i /= 0) call external_link_set (int%source(i), int1, i1) end subroutine interaction_set_source_link @ %def interaction_set_source_link @ Reassign links to a new interaction (which is an image of the current interaction). <>= public :: interaction_reassign_links <>= subroutine interaction_reassign_links (int, int_src, int_target) type(interaction_t), intent(inout) :: int type(interaction_t), intent(in) :: int_src type(interaction_t), intent(in), target :: int_target integer :: i if (allocated (int%source)) then do i = 1, size (int%source) call external_link_reassign (int%source(i), int_src, int_target) end do end if end subroutine interaction_reassign_links @ %def interaction_reassign_links @ Since links are one-directional, if we want to follow them backwards we have to scan all possibilities. This procedure returns the index of the particle within [[int]] which points to the particle [[i1]] within interaction [[int1]]. If unsuccessful, return zero. <>= public :: interaction_find_link <>= function interaction_find_link (int, int1, i1) result (i) integer :: i type(interaction_t), intent(in) :: int, int1 integer, intent(in) :: i1 type(interaction_t), pointer :: int_tmp do i = 1, int%n_tot int_tmp => external_link_get_ptr (int%source(i)) if (int_tmp%tag == int1%tag) then if (external_link_get_index (int%source(i)) == i1) return end if end do i = 0 end function interaction_find_link @ %def interaction_find_link @ The inverse: return interaction pointer and index for the ultimate source of [[i]] within [[int]]. <>= procedure :: find_source => interaction_find_source <>= subroutine interaction_find_source (int, i, int1, i1) class(interaction_t), intent(in) :: int integer, intent(in) :: i type(interaction_t), intent(out), pointer :: int1 integer, intent(out) :: i1 type(external_link_t) :: link link = interaction_get_ultimate_source (int, i) int1 => external_link_get_ptr (link) i1 = external_link_get_index (link) end subroutine interaction_find_source @ %def interaction_find_source @ Follow source links recursively to return the ultimate source of a particle. <>= function interaction_get_ultimate_source (int, i) result (link) type(external_link_t) :: link type(interaction_t), intent(in) :: int integer, intent(in) :: i type(interaction_t), pointer :: int_src integer :: i_src link = int%source(i) if (external_link_is_set (link)) then do int_src => external_link_get_ptr (link) i_src = external_link_get_index (link) if (external_link_is_set (int_src%source(i_src))) then link = int_src%source(i_src) else exit end if end do end if end function interaction_get_ultimate_source @ %def interaction_get_ultimate_source @ Update mask entries by merging them with corresponding masks in interactions linked to the current one. The mask determines quantum numbers which are summed over. Note that both the mask of the current interaction and the mask of the linked interaction are updated (side effect!). This ensures that both agree for the linked particle. <>= public :: interaction_exchange_mask <>= subroutine interaction_exchange_mask (int) type(interaction_t), intent(inout) :: int integer :: i, index_link type(interaction_t), pointer :: int_link do i = 1, int%n_tot if (external_link_is_set (int%source(i))) then int_link => external_link_get_ptr (int%source(i)) index_link = external_link_get_index (int%source(i)) call interaction_merge_mask_entry & (int, i, int_link%mask(index_link)) call interaction_merge_mask_entry & (int_link, index_link, int%mask(i)) end if end do call int%freeze () end subroutine interaction_exchange_mask @ %def interaction_exchange_mask @ Copy momenta from interactions linked to the current one. <>= procedure :: receive_momenta => interaction_receive_momenta <>= subroutine interaction_receive_momenta (int) class(interaction_t), intent(inout) :: int integer :: i, index_link type(interaction_t), pointer :: int_link do i = 1, int%n_tot if (external_link_is_set (int%source(i))) then int_link => external_link_get_ptr (int%source(i)) index_link = external_link_get_index (int%source(i)) call int%set_momentum (int_link%p(index_link), i) end if end do end subroutine interaction_receive_momenta @ %def interaction_receive_momenta @ The inverse operation: Copy momenta back to the interactions linked to the current one. <>= public :: interaction_send_momenta <>= subroutine interaction_send_momenta (int) type(interaction_t), intent(in) :: int integer :: i, index_link type(interaction_t), pointer :: int_link do i = 1, int%n_tot if (external_link_is_set (int%source(i))) then int_link => external_link_get_ptr (int%source(i)) index_link = external_link_get_index (int%source(i)) call int_link%set_momentum (int%p(i), index_link) end if end do end subroutine interaction_send_momenta @ %def interaction_send_momenta @ For numerical comparisons: pacify all momenta in an interaction. <>= public :: interaction_pacify_momenta <>= subroutine interaction_pacify_momenta (int, acc) type(interaction_t), intent(inout) :: int real(default), intent(in) :: acc integer :: i do i = 1, int%n_tot call pacify (int%p(i), acc) end do end subroutine interaction_pacify_momenta @ %def interaction_pacify_momenta @ For each subtraction entry starting from [[SUB = 0]], we duplicate the original state matrix entries as is. <>= procedure :: declare_subtraction => interaction_declare_subtraction <>= subroutine interaction_declare_subtraction (int, n_sub) class(interaction_t), intent(inout), target :: int integer, intent(in) :: n_sub integer :: i_sub type(state_iterator_t) :: it type(quantum_numbers_t), dimension(:), allocatable :: qn type(state_matrix_t) :: state_matrix call state_matrix%init (store_values = .true.) allocate (qn (int%get_state_depth ())) do i_sub = 0, n_sub call it%init (int%state_matrix) do while (it%is_valid ()) qn = it%get_quantum_numbers () call qn%set_subtraction_index (i_sub) call state_matrix%add_state (qn, value = it%get_matrix_element ()) call it%advance () end do end do call state_matrix%freeze () call state_matrix%set_n_sub () call int%state_matrix%final () int%state_matrix = state_matrix end subroutine interaction_declare_subtraction @ %def interaction_declare_subtraction @ \subsection{Recovering connections} When creating an evaluator for two interactions, we have to know by which particles they are connected. The connection indices can be determined if we have two linked interactions. We assume that [[int1]] is the source and [[int2]] the target, so the connections of interest are stored within [[int2]]. A connection is found if either the source is [[int1]], or the (ultimate) source of a particle within [[int2]] coincides with the (ultimate) source of a particle within [[int1]]. The result is an array of index pairs. To make things simple, we scan the interaction twice, once for counting hits, then allocate the array, then scan again and store the connections. The connections are scanned for [[int2]], which has sources in [[int1]]. It may happen that the order of connections is interchanged (crossed). We require the indices in [[int1]] to be sorted, so we reorder both index arrays correspondingly before returning them. (After this, the indices in [[int2]] may be out of order.) <>= public :: find_connections <>= subroutine find_connections (int1, int2, n, connection_index) class(interaction_t), intent(in) :: int1, int2 integer, intent(out) :: n integer, dimension(:,:), intent(out), allocatable :: connection_index integer, dimension(:,:), allocatable :: conn_index_tmp integer, dimension(:), allocatable :: ordering integer :: i, j, k type(external_link_t) :: link1, link2 type(interaction_t), pointer :: int_link1, int_link2 n = 0 do i = 1, size (int2%source) link2 = interaction_get_ultimate_source (int2, i) if (external_link_is_set (link2)) then int_link2 => external_link_get_ptr (link2) if (int_link2%tag == int1%tag) then n = n + 1 else k = external_link_get_index (link2) do j = 1, size (int1%source) link1 = interaction_get_ultimate_source (int1, j) if (external_link_is_set (link1)) then int_link1 => external_link_get_ptr (link1) if (int_link1%tag == int_link2%tag) then if (external_link_get_index (link1) == k) & n = n + 1 end if end if end do end if end if end do allocate (conn_index_tmp (n, 2)) n = 0 do i = 1, size (int2%source) link2 = interaction_get_ultimate_source (int2, i) if (external_link_is_set (link2)) then int_link2 => external_link_get_ptr (link2) if (int_link2%tag == int1%tag) then n = n + 1 conn_index_tmp(n,1) = external_link_get_index (int2%source(i)) conn_index_tmp(n,2) = i else k = external_link_get_index (link2) do j = 1, size (int1%source) link1 = interaction_get_ultimate_source (int1, j) if (external_link_is_set (link1)) then int_link1 => external_link_get_ptr (link1) if (int_link1%tag == int_link2%tag) then if (external_link_get_index (link1) == k) then n = n + 1 conn_index_tmp(n,1) = j conn_index_tmp(n,2) = i end if end if end if end do end if end if end do allocate (connection_index (n, 2)) if (n > 1) then allocate (ordering (n)) ordering = order (conn_index_tmp(:,1)) connection_index = conn_index_tmp(ordering,:) else connection_index = conn_index_tmp end if end subroutine find_connections @ %def find_connections @ \subsection{Unit tests} Test module, followed by the corresponding implementation module. <<[[interactions_ut.f90]]>>= <> module interactions_ut use unit_tests use interactions_uti <> <> contains <> end module interactions_ut @ %def interactions_ut @ <<[[interactions_uti.f90]]>>= <> module interactions_uti <> use lorentz use flavors use colors use helicities use quantum_numbers use state_matrices use interactions <> <> contains <> end module interactions_uti @ %def interactions_ut @ API: driver for the unit tests below. <>= public :: interaction_test <>= subroutine interaction_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine interaction_test @ %def interaction_test @ Generate an interaction of a polarized virtual photon and a colored quark which may be either up or down. Remove the quark polarization. Generate another interaction for the quark radiating a photon and link this to the first interation. The radiation ignores polarization; transfer this information to the first interaction to simplify it. Then, transfer the momentum to the radiating quark and perform a splitting. <>= call test (interaction_1, "interaction_1", & "check interaction setup", & u, results) <>= public :: interaction_1 <>= subroutine interaction_1 (u) integer, intent(in) :: u type(interaction_t), target :: int, rad type(vector4_t), dimension(3) :: p type(quantum_numbers_mask_t), dimension(3) :: mask p(2) = vector4_moving (500._default, 500._default, 1) p(3) = vector4_moving (500._default,-500._default, 1) p(1) = p(2) + p(3) write (u, "(A)") "* Test output: interaction" write (u, "(A)") "* Purpose: check routines for interactions" write (u, "(A)") call int%basic_init (1, 0, 2, set_relations=.true., & store_values = .true. ) call int_set (int, 1, -1, 1, 1, & cmplx (0.3_default, 0.1_default, kind=default)) call int_set (int, 1, -1,-1, 1, & cmplx (0.5_default,-0.7_default, kind=default)) call int_set (int, 1, 1, 1, 1, & cmplx (0.1_default, 0._default, kind=default)) call int_set (int, -1, 1, -1, 2, & cmplx (0.4_default, -0.1_default, kind=default)) call int_set (int, 1, 1, 1, 2, & cmplx (0.2_default, 0._default, kind=default)) call int%freeze () call int%set_momenta (p) mask = quantum_numbers_mask (.false.,.false., [.true.,.true.,.true.]) call rad%basic_init (1, 0, 2, & mask=mask, set_relations=.true., store_values = .true.) call rad_set (1) call rad_set (2) call rad%set_source_link (1, int, 2) call interaction_exchange_mask (rad) call rad%receive_momenta () p(1) = rad%get_momentum (1) p(2) = 0.4_default * p(1) p(3) = p(1) - p(2) call rad%set_momenta (p(2:3), outgoing=.true.) call int%freeze () call rad%freeze () call rad%set_matrix_element & (cmplx (0._default, 0._default, kind=default)) call int%basic_write (u) write (u, "(A)") call rad%basic_write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call int%final () call rad%final () write (u, "(A)") write (u, "(A)") "* Test interaction_1: successful." contains subroutine int_set (int, h1, h2, hq, q, val) type(interaction_t), target, intent(inout) :: int integer, intent(in) :: h1, h2, hq, q type(flavor_t), dimension(3) :: flv type(color_t), dimension(3) :: col type(helicity_t), dimension(3) :: hel type(quantum_numbers_t), dimension(3) :: qn complex(default), intent(in) :: val call flv%init ([21, q, -q]) call col(2)%init_col_acl (5, 0) call col(3)%init_col_acl (0, 5) call hel%init ([h1, hq, -hq], [h2, hq, -hq]) call qn%init (flv, col, hel) call int%add_state (qn) call int%set_matrix_element (val) end subroutine int_set subroutine rad_set (q) integer, intent(in) :: q type(flavor_t), dimension(3) :: flv type(quantum_numbers_t), dimension(3) :: qn call flv%init ([ q, q, 21 ]) call qn%init (flv) call rad%add_state (qn) end subroutine rad_set end subroutine interaction_1 @ %def interaction_1 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Matrix element evaluation} The [[evaluator_t]] type is an extension of the [[interaction_t]] type. It represents either a density matrix as the square of a transition matrix element, or the product of two density matrices. Usually, some quantum numbers are summed over in the result. The [[interaction_t]] subobject represents a multi-particle interaction with incoming, virtual, and outgoing particles and the associated (not necessarily diagonal) density matrix of quantum state. When the evaluator is initialized, this interaction is constructed from the input interaction(s). In addition, the initialization process sets up a multiplication table. For each matrix element of the result, it states which matrix elements are to be taken from the input interaction(s), multiplied (optionally, with an additional weight factor) and summed over. Eventually, to a processes we associate a chain of evaluators which are to be evaluated sequentially. The physical event and its matrix element value(s) can be extracted from the last evaluator in such a chain. Evaluators are constructed only once (as long as this is possible) during an initialization step. Then, for each event, momenta are computed and transferred among evaluators using the links within the interaction subobject. The multiplication tables enable fast evaluation of the result without looking at quantum numbers anymore. <<[[evaluators.f90]]>>= <> module evaluators <> <> use io_units use format_defs, only: FMT_19 use physics_defs, only: n_beams_rescaled use diagnostics use lorentz use flavors use colors use helicities use quantum_numbers use state_matrices use interactions <> <> <> <> <> contains <> end module evaluators @ %def evaluators @ \subsection{Array of pairings} The evaluator contains an array of [[pairing_array]] objects. This makes up the multiplication table. -Each pairing array contains two list of matrix element indices and a +Each pairing array contains two lists of matrix element indices and a list of numerical factors. The matrix element indices correspond to the input interactions. The corresponding matrix elements are to be multiplied and optionally multiplied by a factor. The results are summed over to yield one specific matrix element of the result evaluator. <>= type :: pairing_array_t integer, dimension(:), allocatable :: i1, i2 complex(default), dimension(:), allocatable :: factor end type pairing_array_t @ %def pairing_array_t <>= elemental subroutine pairing_array_init (pa, n, has_i2, has_factor) type(pairing_array_t), intent(out) :: pa integer, intent(in) :: n logical, intent(in) :: has_i2, has_factor allocate (pa%i1 (n)) if (has_i2) allocate (pa%i2 (n)) if (has_factor) allocate (pa%factor (n)) end subroutine pairing_array_init @ %def pairing_array_init @ <>= public :: pairing_array_write <>= subroutine pairing_array_write (pa, unit) type(pairing_array_t), intent(in) :: pa integer, intent(in), optional :: unit integer :: i, u u = given_output_unit (unit); if (u < 0) return write (u, "(A)", advance = "no") "[" if (allocated (pa%i1)) then write (u, "(I0,A)", advance = "no") pa%i1, "," else write (u, "(A)", advance = "no") "x," end if if (allocated (pa%i2)) then write (u, "(I0,A)", advance = "no") pa%i1, "," else write (u, "(A)", advance = "no") "x," end if write (u, "(A)", advance = "no") "]" if (allocated (pa%factor)) then write (u, "(A,F5.4,A,F5.4,A)") ";(", & real(pa%factor), ",", aimag(pa%factor), ")]" else write (u, "(A)") "" end if end subroutine pairing_array_write @ %def pairing_array_write @ \subsection{The evaluator type} Possible variants of evaluators: <>= integer, parameter :: & EVAL_UNDEFINED = 0, & EVAL_PRODUCT = 1, & EVAL_SQUARED_FLOWS = 2, & EVAL_SQUARE_WITH_COLOR_FACTORS = 3, & EVAL_COLOR_CONTRACTION = 4, & EVAL_IDENTITY = 5, & EVAL_QN_SUM = 6 @ %def EVAL_PRODUCT EVAL_SQUARED_FLOWS EVAL_SQUARE_WITH_COLOR_FACTORS @ %def EVAL_COLOR_CONTRACTION EVAL_QN_SUM @ The evaluator type contains the result interaction and an array of pairing lists, one for each matrix element in the result interaction. <>= public :: evaluator_t <>= type, extends (interaction_t) :: evaluator_t private integer :: type = EVAL_UNDEFINED class(interaction_t), pointer :: int_in1 => null () class(interaction_t), pointer :: int_in2 => null () type(pairing_array_t), dimension(:), allocatable :: pairing_array contains <> end type evaluator_t @ %def evaluator_t @ Output. <>= procedure :: write => evaluator_write <>= subroutine evaluator_write (eval, unit, & verbose, show_momentum_sum, show_mass, show_state, show_table, & col_verbose, testflag) class(evaluator_t), intent(in) :: eval integer, intent(in), optional :: unit logical, intent(in), optional :: verbose, show_momentum_sum, show_mass logical, intent(in), optional :: show_state, show_table, col_verbose logical, intent(in), optional :: testflag logical :: conjugate, square, show_tab integer :: u u = given_output_unit (unit); if (u < 0) return show_tab = .true.; if (present (show_table)) show_tab = .false. call eval%basic_write & (unit, verbose, show_momentum_sum, show_mass, & show_state, col_verbose, testflag) if (show_tab) then write (u, "(1x,A)") "Matrix-element multiplication" write (u, "(2x,A)", advance="no") "Input interaction 1:" if (associated (eval%int_in1)) then write (u, "(1x,I0)") eval%int_in1%get_tag () else write (u, "(A)") " [undefined]" end if write (u, "(2x,A)", advance="no") "Input interaction 2:" if (associated (eval%int_in2)) then write (u, "(1x,I0)") eval%int_in2%get_tag () else write (u, "(A)") " [undefined]" end if select case (eval%type) case (EVAL_SQUARED_FLOWS, EVAL_SQUARE_WITH_COLOR_FACTORS) conjugate = .true. square = .true. case (EVAL_IDENTITY) write (u, "(1X,A)") "Identity evaluator, pairing array unused" return case default conjugate = .false. square = .false. end select call eval%write_pairing_array (conjugate, square, u) end if end subroutine evaluator_write @ %def evaluator_write @ <>= procedure :: write_pairing_array => evaluator_write_pairing_array <>= subroutine evaluator_write_pairing_array (eval, conjugate, square, unit) class(evaluator_t), intent(in) :: eval logical, intent(in) :: conjugate, square integer, intent(in), optional :: unit integer :: u, i, j u = given_output_unit (unit); if (u < 0) return if (allocated (eval%pairing_array)) then do i = 1, size (eval%pairing_array) write (u, "(2x,A,I0,A)") "ME(", i, ") = " do j = 1, size (eval%pairing_array(i)%i1) write (u, "(4x,A)", advance="no") "+" if (allocated (eval%pairing_array(i)%i2)) then write (u, "(1x,A,I0,A)", advance="no") & "ME1(", eval%pairing_array(i)%i1(j), ")" if (conjugate) then write (u, "(A)", advance="no") "* x" else write (u, "(A)", advance="no") " x" end if write (u, "(1x,A,I0,A)", advance="no") & "ME2(", eval%pairing_array(i)%i2(j), ")" else if (square) then write (u, "(1x,A)", advance="no") "|" write (u, "(A,I0,A)", advance="no") & "ME1(", eval%pairing_array(i)%i1(j), ")" write (u, "(A)", advance="no") "|^2" else write (u, "(1x,A,I0,A)", advance="no") & "ME1(", eval%pairing_array(i)%i1(j), ")" end if if (allocated (eval%pairing_array(i)%factor)) then write (u, "(1x,A)", advance="no") "x" write (u, "(1x,'('," // FMT_19 // ",','," // FMT_19 // & ",')')") eval%pairing_array(i)%factor(j) else write (u, *) end if end do end do end if end subroutine evaluator_write_pairing_array @ %def evaluator_write_pairing_array @ Assignment: Deep copy of the interaction component. <>= public :: assignment(=) <>= interface assignment(=) module procedure evaluator_assign end interface <>= subroutine evaluator_assign (eval_out, eval_in) type(evaluator_t), intent(out) :: eval_out type(evaluator_t), intent(in) :: eval_in eval_out%type = eval_in%type eval_out%int_in1 => eval_in%int_in1 eval_out%int_in2 => eval_in%int_in2 eval_out%interaction_t = eval_in%interaction_t if (allocated (eval_in%pairing_array)) then allocate (eval_out%pairing_array (size (eval_in%pairing_array))) eval_out%pairing_array = eval_in%pairing_array end if end subroutine evaluator_assign @ %def evaluator_assign @ \subsection{Auxiliary structures for evaluator creation} Creating an evaluator that properly handles all quantum numbers requires some bookkeeping. In this section, we define several auxiliary types and methods that organize and simplify this task. More structures are defined within the specific initializers (as local types and internal subroutines). These types are currently implemented in a partial object-oriented way: We define some basic methods for initialization etc.\ here, but the evaluator routines below do access their internals as well. This simplifies some things such as index addressing using array slices, at the expense of losing some clarity. \subsubsection{Index mapping} Index mapping are abundant when constructing an evaluator. To have arrays of index mappings, we define this: <>= type :: index_map_t integer, dimension(:), allocatable :: entry end type index_map_t @ %def index_map_t <>= elemental subroutine index_map_init (map, n) type(index_map_t), intent(out) :: map integer, intent(in) :: n allocate (map%entry (n)) map%entry = 0 end subroutine index_map_init @ %def index_map_init <>= function index_map_exists (map) result (flag) logical :: flag type(index_map_t), intent(in) :: map flag = allocated (map%entry) end function index_map_exists @ %def index_map_exists <>= interface size module procedure index_map_size end interface @ %def size <>= function index_map_size (map) result (s) integer :: s type(index_map_t), intent(in) :: map if (allocated (map%entry)) then s = size (map%entry) else s = 0 end if end function index_map_size @ %def index_map_size <>= interface assignment(=) module procedure index_map_assign_int module procedure index_map_assign_array end interface @ %def = <>= elemental subroutine index_map_assign_int (map, ival) type(index_map_t), intent(inout) :: map integer, intent(in) :: ival map%entry = ival end subroutine index_map_assign_int subroutine index_map_assign_array (map, array) type(index_map_t), intent(inout) :: map integer, dimension(:), intent(in) :: array map%entry = array end subroutine index_map_assign_array @ %def index_map_assign_int index_map_assign_array <>= elemental subroutine index_map_set_entry (map, i, ival) type(index_map_t), intent(inout) :: map integer, intent(in) :: i integer, intent(in) :: ival map%entry(i) = ival end subroutine index_map_set_entry @ %def index_map_set_entry <>= elemental function index_map_get_entry (map, i) result (ival) integer :: ival type(index_map_t), intent(in) :: map integer, intent(in) :: i ival = map%entry(i) end function index_map_get_entry @ %def index_map_get_entry @ \subsubsection{Index mapping (two-dimensional)} This is a variant with a square matrix instead of an array. <>= type :: index_map2_t integer :: s = 0 integer, dimension(:,:), allocatable :: entry end type index_map2_t @ %def index_map2_t <>= elemental subroutine index_map2_init (map, n) type(index_map2_t), intent(out) :: map integer, intent(in) :: n map%s = n allocate (map%entry (n, n)) end subroutine index_map2_init @ %def index_map2_init <>= function index_map2_exists (map) result (flag) logical :: flag type(index_map2_t), intent(in) :: map flag = allocated (map%entry) end function index_map2_exists @ %def index_map2_exists <>= interface size module procedure index_map2_size end interface @ %def size <>= function index_map2_size (map) result (s) integer :: s type(index_map2_t), intent(in) :: map s = map%s end function index_map2_size @ %def index_map2_size <>= interface assignment(=) module procedure index_map2_assign_int end interface @ %def = <>= elemental subroutine index_map2_assign_int (map, ival) type(index_map2_t), intent(inout) :: map integer, intent(in) :: ival map%entry = ival end subroutine index_map2_assign_int @ %def index_map2_assign_int <>= elemental subroutine index_map2_set_entry (map, i, j, ival) type(index_map2_t), intent(inout) :: map integer, intent(in) :: i, j integer, intent(in) :: ival map%entry(i,j) = ival end subroutine index_map2_set_entry @ %def index_map2_set_entry <>= elemental function index_map2_get_entry (map, i, j) result (ival) integer :: ival type(index_map2_t), intent(in) :: map integer, intent(in) :: i, j ival = map%entry(i,j) end function index_map2_get_entry @ %def index_map2_get_entry @ \subsubsection{Auxiliary structures: particle mask} This is a simple container of a logical array. <>= type :: prt_mask_t logical, dimension(:), allocatable :: entry end type prt_mask_t @ %def prt_mask_t <>= subroutine prt_mask_init (mask, n) type(prt_mask_t), intent(out) :: mask integer, intent(in) :: n allocate (mask%entry (n)) end subroutine prt_mask_init @ %def prt_mask_init <>= interface size module procedure prt_mask_size end interface @ %def size <>= function prt_mask_size (mask) result (s) integer :: s type(prt_mask_t), intent(in) :: mask s = size (mask%entry) end function prt_mask_size @ %def prt_mask_size @ \subsubsection{Quantum number containers} Trivial transparent containers: <>= type :: qn_list_t type(quantum_numbers_t), dimension(:,:), allocatable :: qn end type qn_list_t type :: qn_mask_array_t type(quantum_numbers_mask_t), dimension(:), allocatable :: mask end type qn_mask_array_t @ %def qn_list_t qn_mask_array_t @ \subsubsection{Auxiliary structures: connection entries} This type is used as intermediate storage when computing the product of two evaluators or the square of an evaluator. The quantum-number array [[qn]] corresponds to the particles common to both interactions, but irrelevant quantum numbers (color) masked out. The index arrays [[index_in]] determine the entries in the input interactions that contribute to this connection. [[n_index]] is the size of these arrays, and [[count]] is used while filling the entries. Finally, the quantum-number arrays [[qn_in_list]] are the actual entries in the input interaction that contribute. In the product case, they exclude the connected quantum numbers. Each evaluator has its own [[connection_table]] which contains an array of [[connection_entry]] objects, but also has stuff that specifically applies to the evaluator type. Hence, we do not generalize the [[connection_table_t]] type. The filling procedure [[connection_entry_add_state]] is specific to the various evaluator types. <>= type :: connection_entry_t type(quantum_numbers_t), dimension(:), allocatable :: qn_conn integer, dimension(:), allocatable :: n_index integer, dimension(:), allocatable :: count type(index_map_t), dimension(:), allocatable :: index_in type(qn_list_t), dimension(:), allocatable :: qn_in_list end type connection_entry_t @ %def connection_entry_t <>= subroutine connection_entry_init & (entry, n_count, n_map, qn_conn, count, n_rest) type(connection_entry_t), intent(out) :: entry integer, intent(in) :: n_count, n_map type(quantum_numbers_t), dimension(:), intent(in) :: qn_conn integer, dimension(n_count), intent(in) :: count integer, dimension(n_count), intent(in) :: n_rest integer :: i allocate (entry%qn_conn (size (qn_conn))) allocate (entry%n_index (n_count)) allocate (entry%count (n_count)) allocate (entry%index_in (n_map)) allocate (entry%qn_in_list (n_count)) entry%qn_conn = qn_conn entry%n_index = count entry%count = 0 if (size (entry%index_in) == size (count)) then call index_map_init (entry%index_in, count) else call index_map_init (entry%index_in, count(1)) end if do i = 1, n_count allocate (entry%qn_in_list(i)%qn (n_rest(i), count(i))) end do end subroutine connection_entry_init @ %def connection_entry_init <>= subroutine connection_entry_write (entry, unit) type(connection_entry_t), intent(in) :: entry integer, intent(in), optional :: unit integer :: i, j integer :: u u = given_output_unit (unit) call quantum_numbers_write (entry%qn_conn, unit) write (u, *) do i = 1, size (entry%n_index) write (u, *) "Input interaction", i do j = 1, entry%n_index(i) if (size (entry%n_index) == size (entry%index_in)) then write (u, "(2x,I0,4x,I0,2x)", advance = "no") & j, index_map_get_entry (entry%index_in(i), j) else write (u, "(2x,I0,4x,I0,2x,I0,2x)", advance = "no") & j, index_map_get_entry (entry%index_in(1), j), & index_map_get_entry (entry%index_in(2), j) end if call quantum_numbers_write (entry%qn_in_list(i)%qn(:,j), unit) write (u, *) end do end do end subroutine connection_entry_write @ %def connection_entry_write @ \subsubsection{Color handling} For managing color-factor computation, we introduce this local type. The [[index]] is the index in the color table that corresponds to a given matrix element index in the input interaction. The [[col]] array stores the color assignments in rows. The [[factor]] array associates a complex number with each pair of arrays in the color table. The [[factor_is_known]] array reveals whether a given factor is known already or still has to be computed. <>= type :: color_table_t integer, dimension(:), allocatable :: index type(color_t), dimension(:,:), allocatable :: col logical, dimension(:,:), allocatable :: factor_is_known complex(default), dimension(:,:), allocatable :: factor end type color_table_t @ %def color_table_t @ This is the initializer. We extract the color states from the given state matrices, establish index mappings between the two states (implemented by the array [[me_index]]), make an array of color states, and initialize the color-factor table. The latter is two-dimensional (includes interference) and not yet filled. <>= subroutine color_table_init (color_table, state, n_tot) type(color_table_t), intent(out) :: color_table type(state_matrix_t), intent(in) :: state integer, intent(in) :: n_tot type(state_iterator_t) :: it type(quantum_numbers_t), dimension(:), allocatable :: qn type(state_matrix_t) :: state_col integer :: index, n_col_state allocate (color_table%index (state%get_n_matrix_elements ())) color_table%index = 0 allocate (qn (n_tot)) call state_col%init () call it%init (state) do while (it%is_valid ()) index = it%get_me_index () call qn%init (col = it%get_color ()) call state_col%add_state (qn, me_index = color_table%index(index)) call it%advance () end do n_col_state = state_col%get_n_matrix_elements () allocate (color_table%col (n_tot, n_col_state)) call it%init (state_col) do while (it%is_valid ()) index = it%get_me_index () color_table%col(:,index) = it%get_color () call it%advance () end do call state_col%final () allocate (color_table%factor_is_known (n_col_state, n_col_state)) allocate (color_table%factor (n_col_state, n_col_state)) color_table%factor_is_known = .false. end subroutine color_table_init @ %def color_table_init @ Output (debugging use): <>= subroutine color_table_write (color_table, unit) type(color_table_t), intent(in) :: color_table integer, intent(in), optional :: unit integer :: i, j integer :: u u = given_output_unit (unit) write (u, *) "Color table:" if (allocated (color_table%index)) then write (u, *) " Index mapping state => color table:" do i = 1, size (color_table%index) write (u, "(3x,I0,2x,I0,2x)") i, color_table%index(i) end do write (u, *) " Color table:" do i = 1, size (color_table%col, 2) write (u, "(3x,I0,2x)", advance = "no") i call color_write (color_table%col(:,i), unit) write (u, *) end do write (u, *) " Defined color factors:" do i = 1, size (color_table%factor, 1) do j = 1, size (color_table%factor, 2) if (color_table%factor_is_known(i,j)) then write (u, *) i, j, color_table%factor(i,j) end if end do end do end if end subroutine color_table_write @ %def color_table_write @ This subroutine sets color factors, based on information from the hard matrix element: the list of pairs of color-flow indices (in the basis of the matrix element code), the list of corresponding factors, and the list of mappings from the matrix element index in the input interaction to the color-flow index in the hard matrix element object. We first determine the mapping of color-flow indices from the hard matrix element code to the current color table. The mapping could be nontrivial because the latter is derived from iterating over a state matrix, which may return states in non-canonical order. The translation table can be determined because we have, for the complete state matrix, both the mapping to the hard interaction (the input [[col_index_hi]]) and the mapping to the current color table (the component [[color_table%index]]). Once this mapping is known, we scan the list of index pairs [[color_flow_index]] and translate them to valid color-table index pairs. For this pair, the color factor is set using the corresponding entry in the list [[col_factor]]. <>= subroutine color_table_set_color_factors (color_table, & col_flow_index, col_factor, col_index_hi) type(color_table_t), intent(inout) :: color_table integer, dimension(:,:), intent(in) :: col_flow_index complex(default), dimension(:), intent(in) :: col_factor integer, dimension(:), intent(in) :: col_index_hi integer, dimension(:), allocatable :: hi_to_ct integer :: n_cflow integer :: hi_index, me_index, ct_index, cf_index integer, dimension(2) :: hi_index_pair, ct_index_pair n_cflow = size (col_index_hi) if (size (color_table%index) /= n_cflow) & call msg_bug ("Mismatch between hard matrix element and color table") allocate (hi_to_ct (n_cflow)) do me_index = 1, size (color_table%index) ct_index = color_table%index(me_index) hi_index = col_index_hi(me_index) hi_to_ct(hi_index) = ct_index end do do cf_index = 1, size (col_flow_index, 2) hi_index_pair = col_flow_index(:,cf_index) ct_index_pair = hi_to_ct(hi_index_pair) color_table%factor(ct_index_pair(1), ct_index_pair(2)) = & col_factor(cf_index) color_table%factor_is_known(ct_index_pair(1), ct_index_pair(2)) = .true. end do end subroutine color_table_set_color_factors @ %def color_table_set_color_factors @ This function returns a color factor, given two indices which point to the matrix elements of the initial state matrix. Internally, we can map them to the corresponding indices in the color table. As a side effect, we store the color factor in the color table for later lookup. (I.e., this function is impure.) <>= function color_table_get_color_factor (color_table, index1, index2, nc) & result (factor) real(default) :: factor type(color_table_t), intent(inout) :: color_table integer, intent(in) :: index1, index2 integer, intent(in), optional :: nc integer :: i1, i2 i1 = color_table%index(index1) i2 = color_table%index(index2) if (color_table%factor_is_known(i1,i2)) then factor = real(color_table%factor(i1,i2), kind=default) else factor = compute_color_factor & (color_table%col(:,i1), color_table%col(:,i2), nc) color_table%factor(i1,i2) = factor color_table%factor_is_known(i1,i2) = .true. end if end function color_table_get_color_factor @ %def color_table_get_color_factor @ \subsection{Creating an evaluator: Matrix multiplication} The evaluator for matrix multiplication is the most complicated variant. The initializer takes two input interactions and constructs the result evaluator, which consists of the interaction and the multiplication table for the product (or convolution) of the two. Normally, the input interactions are connected by one or more common particles (e.g., decay, structure function convolution). In the result interaction, quantum numbers of the connections can be summed over. This is determined by the [[qn_mask_conn]] argument. The [[qn_mask_rest]] argument is its analog for the other particles within the result interaction. (E.g., for the trace of the state matrix, all quantum numbers are summed over.) Finally, the [[connections_are_resonant]] argument tells whether the connecting particles should be marked as resonant in the final event record. If true, this also implies that the second interaction is not the hard process, so any corresponding tags should be removed from the outgoing particles. This applies to decays. The algorithm consists of the following steps: \begin{enumerate} \item [[find_connections]]: Find the particles which are connected, i.e., common to both input interactions. Either they are directly linked, or both are linked to a common source. \item [[compute_index_bounds_and_mappings]]: Compute the mappings of particle indices from the input interactions to the result interaction. There is a separate mapping for the connected particles. \item [[accumulate_connected_states]]: Create an auxiliary state matrix which lists the possible quantum numbers for the connected particles. When building this matrix, count the number of times each assignment is contained in any of the input states and, for each of the input states, record the index of the matrix element within the new state matrix. For the connected particles, reassign color indices such that no color state is present twice in different color-index assignment. Note that helicity assignments of the connected state can be (and will be) off-diagonal, so no spin correlations are lost in decays. Do this for both input interactions. \item [[allocate_connection_entries]]: Allocate a table of connections. Each table row corresponds to one state in the auxiliary matrix, and to multiple states of the input interactions. It collects all states of the unconnected particles in the two input interactions that are associated with the particular state (quantum-number assignment) of the connected particles. \item [[fill_connection_table]]: Fill the table of connections by scanning both input interactions. When copying states, reassign color indices for the unconnected particles such that they match between all involved particle sets (interaction 1, interaction 2, and connected particles). \item [[make_product_interaction]]: Scan the table of connections we have just built. For each entry, construct all possible pairs of states of the unconnected particles and combine them with the specific connected-particle state. This is a possible quantum-number assignment of the result interaction. Now mask all quantum numbers that should be summed over, and append this to the result state matrix. Record the matrix element index of the result. We now have the result interaction. \item [[make_pairing_array]]: First allocate the pairing array with the number of entries of the result interaction. Then scan the table of connections again. For each entry, record the indices of the matrix elements which have to be multiplied and summed over in order to compute this particular matrix element. This makes up the multiplication table. \item [[record_links]]: Transfer all source pointers from the input interactions to the result interaction. Do the same for the internal parent-child relations and resonance assignments. For the connected particles, make up appropriate additional parent-child relations. This allows for fetching momenta from other interactions when a new event is filled, and to reconstruct the event history when the event is analyzed. \end{enumerate} After all this is done, for each event, we just have to evaluate the pairing arrays (multiplication tables) in order to compute the result matrix elements in their proper positions. The quantum-number assignments remain fixed from now on. <>= procedure :: init_product => evaluator_init_product <>= subroutine evaluator_init_product & (eval, int_in1, int_in2, qn_mask_conn, qn_filter_conn, qn_mask_rest, & connections_are_resonant, ignore_sub_for_qn) class(evaluator_t), intent(out), target :: eval class(interaction_t), intent(in), target :: int_in1, int_in2 type(quantum_numbers_mask_t), intent(in) :: qn_mask_conn type(quantum_numbers_t), intent(in), optional :: qn_filter_conn type(quantum_numbers_mask_t), intent(in), optional :: qn_mask_rest logical, intent(in), optional :: connections_are_resonant logical, intent(in), optional :: ignore_sub_for_qn type(qn_mask_array_t), dimension(2) :: qn_mask_in type(state_matrix_t), pointer :: state_in1, state_in2 type :: connection_table_t integer :: n_conn = 0 integer, dimension(2) :: n_rest = 0 integer :: n_tot = 0 integer :: n_me_conn = 0 type(state_matrix_t) :: state type(index_map_t), dimension(:), allocatable :: index_conn type(connection_entry_t), dimension(:), allocatable :: entry type(index_map_t) :: index_result end type connection_table_t type(connection_table_t) :: connection_table integer :: n_in, n_vir, n_out, n_tot integer, dimension(2) :: n_rest integer :: n_conn integer, dimension(:,:), allocatable :: connection_index type(index_map_t), dimension(2) :: prt_map_in type(index_map_t) :: prt_map_conn type(prt_mask_t), dimension(2) :: prt_is_connected type(quantum_numbers_mask_t), dimension(:), allocatable :: & qn_mask_conn_initial, int_in1_mask, int_in2_mask integer :: i eval%type = EVAL_PRODUCT eval%int_in1 => int_in1 eval%int_in2 => int_in2 state_in1 => int_in1%get_state_matrix_ptr () state_in2 => int_in2%get_state_matrix_ptr () call find_connections (int_in1, int_in2, n_conn, connection_index) if (n_conn == 0) then call msg_message ("First interaction:") call int_in1%basic_write (col_verbose=.true.) call msg_message ("Second interaction:") call int_in2%basic_write (col_verbose=.true.) call msg_fatal ("Evaluator product: no connections found between factors") end if call compute_index_bounds_and_mappings & (int_in1, int_in2, n_conn, & n_in, n_vir, n_out, n_tot, & n_rest, prt_map_in, prt_map_conn) call prt_mask_init (prt_is_connected(1), int_in1%get_n_tot ()) call prt_mask_init (prt_is_connected(2), int_in2%get_n_tot ()) do i = 1, 2 prt_is_connected(i)%entry = .true. prt_is_connected(i)%entry(connection_index(:,i)) = .false. end do allocate (qn_mask_conn_initial (n_conn), & int_in1_mask (n_conn), int_in2_mask (n_conn)) int_in1_mask = int_in1%get_mask (connection_index(:,1)) int_in2_mask = int_in2%get_mask (connection_index(:,2)) do i = 1, n_conn qn_mask_conn_initial(i) = int_in1_mask(i) .or. int_in2_mask(i) end do allocate (qn_mask_in(1)%mask (int_in1%get_n_tot ())) allocate (qn_mask_in(2)%mask (int_in2%get_n_tot ())) qn_mask_in(1)%mask = int_in1%get_mask () qn_mask_in(2)%mask = int_in2%get_mask () - call connection_table_init (connection_table, & state_in1, state_in2, & qn_mask_conn_initial, & n_conn, connection_index, n_rest, & qn_filter_conn, ignore_sub_for_qn) call connection_table_fill (connection_table, & state_in1, state_in2, & connection_index, prt_is_connected) call make_product_interaction (eval%interaction_t, & n_in, n_vir, n_out, & connection_table, & prt_map_in, prt_is_connected, & qn_mask_in, qn_mask_conn_initial, & qn_mask_conn, qn_filter_conn, qn_mask_rest) call make_pairing_array (eval%pairing_array, & eval%get_n_matrix_elements (), & connection_table) call record_links (eval%interaction_t, & int_in1, int_in2, connection_index, prt_map_in, prt_map_conn, & prt_is_connected, connections_are_resonant) call connection_table_final (connection_table) - if (eval%get_n_matrix_elements () == 0) then print *, "Evaluator product" print *, "First interaction" call int_in1%basic_write (col_verbose=.true.) print * print *, "Second interaction" call int_in2%basic_write (col_verbose=.true.) print * call msg_fatal ("Product of density matrices is empty", & [var_str (" --------------------------------------------"), & var_str ("This happens when two density matrices are convoluted "), & var_str ("but the processes they belong to (e.g., production "), & var_str ("and decay) do not match. This could happen if the "), & var_str ("beam specification does not match the hard "), & var_str ("process. Or it may indicate a WHIZARD bug.")]) end if contains subroutine compute_index_bounds_and_mappings & (int1, int2, n_conn, & n_in, n_vir, n_out, n_tot, & n_rest, prt_map_in, prt_map_conn) class(interaction_t), intent(in) :: int1, int2 integer, intent(in) :: n_conn integer, intent(out) :: n_in, n_vir, n_out, n_tot integer, dimension(2), intent(out) :: n_rest type(index_map_t), dimension(2), intent(out) :: prt_map_in type(index_map_t), intent(out) :: prt_map_conn integer, dimension(:), allocatable :: index integer :: n_in1, n_vir1, n_out1 integer :: n_in2, n_vir2, n_out2 integer :: k n_in1 = int1%get_n_in () n_vir1 = int1%get_n_vir () n_out1 = int1%get_n_out () - n_conn n_rest(1) = n_in1 + n_vir1 + n_out1 n_in2 = int2%get_n_in () - n_conn n_vir2 = int2%get_n_vir () n_out2 = int2%get_n_out () n_rest(2) = n_in2 + n_vir2 + n_out2 n_in = n_in1 + n_in2 n_vir = n_vir1 + n_vir2 + n_conn n_out = n_out1 + n_out2 n_tot = n_in + n_vir + n_out call index_map_init (prt_map_in, n_rest) call index_map_init (prt_map_conn, n_conn) allocate (index (n_tot)) index = [ (i, i = 1, n_tot) ] prt_map_in(1)%entry(1 : n_in1) = index( 1 : n_in1) k = n_in1 prt_map_in(2)%entry(1 : n_in2) = index(k + 1 : k + n_in2) k = k + n_in2 prt_map_in(1)%entry(n_in1 + 1 : n_in1 + n_vir1) = index(k + 1 : k + n_vir1) k = k + n_vir1 prt_map_in(2)%entry(n_in2 + 1 : n_in2 + n_vir2) = index(k + 1 : k + n_vir2) k = k + n_vir2 prt_map_conn%entry = index(k + 1 : k + n_conn) k = k + n_conn prt_map_in(1)%entry(n_in1 + n_vir1 + 1 : n_rest(1)) = index(k + 1 : k + n_out1) k = k + n_out1 prt_map_in(2)%entry(n_in2 + n_vir2 + 1 : n_rest(2)) = index(k + 1 : k + n_out2) end subroutine compute_index_bounds_and_mappings subroutine connection_table_init & (connection_table, state_in1, state_in2, qn_mask_conn, & n_conn, connection_index, n_rest, & qn_filter_conn, ignore_sub_for_qn_in) type(connection_table_t), intent(out) :: connection_table type(state_matrix_t), intent(in), target :: state_in1, state_in2 type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask_conn integer, intent(in) :: n_conn integer, dimension(:,:), intent(in) :: connection_index integer, dimension(2), intent(in) :: n_rest type(quantum_numbers_t), intent(in), optional :: qn_filter_conn logical, intent(in), optional :: ignore_sub_for_qn_in integer, dimension(2) :: n_me_in type(state_iterator_t) :: it type(quantum_numbers_t), dimension(n_conn) :: qn integer :: i, me_index_in, me_index_conn, n_me_conn integer, dimension(2) :: me_count logical :: ignore_sub_for_qn, has_sub_qn integer :: i_beam_sub connection_table%n_conn = n_conn connection_table%n_rest = n_rest n_me_in(1) = state_in1%get_n_matrix_elements () n_me_in(2) = state_in2%get_n_matrix_elements () allocate (connection_table%index_conn (2)) call index_map_init (connection_table%index_conn, n_me_in) connection_table%index_conn = 0 call connection_table%state%init (n_counters = 2) do i = 1, 2 select case (i) case (1); call it%init (state_in1) case (2); call it%init (state_in2) end select do while (it%is_valid ()) qn = it%get_quantum_numbers (connection_index(:,i)) call qn%undefine (qn_mask_conn) if (present (qn_filter_conn)) then if (.not. all (qn .match. qn_filter_conn)) then call it%advance (); cycle end if end if call quantum_numbers_canonicalize_color (qn) me_index_in = it%get_me_index () ignore_sub_for_qn = .false.; if (present (ignore_sub_for_qn_in)) ignore_sub_for_qn = ignore_sub_for_qn_in has_sub_qn = .false. do i_beam_sub = 1, n_beams_rescaled has_sub_qn = has_sub_qn .or. any (qn%get_sub () == i_beam_sub) end do call connection_table%state%add_state (qn, & counter_index = i, & ignore_sub_for_qn = .not. (ignore_sub_for_qn .and. has_sub_qn), & me_index = me_index_conn) call index_map_set_entry (connection_table%index_conn(i), & me_index_in, me_index_conn) call it%advance () end do end do n_me_conn = connection_table%state%get_n_matrix_elements () connection_table%n_me_conn = n_me_conn allocate (connection_table%entry (n_me_conn)) call it%init (connection_table%state) do while (it%is_valid ()) i = it%get_me_index () me_count = it%get_me_count () call connection_entry_init (connection_table%entry(i), 2, 2, & it%get_quantum_numbers (), me_count, n_rest) call it%advance () end do end subroutine connection_table_init subroutine connection_table_final (connection_table) type(connection_table_t), intent(inout) :: connection_table call connection_table%state%final () end subroutine connection_table_final subroutine connection_table_write (connection_table, unit) type(connection_table_t), intent(in) :: connection_table integer, intent(in), optional :: unit integer :: i, j integer :: u u = given_output_unit (unit) write (u, *) "Connection table:" call connection_table%state%write (unit) if (allocated (connection_table%index_conn)) then write (u, *) " Index mapping input => connection table:" do i = 1, size (connection_table%index_conn) write (u, *) " Input state", i do j = 1, size (connection_table%index_conn(i)) write (u, *) j, & index_map_get_entry (connection_table%index_conn(i), j) end do end do end if if (allocated (connection_table%entry)) then write (u, *) " Connection table contents:" do i = 1, size (connection_table%entry) call connection_entry_write (connection_table%entry(i), unit) end do end if if (index_map_exists (connection_table%index_result)) then write (u, *) " Index mapping connection table => output:" do i = 1, size (connection_table%index_result) write (u, *) i, & index_map_get_entry (connection_table%index_result, i) end do end if end subroutine connection_table_write subroutine connection_table_fill & (connection_table, state_in1, state_in2, & connection_index, prt_is_connected) type(connection_table_t), intent(inout) :: connection_table type(state_matrix_t), intent(in), target :: state_in1, state_in2 integer, dimension(:,:), intent(in) :: connection_index type(prt_mask_t), dimension(2), intent(in) :: prt_is_connected type(state_iterator_t) :: it integer :: index_in, index_conn integer :: color_offset integer :: n_result_entries integer :: i, k color_offset = connection_table%state%get_max_color_value () do i = 1, 2 select case (i) case (1); call it%init (state_in1) case (2); call it%init (state_in2) end select do while (it%is_valid ()) index_in = it%get_me_index () index_conn = index_map_get_entry & (connection_table%index_conn(i), index_in) if (index_conn /= 0) then call connection_entry_add_state & (connection_table%entry(index_conn), i, & index_in, it%get_quantum_numbers (), & connection_index(:,i), prt_is_connected(i), & color_offset) end if call it%advance () end do color_offset = color_offset + state_in1%get_max_color_value () end do n_result_entries = 0 do k = 1, size (connection_table%entry) n_result_entries = & n_result_entries + product (connection_table%entry(k)%n_index) end do call index_map_init (connection_table%index_result, n_result_entries) end subroutine connection_table_fill subroutine connection_entry_add_state & (entry, i, index_in, qn_in, connection_index, prt_is_connected, & color_offset) type(connection_entry_t), intent(inout) :: entry integer, intent(in) :: i integer, intent(in) :: index_in type(quantum_numbers_t), dimension(:), intent(in) :: qn_in integer, dimension(:), intent(in) :: connection_index type(prt_mask_t), intent(in) :: prt_is_connected integer, intent(in) :: color_offset integer :: c integer, dimension(:,:), allocatable :: color_map entry%count(i) = entry%count(i) + 1 c = entry%count(i) call make_color_map (color_map, & qn_in(connection_index), entry%qn_conn) call index_map_set_entry (entry%index_in(i), c, index_in) entry%qn_in_list(i)%qn(:,c) = pack (qn_in, prt_is_connected%entry) call quantum_numbers_translate_color & (entry%qn_in_list(i)%qn(:,c), color_map, color_offset) end subroutine connection_entry_add_state subroutine make_product_interaction (int, & n_in, n_vir, n_out, & connection_table, & prt_map_in, prt_is_connected, & qn_mask_in, qn_mask_conn_initial, & qn_mask_conn, qn_filter_conn, qn_mask_rest) type(interaction_t), intent(out), target :: int integer, intent(in) :: n_in, n_vir, n_out type(connection_table_t), intent(inout), target :: connection_table type(index_map_t), dimension(2), intent(in) :: prt_map_in type(prt_mask_t), dimension(2), intent(in) :: prt_is_connected type(qn_mask_array_t), dimension(2), intent(in) :: qn_mask_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: & qn_mask_conn_initial type(quantum_numbers_mask_t), intent(in) :: qn_mask_conn type(quantum_numbers_t), intent(in), optional :: qn_filter_conn type(quantum_numbers_mask_t), intent(in), optional :: qn_mask_rest type(index_map_t), dimension(2) :: prt_index_in type(index_map_t) :: prt_index_conn integer :: n_tot, n_conn integer, dimension(2) :: n_rest integer :: i, j, k, m type(quantum_numbers_t), dimension(:), allocatable :: qn type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask type(connection_entry_t), pointer :: entry integer :: result_index n_conn = connection_table%n_conn n_rest = connection_table%n_rest n_tot = sum (n_rest) + n_conn allocate (qn (n_tot), qn_mask (n_tot)) do i = 1, 2 call index_map_init (prt_index_in(i), n_rest(i)) prt_index_in(i) = & prt_map_in(i)%entry ([ (j, j = 1, n_rest(i)) ]) end do call index_map_init (prt_index_conn, n_conn) prt_index_conn = prt_map_conn%entry ([ (j, j = 1, n_conn) ]) do i = 1, 2 if (present (qn_mask_rest)) then qn_mask(prt_index_in(i)%entry) = & pack (qn_mask_in(i)%mask, prt_is_connected(i)%entry) & .or. qn_mask_rest else qn_mask(prt_index_in(i)%entry) = & pack (qn_mask_in(i)%mask, prt_is_connected(i)%entry) end if end do qn_mask(prt_index_conn%entry) = qn_mask_conn_initial .or. qn_mask_conn call eval%interaction_t%basic_init (n_in, n_vir, n_out, mask = qn_mask) m = 1 do i = 1, connection_table%n_me_conn entry => connection_table%entry(i) qn(prt_index_conn%entry) = & quantum_numbers_undefined (entry%qn_conn, qn_mask_conn) if (present (qn_filter_conn)) then if (.not. all (qn(prt_index_conn%entry) .match. qn_filter_conn)) & cycle end if do j = 1, entry%n_index(1) qn(prt_index_in(1)%entry) = entry%qn_in_list(1)%qn(:,j) do k = 1, entry%n_index(2) qn(prt_index_in(2)%entry) = entry%qn_in_list(2)%qn(:,k) call int%add_state (qn, me_index = result_index) call index_map_set_entry & (connection_table%index_result, m, result_index) m = m + 1 end do end do end do call int%freeze () end subroutine make_product_interaction subroutine make_pairing_array (pa, n_matrix_elements, connection_table) type(pairing_array_t), dimension(:), intent(out), allocatable :: pa integer, intent(in) :: n_matrix_elements type(connection_table_t), intent(in), target :: connection_table type(connection_entry_t), pointer :: entry integer, dimension(:), allocatable :: n_entries integer :: i, j, k, m, r allocate (pa (n_matrix_elements)) allocate (n_entries (n_matrix_elements)) n_entries = 0 do m = 1, size (connection_table%index_result) r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 end do call pairing_array_init & (pa, n_entries, has_i2=.true., has_factor=.false.) m = 1 n_entries = 0 do i = 1, connection_table%n_me_conn entry => connection_table%entry(i) do j = 1, entry%n_index(1) do k = 1, entry%n_index(2) r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 pa(r)%i1(n_entries(r)) = & index_map_get_entry (entry%index_in(1), j) pa(r)%i2(n_entries(r)) = & index_map_get_entry (entry%index_in(2), k) m = m + 1 end do end do end do end subroutine make_pairing_array subroutine record_links (int, & int_in1, int_in2, connection_index, prt_map_in, prt_map_conn, & prt_is_connected, connections_are_resonant) class(interaction_t), intent(inout) :: int class(interaction_t), intent(in), target :: int_in1, int_in2 integer, dimension(:,:), intent(in) :: connection_index type(index_map_t), dimension(2), intent(in) :: prt_map_in type(index_map_t), intent(in) :: prt_map_conn type(prt_mask_t), dimension(2), intent(in) :: prt_is_connected logical, intent(in), optional :: connections_are_resonant type(index_map_t), dimension(2) :: prt_map_all integer :: i, j, k, ival call index_map_init (prt_map_all(1), size (prt_is_connected(1))) k = 0 j = 0 do i = 1, size (prt_is_connected(1)) if (prt_is_connected(1)%entry(i)) then j = j + 1 ival = index_map_get_entry (prt_map_in(1), j) call index_map_set_entry (prt_map_all(1), i, ival) else k = k + 1 ival = index_map_get_entry (prt_map_conn, k) call index_map_set_entry (prt_map_all(1), i, ival) end if call int%set_source_link (ival, int_in1, i) end do call int_in1%transfer_relations (int, prt_map_all(1)%entry) call index_map_init (prt_map_all(2), size (prt_is_connected(2))) j = 0 do i = 1, size (prt_is_connected(2)) if (prt_is_connected(2)%entry(i)) then j = j + 1 ival = index_map_get_entry (prt_map_in(2), j) call index_map_set_entry (prt_map_all(2), i, ival) call int%set_source_link (ival, int_in2, i) else call index_map_set_entry (prt_map_all(2), i, 0) end if end do call int_in2%transfer_relations (int, prt_map_all(2)%entry) call int%relate_connections & (int_in2, connection_index(:,2), prt_map_all(2)%entry, & prt_map_conn%entry, connections_are_resonant) end subroutine record_links end subroutine evaluator_init_product @ %def evaluator_init_product @ \subsection{Creating an evaluator: square} The generic initializer for an evaluator that squares a matrix element. Depending on the provided mask, we select the appropriate specific initializer for either diagonal or non-diagonal helicity density matrices. <>= procedure :: init_square => evaluator_init_square <>= subroutine evaluator_init_square (eval, int_in, qn_mask, & col_flow_index, col_factor, col_index_hi, expand_color_flows, nc) class(evaluator_t), intent(out), target :: eval class(interaction_t), intent(in), target :: int_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask integer, dimension(:,:), intent(in), optional :: col_flow_index complex(default), dimension(:), intent(in), optional :: col_factor integer, dimension(:), intent(in), optional :: col_index_hi logical, intent(in), optional :: expand_color_flows integer, intent(in), optional :: nc if (all (qn_mask%diagonal_helicity ())) then call eval%init_square_diag (int_in, qn_mask, & col_flow_index, col_factor, col_index_hi, expand_color_flows, nc) else call eval%init_square_nondiag (int_in, qn_mask, & col_flow_index, col_factor, col_index_hi, expand_color_flows, nc) end if end subroutine evaluator_init_square @ %def evaluator_init_square @ \subsubsection{Color-summed squared matrix (diagonal helicities)} The initializer for an evaluator that squares a matrix element, including color factors. The mask must be such that off-diagonal matrix elements are excluded. If [[color_flows]] is set, the evaluator keeps color-flow entries separate and drops all interfering color structures. The color factors are set to unity in this case. There is only one input interaction. The quantum-number mask is an array, one entry for each particle, so they can be treated individually. For academic purposes, we allow for the number of colors being different from three (but 3 is the default). The algorithm is analogous to multiplication, with a few notable differences: \begin{enumerate} \item The connected particles are known, the correspondence is one-to-one. All particles are connected, and the mapping of indices is trivial, which simplifies the following steps. \item [[accumulate_connected_states]]: The matrix of connected states encompasses all particles, but color indices are removed. However, ghost states are still kept separate from physical color states. No color-index reassignment is necessary. \item The table of connections contains single index and quantum-number arrays instead of pairs of them. They are paired with themselves in all possible ways. \item [[make_squared_interaction]]: Now apply the predefined quantum-numbers mask, which usually collects all color states (physical and ghosts), and possibly a helicity sum. \item [[make_pairing_array]]: For each pair of input states, compute the color factor (including a potential ghost-parity sign) and store this in the pairing array together with the matrix-element indices for multiplication. \item [[record_links]]: This is again trivial due to the one-to-one correspondence. \end{enumerate} <>= procedure :: init_square_diag => evaluator_init_square_diag <>= subroutine evaluator_init_square_diag (eval, int_in, qn_mask, & col_flow_index, col_factor, col_index_hi, expand_color_flows, nc) class(evaluator_t), intent(out), target :: eval class(interaction_t), intent(in), target :: int_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask integer, dimension(:,:), intent(in), optional :: col_flow_index complex(default), dimension(:), intent(in), optional :: col_factor integer, dimension(:), intent(in), optional :: col_index_hi logical, intent(in), optional :: expand_color_flows integer, intent(in), optional :: nc integer :: n_in, n_vir, n_out, n_tot type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask_initial type(state_matrix_t), pointer :: state_in type :: connection_table_t integer :: n_tot = 0 integer :: n_me_conn = 0 type(state_matrix_t) :: state type(index_map_t) :: index_conn type(connection_entry_t), dimension(:), allocatable :: entry type(index_map_t) :: index_result end type connection_table_t type(connection_table_t) :: connection_table logical :: sum_colors type(color_table_t) :: color_table if (present (expand_color_flows)) then sum_colors = .not. expand_color_flows else sum_colors = .true. end if if (sum_colors) then eval%type = EVAL_SQUARE_WITH_COLOR_FACTORS else eval%type = EVAL_SQUARED_FLOWS end if eval%int_in1 => int_in n_in = int_in%get_n_in () n_vir = int_in%get_n_vir () n_out = int_in%get_n_out () n_tot = int_in%get_n_tot () state_in => int_in%get_state_matrix_ptr () allocate (qn_mask_initial (n_tot)) qn_mask_initial = int_in%get_mask () call qn_mask_initial%set_color (sum_colors, mask_cg=.false.) if (sum_colors) then call color_table_init (color_table, state_in, n_tot) if (present (col_flow_index) .and. present (col_factor) & .and. present (col_index_hi)) then call color_table_set_color_factors & (color_table, col_flow_index, col_factor, col_index_hi) end if end if call connection_table_init (connection_table, state_in, & qn_mask_initial, qn_mask, n_tot) call connection_table_fill (connection_table, state_in) call make_squared_interaction (eval%interaction_t, & n_in, n_vir, n_out, n_tot, & connection_table, sum_colors, qn_mask_initial .or. qn_mask) call make_pairing_array (eval%pairing_array, & eval%get_n_matrix_elements (), & connection_table, sum_colors, color_table, n_in, n_tot, nc) call record_links (eval, int_in, n_tot) call connection_table_final (connection_table) contains subroutine connection_table_init & (connection_table, state_in, qn_mask_in, qn_mask, n_tot) type(connection_table_t), intent(out) :: connection_table type(state_matrix_t), intent(in), target :: state_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask integer, intent(in) :: n_tot type(quantum_numbers_t), dimension(n_tot) :: qn type(state_iterator_t) :: it integer :: i, n_me_in, me_index_in integer :: me_index_conn, n_me_conn integer, dimension(1) :: me_count logical :: qn_passed connection_table%n_tot = n_tot n_me_in = state_in%get_n_matrix_elements () call index_map_init (connection_table%index_conn, n_me_in) connection_table%index_conn = 0 call connection_table%state%init (n_counters=1) call it%init (state_in) do while (it%is_valid ()) qn = it%get_quantum_numbers () if (all (quantum_numbers_are_physical (qn, qn_mask))) then call qn%undefine (qn_mask_in) qn_passed = .true. if (qn_passed) then me_index_in = it%get_me_index () call connection_table%state%add_state (qn, & counter_index = 1, me_index = me_index_conn) call index_map_set_entry (connection_table%index_conn, & me_index_in, me_index_conn) end if end if call it%advance () end do n_me_conn = connection_table%state%get_n_matrix_elements () connection_table%n_me_conn = n_me_conn allocate (connection_table%entry (n_me_conn)) call it%init (connection_table%state) do while (it%is_valid ()) i = it%get_me_index () me_count = it%get_me_count () call connection_entry_init (connection_table%entry(i), 1, 2, & it%get_quantum_numbers (), me_count, [n_tot]) call it%advance () end do end subroutine connection_table_init subroutine connection_table_final (connection_table) type(connection_table_t), intent(inout) :: connection_table call connection_table%state%final () end subroutine connection_table_final subroutine connection_table_write (connection_table, unit) type(connection_table_t), intent(in) :: connection_table integer, intent(in), optional :: unit integer :: i integer :: u u = given_output_unit (unit) write (u, *) "Connection table:" call connection_table%state%write (unit) if (index_map_exists (connection_table%index_conn)) then write (u, *) " Index mapping input => connection table:" do i = 1, size (connection_table%index_conn) write (u, *) i, & index_map_get_entry (connection_table%index_conn, i) end do end if if (allocated (connection_table%entry)) then write (u, *) " Connection table contents" do i = 1, size (connection_table%entry) call connection_entry_write (connection_table%entry(i), unit) end do end if if (index_map_exists (connection_table%index_result)) then write (u, *) " Index mapping connection table => output" do i = 1, size (connection_table%index_result) write (u, *) i, & index_map_get_entry (connection_table%index_result, i) end do end if end subroutine connection_table_write subroutine connection_table_fill (connection_table, state) type(connection_table_t), intent(inout) :: connection_table type(state_matrix_t), intent(in), target :: state integer :: index_in, index_conn, n_result_entries type(state_iterator_t) :: it integer :: k call it%init (state) do while (it%is_valid ()) index_in = it%get_me_index () index_conn = & index_map_get_entry (connection_table%index_conn, index_in) if (index_conn /= 0) then call connection_entry_add_state & (connection_table%entry(index_conn), & index_in, it%get_quantum_numbers ()) end if call it%advance () end do n_result_entries = 0 do k = 1, size (connection_table%entry) n_result_entries = & n_result_entries + connection_table%entry(k)%n_index(1) ** 2 end do call index_map_init (connection_table%index_result, n_result_entries) connection_table%index_result = 0 end subroutine connection_table_fill subroutine connection_entry_add_state (entry, index_in, qn_in) type(connection_entry_t), intent(inout) :: entry integer, intent(in) :: index_in type(quantum_numbers_t), dimension(:), intent(in) :: qn_in integer :: c entry%count = entry%count + 1 c = entry%count(1) call index_map_set_entry (entry%index_in(1), c, index_in) entry%qn_in_list(1)%qn(:,c) = qn_in end subroutine connection_entry_add_state subroutine make_squared_interaction (int, & n_in, n_vir, n_out, n_tot, & connection_table, sum_colors, qn_mask) type(interaction_t), intent(out), target :: int integer, intent(in) :: n_in, n_vir, n_out, n_tot type(connection_table_t), intent(inout), target :: connection_table logical, intent(in) :: sum_colors type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask type(connection_entry_t), pointer :: entry integer :: result_index, n_contrib integer :: i, m type(quantum_numbers_t), dimension(n_tot) :: qn call eval%interaction_t%basic_init (n_in, n_vir, n_out, mask=qn_mask) m = 0 do i = 1, connection_table%n_me_conn entry => connection_table%entry(i) qn = quantum_numbers_undefined (entry%qn_conn, qn_mask) if (.not. sum_colors) call qn(1:n_in)%invert_color () call int%add_state (qn, me_index = result_index) n_contrib = entry%n_index(1) ** 2 connection_table%index_result%entry(m+1:m+n_contrib) = result_index m = m + n_contrib end do call int%freeze () end subroutine make_squared_interaction subroutine make_pairing_array (pa, & n_matrix_elements, connection_table, sum_colors, color_table, & n_in, n_tot, nc) type(pairing_array_t), dimension(:), intent(out), allocatable :: pa integer, intent(in) :: n_matrix_elements type(connection_table_t), intent(in), target :: connection_table logical, intent(in) :: sum_colors type(color_table_t), intent(inout) :: color_table type(connection_entry_t), pointer :: entry integer, intent(in) :: n_in, n_tot integer, intent(in), optional :: nc integer, dimension(:), allocatable :: n_entries integer :: i, k, l, ks, ls, m, r integer :: color_multiplicity_in allocate (pa (n_matrix_elements)) allocate (n_entries (n_matrix_elements)) n_entries = 0 do m = 1, size (connection_table%index_result) r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 end do call pairing_array_init & (pa, n_entries, has_i2 = sum_colors, has_factor = sum_colors) m = 1 n_entries = 0 do i = 1, connection_table%n_me_conn entry => connection_table%entry(i) do k = 1, entry%n_index(1) if (sum_colors) then color_multiplicity_in = product (abs & (entry%qn_in_list(1)%qn(:n_in, k)%get_color_type ())) do l = 1, entry%n_index(1) r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 ks = index_map_get_entry (entry%index_in(1), k) ls = index_map_get_entry (entry%index_in(1), l) pa(r)%i1(n_entries(r)) = ks pa(r)%i2(n_entries(r)) = ls pa(r)%factor(n_entries(r)) = & color_table_get_color_factor (color_table, ks, ls, nc) & / color_multiplicity_in m = m + 1 end do else r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 ks = index_map_get_entry (entry%index_in(1), k) pa(r)%i1(n_entries(r)) = ks m = m + 1 end if end do end do end subroutine make_pairing_array subroutine record_links (int, int_in, n_tot) class(interaction_t), intent(inout) :: int class(interaction_t), intent(in), target :: int_in integer, intent(in) :: n_tot integer, dimension(n_tot) :: map integer :: i do i = 1, n_tot call int%set_source_link (i, int_in, i) end do map = [ (i, i = 1, n_tot) ] call int_in%transfer_relations (int, map) end subroutine record_links end subroutine evaluator_init_square_diag @ %def evaluator_init_square_diag @ \subsubsection{Color-summed squared matrix (support nodiagonal helicities)} The initializer for an evaluator that squares a matrix element, including color factors. Unless requested otherwise by the quantum-number mask, the result contains off-diagonal matrix elements. (The input interaction must be diagonal since it represents an amplitude, not a density matrix.) There is only one input interaction. The quantum-number mask is an array, one entry for each particle, so they can be treated individually. For academic purposes, we allow for the number of colors being different from three (but 3 is the default). The algorithm is analogous to the previous one, with some additional complications due to the necessity to loop over two helicity indices. <>= procedure :: init_square_nondiag => evaluator_init_square_nondiag <>= subroutine evaluator_init_square_nondiag (eval, int_in, qn_mask, & col_flow_index, col_factor, col_index_hi, expand_color_flows, nc) class(evaluator_t), intent(out), target :: eval class(interaction_t), intent(in), target :: int_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask integer, dimension(:,:), intent(in), optional :: col_flow_index complex(default), dimension(:), intent(in), optional :: col_factor integer, dimension(:), intent(in), optional :: col_index_hi logical, intent(in), optional :: expand_color_flows integer, intent(in), optional :: nc integer :: n_in, n_vir, n_out, n_tot type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask_initial type(state_matrix_t), pointer :: state_in type :: connection_table_t integer :: n_tot = 0 integer :: n_me_conn = 0 type(state_matrix_t) :: state type(index_map2_t) :: index_conn type(connection_entry_t), dimension(:), allocatable :: entry type(index_map_t) :: index_result end type connection_table_t type(connection_table_t) :: connection_table logical :: sum_colors type(color_table_t) :: color_table if (present (expand_color_flows)) then sum_colors = .not. expand_color_flows else sum_colors = .true. end if if (sum_colors) then eval%type = EVAL_SQUARE_WITH_COLOR_FACTORS else eval%type = EVAL_SQUARED_FLOWS end if eval%int_in1 => int_in n_in = int_in%get_n_in () n_vir = int_in%get_n_vir () n_out = int_in%get_n_out () n_tot = int_in%get_n_tot () state_in => int_in%get_state_matrix_ptr () allocate (qn_mask_initial (n_tot)) qn_mask_initial = int_in%get_mask () call qn_mask_initial%set_color (sum_colors, mask_cg=.false.) if (sum_colors) then call color_table_init (color_table, state_in, n_tot) if (present (col_flow_index) .and. present (col_factor) & .and. present (col_index_hi)) then call color_table_set_color_factors & (color_table, col_flow_index, col_factor, col_index_hi) end if end if call connection_table_init (connection_table, state_in, & qn_mask_initial, qn_mask, n_tot) call connection_table_fill (connection_table, state_in) call make_squared_interaction (eval%interaction_t, & n_in, n_vir, n_out, n_tot, & connection_table, sum_colors, qn_mask_initial .or. qn_mask) call make_pairing_array (eval%pairing_array, & eval%get_n_matrix_elements (), & connection_table, sum_colors, color_table, n_in, n_tot, nc) call record_links (eval, int_in, n_tot) call connection_table_final (connection_table) contains subroutine connection_table_init & (connection_table, state_in, qn_mask_in, qn_mask, n_tot) type(connection_table_t), intent(out) :: connection_table type(state_matrix_t), intent(in), target :: state_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask_in type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask integer, intent(in) :: n_tot type(quantum_numbers_t), dimension(n_tot) :: qn1, qn2, qn type(state_iterator_t) :: it1, it2, it integer :: i, n_me_in, me_index_in1, me_index_in2 integer :: me_index_conn, n_me_conn integer, dimension(1) :: me_count logical :: qn_passed connection_table%n_tot = n_tot n_me_in = state_in%get_n_matrix_elements () call index_map2_init (connection_table%index_conn, n_me_in) connection_table%index_conn = 0 call connection_table%state%init (n_counters=1) call it1%init (state_in) do while (it1%is_valid ()) qn1 = it1%get_quantum_numbers () me_index_in1 = it1%get_me_index () call it2%init (state_in) do while (it2%is_valid ()) qn2 = it2%get_quantum_numbers () if (all (quantum_numbers_are_compatible (qn1, qn2, qn_mask))) then qn = qn1 .merge. qn2 call qn%undefine (qn_mask_in) qn_passed = .true. if (qn_passed) then me_index_in2 = it2%get_me_index () call connection_table%state%add_state (qn, & counter_index = 1, me_index = me_index_conn) call index_map2_set_entry (connection_table%index_conn, & me_index_in1, me_index_in2, me_index_conn) end if end if call it2%advance () end do call it1%advance () end do n_me_conn = connection_table%state%get_n_matrix_elements () connection_table%n_me_conn = n_me_conn allocate (connection_table%entry (n_me_conn)) call it%init (connection_table%state) do while (it%is_valid ()) i = it%get_me_index () me_count = it%get_me_count () call connection_entry_init (connection_table%entry(i), 1, 2, & it%get_quantum_numbers (), me_count, [n_tot]) call it%advance () end do end subroutine connection_table_init subroutine connection_table_final (connection_table) type(connection_table_t), intent(inout) :: connection_table call connection_table%state%final () end subroutine connection_table_final subroutine connection_table_write (connection_table, unit) type(connection_table_t), intent(in) :: connection_table integer, intent(in), optional :: unit integer :: i, j integer :: u u = given_output_unit (unit) write (u, *) "Connection table:" call connection_table%state%write (unit) if (index_map2_exists (connection_table%index_conn)) then write (u, *) " Index mapping input => connection table:" do i = 1, size (connection_table%index_conn) do j = 1, size (connection_table%index_conn) write (u, *) i, j, & index_map2_get_entry (connection_table%index_conn, i, j) end do end do end if if (allocated (connection_table%entry)) then write (u, *) " Connection table contents" do i = 1, size (connection_table%entry) call connection_entry_write (connection_table%entry(i), unit) end do end if if (index_map_exists (connection_table%index_result)) then write (u, *) " Index mapping connection table => output" do i = 1, size (connection_table%index_result) write (u, *) i, & index_map_get_entry (connection_table%index_result, i) end do end if end subroutine connection_table_write subroutine connection_table_fill (connection_table, state) type(connection_table_t), intent(inout), target :: connection_table type(state_matrix_t), intent(in), target :: state integer :: index1_in, index2_in, index_conn, n_result_entries type(state_iterator_t) :: it1, it2 integer :: k call it1%init (state) do while (it1%is_valid ()) index1_in = it1%get_me_index () call it2%init (state) do while (it2%is_valid ()) index2_in = it2%get_me_index () index_conn = index_map2_get_entry & (connection_table%index_conn, index1_in, index2_in) if (index_conn /= 0) then call connection_entry_add_state & (connection_table%entry(index_conn), & index1_in, index2_in, & it1%get_quantum_numbers () & .merge. & it2%get_quantum_numbers ()) end if call it2%advance () end do call it1%advance () end do n_result_entries = 0 do k = 1, size (connection_table%entry) n_result_entries = & n_result_entries + connection_table%entry(k)%n_index(1) end do call index_map_init (connection_table%index_result, n_result_entries) connection_table%index_result = 0 end subroutine connection_table_fill subroutine connection_entry_add_state (entry, index1_in, index2_in, qn_in) type(connection_entry_t), intent(inout) :: entry integer, intent(in) :: index1_in, index2_in type(quantum_numbers_t), dimension(:), intent(in) :: qn_in integer :: c entry%count = entry%count + 1 c = entry%count(1) call index_map_set_entry (entry%index_in(1), c, index1_in) call index_map_set_entry (entry%index_in(2), c, index2_in) entry%qn_in_list(1)%qn(:,c) = qn_in end subroutine connection_entry_add_state subroutine make_squared_interaction (int, & n_in, n_vir, n_out, n_tot, & connection_table, sum_colors, qn_mask) type(interaction_t), intent(out), target :: int integer, intent(in) :: n_in, n_vir, n_out, n_tot type(connection_table_t), intent(inout), target :: connection_table logical, intent(in) :: sum_colors type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask type(connection_entry_t), pointer :: entry integer :: result_index integer :: i, k, m type(quantum_numbers_t), dimension(n_tot) :: qn call eval%interaction_t%basic_init (n_in, n_vir, n_out, mask=qn_mask) m = 0 do i = 1, connection_table%n_me_conn entry => connection_table%entry(i) do k = 1, size (entry%qn_in_list(1)%qn, 2) qn = quantum_numbers_undefined & (entry%qn_in_list(1)%qn(:,k), qn_mask) if (.not. sum_colors) call qn(1:n_in)%invert_color () call int%add_state (qn, me_index = result_index) call index_map_set_entry (connection_table%index_result, m + 1, & result_index) m = m + 1 end do end do call int%freeze () end subroutine make_squared_interaction subroutine make_pairing_array (pa, & n_matrix_elements, connection_table, sum_colors, color_table, & n_in, n_tot, nc) type(pairing_array_t), dimension(:), intent(out), allocatable :: pa integer, intent(in) :: n_matrix_elements type(connection_table_t), intent(in), target :: connection_table logical, intent(in) :: sum_colors type(color_table_t), intent(inout) :: color_table type(connection_entry_t), pointer :: entry integer, intent(in) :: n_in, n_tot integer, intent(in), optional :: nc integer, dimension(:), allocatable :: n_entries integer :: i, k, k1s, k2s, m, r integer :: color_multiplicity_in allocate (pa (n_matrix_elements)) allocate (n_entries (n_matrix_elements)) n_entries = 0 do m = 1, size (connection_table%index_result) r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 end do call pairing_array_init & (pa, n_entries, has_i2 = sum_colors, has_factor = sum_colors) m = 1 n_entries = 0 do i = 1, connection_table%n_me_conn entry => connection_table%entry(i) do k = 1, entry%n_index(1) r = index_map_get_entry (connection_table%index_result, m) n_entries(r) = n_entries(r) + 1 if (sum_colors) then k1s = index_map_get_entry (entry%index_in(1), k) k2s = index_map_get_entry (entry%index_in(2), k) pa(r)%i1(n_entries(r)) = k1s pa(r)%i2(n_entries(r)) = k2s color_multiplicity_in = product (abs & (entry%qn_in_list(1)%qn(:n_in, k)%get_color_type ())) pa(r)%factor(n_entries(r)) = & color_table_get_color_factor (color_table, k1s, k2s, nc) & / color_multiplicity_in else k1s = index_map_get_entry (entry%index_in(1), k) pa(r)%i1(n_entries(r)) = k1s end if m = m + 1 end do end do end subroutine make_pairing_array subroutine record_links (int, int_in, n_tot) class(interaction_t), intent(inout) :: int class(interaction_t), intent(in), target :: int_in integer, intent(in) :: n_tot integer, dimension(n_tot) :: map integer :: i do i = 1, n_tot call int%set_source_link (i, int_in, i) end do map = [ (i, i = 1, n_tot) ] call int_in%transfer_relations (int, map) end subroutine record_links end subroutine evaluator_init_square_nondiag @ %def evaluator_init_square_nondiag @ \subsubsection{Copy with additional contracted color states} This evaluator involves no square or multiplication, its matrix elements are just copies of the (single) input interaction. However, the state matrix of the interaction contains additional states that have color indices contracted. This is used for copies of the beam or structure-function interactions that need to match the hard interaction also in the case where its color indices coincide. <>= procedure :: init_color_contractions => evaluator_init_color_contractions <>= subroutine evaluator_init_color_contractions (eval, int_in) class(evaluator_t), intent(out), target :: eval type(interaction_t), intent(in), target :: int_in integer :: n_in, n_vir, n_out, n_tot type(state_matrix_t) :: state_with_contractions integer, dimension(:), allocatable :: me_index integer, dimension(:), allocatable :: result_index eval%type = EVAL_COLOR_CONTRACTION eval%int_in1 => int_in n_in = int_in%get_n_in () n_vir = int_in%get_n_vir () n_out = int_in%get_n_out () n_tot = int_in%get_n_tot () state_with_contractions = int_in%get_state_matrix_ptr () call state_with_contractions%add_color_contractions () call make_contracted_interaction (eval%interaction_t, & me_index, result_index, & n_in, n_vir, n_out, n_tot, & state_with_contractions, int_in%get_mask ()) call make_pairing_array (eval%pairing_array, me_index, result_index) call record_links (eval, int_in, n_tot) call state_with_contractions%final () contains subroutine make_contracted_interaction (int, & me_index, result_index, & n_in, n_vir, n_out, n_tot, state, qn_mask) type(interaction_t), intent(out), target :: int integer, dimension(:), intent(out), allocatable :: me_index integer, dimension(:), intent(out), allocatable :: result_index integer, intent(in) :: n_in, n_vir, n_out, n_tot type(state_matrix_t), intent(in) :: state type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask type(state_iterator_t) :: it integer :: n_me, i type(quantum_numbers_t), dimension(n_tot) :: qn call int%basic_init (n_in, n_vir, n_out, mask=qn_mask) n_me = state%get_n_leaves () allocate (me_index (n_me)) allocate (result_index (n_me)) call it%init (state) i = 0 do while (it%is_valid ()) i = i + 1 me_index(i) = it%get_me_index () qn = it%get_quantum_numbers () call int%add_state (qn, me_index = result_index(i)) call it%advance () end do call int%freeze () end subroutine make_contracted_interaction subroutine make_pairing_array (pa, me_index, result_index) type(pairing_array_t), dimension(:), intent(out), allocatable :: pa integer, dimension(:), intent(in) :: me_index, result_index integer, dimension(:), allocatable :: n_entries integer :: n_matrix_elements, r, i, k !!! The result indices of the appended color contracted states !!! start counting from 1 again. For the pairing array, we currently !!! only take the first part of ascending indices into account !!! excluding the color contracted states. n_matrix_elements = size (me_index) k = 0 do i = 1, n_matrix_elements r = result_index(i) if (r < i) exit k = r end do allocate (pa (k)) allocate (n_entries (k)) n_entries = 1 call pairing_array_init & (pa, n_entries, has_i2=.false., has_factor=.false.) do i = 1, k r = result_index(i) pa(r)%i1(1) = me_index(i) end do end subroutine make_pairing_array subroutine record_links (int, int_in, n_tot) class(interaction_t), intent(inout) :: int class(interaction_t), intent(in), target :: int_in integer, intent(in) :: n_tot integer, dimension(n_tot) :: map integer :: i do i = 1, n_tot call int%set_source_link (i, int_in, i) end do map = [ (i, i = 1, n_tot) ] call int_in%transfer_relations (int, map) end subroutine record_links end subroutine evaluator_init_color_contractions @ %def evaluator_init_color_contractions @ \subsubsection{Auxiliary procedure for initialization} This will become a standard procedure in F2008. The result is true if the number of true values in the mask is odd. We use the function for determining the ghost parity of a quantum-number array. [tho:] It's not used anymore and [[mod (count (mask), 2) == 1]] is a cooler implementation anyway. <<(UNUSED) Evaluators: procedures>>= function parity (mask) logical :: parity logical, dimension(:) :: mask integer :: i parity = .false. do i = 1, size (mask) if (mask(i)) parity = .not. parity end do end function parity @ %def parity @ Reassign external source links from one to another. <>= public :: evaluator_reassign_links <>= interface evaluator_reassign_links module procedure evaluator_reassign_links_eval module procedure evaluator_reassign_links_int end interface <>= subroutine evaluator_reassign_links_eval (eval, eval_src, eval_target) type(evaluator_t), intent(inout) :: eval type(evaluator_t), intent(in) :: eval_src type(evaluator_t), intent(in), target :: eval_target if (associated (eval%int_in1)) then if (eval%int_in1%get_tag () == eval_src%get_tag ()) then eval%int_in1 => eval_target%interaction_t end if end if if (associated (eval%int_in2)) then if (eval%int_in2%get_tag () == eval_src%get_tag ()) then eval%int_in2 => eval_target%interaction_t end if end if call interaction_reassign_links & (eval%interaction_t, eval_src%interaction_t, & eval_target%interaction_t) end subroutine evaluator_reassign_links_eval subroutine evaluator_reassign_links_int (eval, int_src, int_target) type(evaluator_t), intent(inout) :: eval type(interaction_t), intent(in) :: int_src type(interaction_t), intent(in), target :: int_target if (associated (eval%int_in1)) then if (eval%int_in1%get_tag () == int_src%get_tag ()) then eval%int_in1 => int_target end if end if if (associated (eval%int_in2)) then if (eval%int_in2%get_tag () == int_src%get_tag ()) then eval%int_in2 => int_target end if end if call interaction_reassign_links (eval%interaction_t, int_src, int_target) end subroutine evaluator_reassign_links_int @ %def evaluator_reassign_links @ Return flavor, momentum, and position of the first unstable particle present in the interaction. <>= public :: evaluator_get_unstable_particle <>= subroutine evaluator_get_unstable_particle (eval, flv, p, i) type(evaluator_t), intent(in) :: eval type(flavor_t), intent(out) :: flv type(vector4_t), intent(out) :: p integer, intent(out) :: i call interaction_get_unstable_particle (eval%interaction_t, flv, p, i) end subroutine evaluator_get_unstable_particle @ %def evaluator_get_unstable_particle @ <>= public :: evaluator_get_int_in_ptr <>= function evaluator_get_int_in_ptr (eval, i) result (int_in) class(interaction_t), pointer :: int_in type(evaluator_t), intent(in), target :: eval integer, intent(in) :: i if (i == 1) then int_in => eval%int_in1 else if (i == 2) then int_in => eval%int_in2 else int_in => null () end if end function evaluator_get_int_in_ptr @ %def evaluator_get_int_in_ptr @ \subsection{Creating an evaluator: identity} The identity evaluator creates a copy of the first input evaluator; the second input is not used. All particles link back to the input evaluatorand the internal relations are copied. As evaluation does take a shortcut by cloning the matrix elements, the pairing array is not used and does not have to be set up. <>= procedure :: init_identity => evaluator_init_identity <>= subroutine evaluator_init_identity (eval, int) class(evaluator_t), intent(out), target :: eval class(interaction_t), intent(in), target :: int integer :: n_in, n_out, n_vir, n_tot integer :: i integer, dimension(:), allocatable :: map type(state_matrix_t), pointer :: state type(state_iterator_t) :: it eval%type = EVAL_IDENTITY eval%int_in1 => int nullify (eval%int_in2) n_in = int%get_n_in () n_out = int%get_n_out () n_vir = int%get_n_vir () n_tot = int%get_n_tot () call eval%interaction_t%basic_init (n_in, n_vir, n_out, & mask = int%get_mask (), & resonant = int%get_resonance_flags ()) do i = 1, n_tot call eval%set_source_link (i, int, i) end do allocate (map(n_tot)) map = [(i, i = 1, n_tot)] call int%transfer_relations (eval, map) state => int%get_state_matrix_ptr () call it%init (state) do while (it%is_valid ()) call eval%add_state (it%get_quantum_numbers (), & it%get_me_index ()) call it%advance () end do call eval%freeze () end subroutine evaluator_init_identity @ %def evaluator_init_identity @ \subsection {Creating an evaluator: quantum number sum} This evaluator operates on the diagonal of a density matrix and sums over the quantum numbers specified by the mask. The optional argument [[drop]] allows to drop a particle from the resulting density matrix. The handling of virtuals is not completely sane, especially in connection with dropping particles. When summing over matrix element entries, we keep the separation into entries and normalization (in the corresponding evaluation routine below). <>= procedure :: init_qn_sum => evaluator_init_qn_sum <>= subroutine evaluator_init_qn_sum (eval, int, qn_mask, drop) class(evaluator_t), intent(out), target :: eval class(interaction_t), target, intent(in) :: int type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask logical, intent(in), optional, dimension(:) :: drop type(state_iterator_t) :: it_old, it_new integer, dimension(:), allocatable :: pairing_size, pairing_target, i_new integer, dimension(:), allocatable :: map integer :: n_in, n_out, n_vir, n_tot, n_me_old, n_me_new integer :: i, j type(state_matrix_t), pointer :: state_new, state_old type(quantum_numbers_t), dimension(:), allocatable :: qn logical :: matched logical, dimension(size (qn_mask)) :: dropped integer :: ndropped integer, dimension(:), allocatable :: inotdropped type(quantum_numbers_mask_t), dimension(:), allocatable :: mask logical, dimension(:), allocatable :: resonant eval%type = EVAL_QN_SUM eval%int_in1 => int nullify (eval%int_in2) if (present (drop)) then dropped = drop else dropped = .false. end if ndropped = count (dropped) n_in = int%get_n_in () n_out = int%get_n_out () - ndropped n_vir = int%get_n_vir () n_tot = int%get_n_tot () - ndropped allocate (inotdropped (n_tot)) i = 1 do j = 1, n_tot + ndropped if (dropped (j)) cycle inotdropped(i) = j i = i + 1 end do allocate (mask(n_tot + ndropped)) mask = int%get_mask () allocate (resonant(n_tot + ndropped)) resonant = int%get_resonance_flags () call eval%interaction_t%basic_init (n_in, n_vir, n_out, & mask = mask(inotdropped) .or. qn_mask(inotdropped), & resonant = resonant(inotdropped)) i = 1 do j = 1, n_tot + ndropped if (dropped(j)) cycle call eval%set_source_link (i, int, j) i = i + 1 end do allocate (map(n_tot + ndropped)) i = 1 do j = 1, n_tot + ndropped if (dropped (j)) then map(j) = 0 else map(j) = i i = i + 1 end if end do call int%transfer_relations (eval, map) n_me_old = int%get_n_matrix_elements () allocate (pairing_size (n_me_old), source = 0) allocate (pairing_target (n_me_old), source = 0) pairing_size = 0 state_old => int%get_state_matrix_ptr () state_new => eval%get_state_matrix_ptr () call it_old%init (state_old) allocate (qn(n_tot + ndropped)) do while (it_old%is_valid ()) qn = it_old%get_quantum_numbers () if (.not. all (qn%are_diagonal ())) then call it_old%advance () cycle end if matched = .false. call it_new%init (state_new) if (eval%get_n_matrix_elements () > 0) then do while (it_new%is_valid ()) if (all (qn(inotdropped) .match. & it_new%get_quantum_numbers ())) & then matched = .true. i = it_new%get_me_index () exit end if call it_new%advance () end do end if if (.not. matched) then call eval%add_state (qn(inotdropped)) i = eval%get_n_matrix_elements () end if pairing_size(i) = pairing_size(i) + 1 pairing_target(it_old%get_me_index ()) = i call it_old%advance () end do call eval%freeze () n_me_new = eval%get_n_matrix_elements () allocate (eval%pairing_array (n_me_new)) do i = 1, n_me_new call pairing_array_init (eval%pairing_array(i), & pairing_size(i), .false., .false.) end do allocate (i_new (n_me_new), source = 0) do i = 1, n_me_old j = pairing_target(i) if (j > 0) then i_new(j) = i_new(j) + 1 eval%pairing_array(j)%i1(i_new(j)) = i end if end do end subroutine evaluator_init_qn_sum @ %def evaluator_init_qn_sum @ \subsection{Evaluation} When the input interactions (which are pointed to in the pairings stored within the evaluator) are filled with values, we can activate the evaluator, i.e., calculate the result values which are stored in the interaction. The evaluation of matrix elements can be done in parallel. A [[forall]] construct is not appropriate, however. We would need [[do concurrent]] here. Nevertheless, the evaluation functions are marked as [[pure]]. <>= procedure :: evaluate => evaluator_evaluate <>= subroutine evaluator_evaluate (eval) class(evaluator_t), intent(inout), target :: eval integer :: i select case (eval%type) case (EVAL_PRODUCT) do i = 1, size(eval%pairing_array) call eval%evaluate_product (i, & eval%int_in1, eval%int_in2, & eval%pairing_array(i)%i1, eval%pairing_array(i)%i2) if (debug2_active (D_QFT)) then print *, 'eval%pairing_array(i)%i1, eval%pairing_array(i)%i2 = ', & eval%pairing_array(i)%i1, eval%pairing_array(i)%i2 print *, 'MEs = ', & eval%int_in1%get_matrix_element (eval%pairing_array(i)%i1), & eval%int_in2%get_matrix_element (eval%pairing_array(i)%i2) end if end do case (EVAL_SQUARE_WITH_COLOR_FACTORS) do i = 1, size(eval%pairing_array) call eval%evaluate_product_cf (i, & eval%int_in1, eval%int_in1, & eval%pairing_array(i)%i1, eval%pairing_array(i)%i2, & eval%pairing_array(i)%factor) end do case (EVAL_SQUARED_FLOWS) do i = 1, size(eval%pairing_array) call eval%evaluate_square_c (i, & eval%int_in1, & eval%pairing_array(i)%i1) end do case (EVAL_COLOR_CONTRACTION) do i = 1, size(eval%pairing_array) call eval%evaluate_sum (i, & eval%int_in1, & eval%pairing_array(i)%i1) end do case (EVAL_IDENTITY) call eval%set_matrix_element (eval%int_in1) case (EVAL_QN_SUM) do i = 1, size (eval%pairing_array) call eval%evaluate_me_sum (i, & eval%int_in1, eval%pairing_array(i)%i1) call eval%set_norm (eval%int_in1%get_norm ()) end do end select end subroutine evaluator_evaluate @ %def evaluator_evaluate @ \subsection{Unit tests} Test module, followed by the corresponding implementation module. <<[[evaluators_ut.f90]]>>= <> module evaluators_ut use unit_tests use evaluators_uti <> <> contains <> end module evaluators_ut @ %def evaluators_ut @ <<[[evaluators_uti.f90]]>>= <> module evaluators_uti <> use lorentz use flavors use colors use helicities use quantum_numbers use interactions use model_data use evaluators <> <> contains <> end module evaluators_uti @ %def evaluators_ut @ API: driver for the unit tests below. <>= public :: evaluator_test <>= subroutine evaluator_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine evaluator_test @ %def evaluator_test @ Test: Create two interactions. The interactions are twofold connected. The first connection has a helicity index that is kept, the second connection has a helicity index that is summed over. Concatenate the interactions in an evaluator, which thus contains a result interaction. Fill the input interactions with values, activate the evaluator and print the result. <>= call test (evaluator_1, "evaluator_1", & "check evaluators (1)", & u, results) <>= public :: evaluator_1 <>= subroutine evaluator_1 (u) integer, intent(in) :: u type(model_data_t), target :: model type(interaction_t), target :: int_qqtt, int_tbw, int1, int2 type(flavor_t), dimension(:), allocatable :: flv type(color_t), dimension(:), allocatable :: col type(helicity_t), dimension(:), allocatable :: hel type(quantum_numbers_t), dimension(:), allocatable :: qn integer :: f, c, h1, h2, h3 type(vector4_t), dimension(4) :: p type(vector4_t), dimension(2) :: q type(quantum_numbers_mask_t) :: qn_mask_conn type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask2 type(evaluator_t), target :: eval, eval2, eval3 call model%init_sm_test () write (u, "(A)") "*** Evaluator for matrix product" write (u, "(A)") "*** Construct interaction for qq -> tt" write (u, "(A)") call int_qqtt%basic_init (2, 0, 2, set_relations=.true.) allocate (flv (4), col (4), hel (4), qn (4)) allocate (qn_mask2 (4)) do c = 1, 2 select case (c) case (1) call col%init_col_acl ([1, 0, 1, 0], [0, 2, 0, 2]) case (2) call col%init_col_acl ([1, 0, 2, 0], [0, 1, 0, 2]) end select do f = 1, 2 call flv%init ([f, -f, 6, -6], model) do h1 = -1, 1, 2 call hel(3)%init (h1) do h2 = -1, 1, 2 call hel(4)%init (h2) call qn%init (flv, col, hel) call int_qqtt%add_state (qn) end do end do end do end do call int_qqtt%freeze () deallocate (flv, col, hel, qn) write (u, "(A)") "*** Construct interaction for t -> bW" call int_tbw%basic_init (1, 0, 2, set_relations=.true.) allocate (flv (3), col (3), hel (3), qn (3)) call flv%init ([6, 5, 24], model) call col%init_col_acl ([1, 1, 0], [0, 0, 0]) do h1 = -1, 1, 2 call hel(1)%init (h1) do h2 = -1, 1, 2 call hel(2)%init (h2) do h3 = -1, 1 call hel(3)%init (h3) call qn%init (flv, col, hel) call int_tbw%add_state (qn) end do end do end do call int_tbw%freeze () deallocate (flv, col, hel, qn) write (u, "(A)") "*** Link interactions" call int_tbw%set_source_link (1, int_qqtt, 3) qn_mask_conn = quantum_numbers_mask (.false.,.false.,.true.) write (u, "(A)") write (u, "(A)") "*** Show input" call int_qqtt%basic_write (unit = u) write (u, "(A)") call int_tbw%basic_write (unit = u) write (u, "(A)") write (u, "(A)") "*** Evaluate product" call eval%init_product (int_qqtt, int_tbw, qn_mask_conn) call eval%write (unit = u) call int1%basic_init (2, 0, 2, set_relations=.true.) call int2%basic_init (1, 0, 2, set_relations=.true.) p(1) = vector4_moving (1000._default, 1000._default, 3) p(2) = vector4_moving (200._default, 200._default, 2) p(3) = vector4_moving (100._default, 200._default, 1) p(4) = p(1) - p(2) - p(3) call int1%set_momenta (p) q(1) = vector4_moving (50._default,-50._default, 3) q(2) = p(2) + p(4) - q(1) call int2%set_momenta (q, outgoing=.true.) call int1%set_matrix_element ([(2._default,0._default), & (4._default,1._default), (-3._default,0._default)]) call int2%set_matrix_element ([(-3._default,0._default), & (0._default,1._default), (1._default,2._default)]) call eval%receive_momenta () call eval%evaluate () call int1%basic_write (unit = u) write (u, "(A)") call int2%basic_write (unit = u) write (u, "(A)") call eval%write (unit = u) write (u, "(A)") call int1%final () call int2%final () call eval%final () write (u, "(A)") write (u, "(A)") "*** Evaluator for matrix square" allocate (flv(4), col(4), qn(4)) call int1%basic_init (2, 0, 2, set_relations=.true.) call flv%init ([1, -1, 21, 21], model) call col(1)%init ([1]) call col(2)%init ([-2]) call col(3)%init ([2, -3]) call col(4)%init ([3, -1]) call qn%init (flv, col) call int1%add_state (qn) call col(3)%init ([3, -1]) call col(4)%init ([2, -3]) call qn%init (flv, col) call int1%add_state (qn) call col(3)%init ([2, -1]) call col(4)%init (.true.) call qn%init (flv, col) call int1%add_state (qn) call int1%freeze () ! [qn_mask2 not set since default is false] call eval%init_square (int1, qn_mask2, nc=3) call eval2%init_square_nondiag (int1, qn_mask2) qn_mask2 = quantum_numbers_mask (.false., .true., .true.) call eval3%init_square_diag (eval, qn_mask2) call int1%set_matrix_element & ([(2._default,0._default), & (4._default,1._default), (-3._default,0._default)]) call int1%set_momenta (p) call int1%basic_write (unit = u) write (u, "(A)") call eval%receive_momenta () call eval%evaluate () call eval%write (unit = u) write (u, "(A)") call eval2%receive_momenta () call eval2%evaluate () call eval2%write (unit = u) write (u, "(A)") call eval3%receive_momenta () call eval3%evaluate () call eval3%write (unit = u) call int1%final () call eval%final () call eval2%final () call eval3%final () call model%final () end subroutine evaluator_1 @ %def evaluator_1 @ <>= call test (evaluator_2, "evaluator_2", & "check evaluators (2)", & u, results) <>= public :: evaluator_2 <>= subroutine evaluator_2 (u) integer, intent(in) :: u type(model_data_t), target :: model type(interaction_t), target :: int integer :: h1, h2, h3, h4 type(helicity_t), dimension(4) :: hel type(color_t), dimension(4) :: col type(flavor_t), dimension(4) :: flv type(quantum_numbers_t), dimension(4) :: qn type(vector4_t), dimension(4) :: p type(evaluator_t) :: eval integer :: i call model%init_sm_test () write (u, "(A)") "*** Creating interaction for e+ e- -> W+ W-" write (u, "(A)") call flv%init ([11, -11, 24, -24], model) do i = 1, 4 call col(i)%init () end do call int%basic_init (2, 0, 2, set_relations=.true.) do h1 = -1, 1, 2 call hel(1)%init (h1) do h2 = -1, 1, 2 call hel(2)%init (h2) do h3 = -1, 1 call hel(3)%init (h3) do h4 = -1, 1 call hel(4)%init (h4) call qn%init (flv, col, hel) call int%add_state (qn) end do end do end do end do call int%freeze () call int%set_matrix_element & ([(cmplx (i, kind=default), i = 1, 36)]) p(1) = vector4_moving (1000._default, 1000._default, 3) p(2) = vector4_moving (1000._default, -1000._default, 3) p(3) = vector4_moving (1000._default, & sqrt (1E6_default - 80._default**2), 3) p(4) = p(1) + p(2) - p(3) call int%set_momenta (p) write (u, "(A)") "*** Setting up evaluator" write (u, "(A)") call eval%init_identity (int) write (u, "(A)") "*** Transferring momenta and evaluating" write (u, "(A)") call eval%receive_momenta () call eval%evaluate () write (u, "(A)") "*******************************************************" write (u, "(A)") " Interaction dump" write (u, "(A)") "*******************************************************" call int%basic_write (unit = u) write (u, "(A)") write (u, "(A)") "*******************************************************" write (u, "(A)") " Evaluator dump" write (u, "(A)") "*******************************************************" call eval%write (unit = u) write (u, "(A)") write (u, "(A)") "*** cleaning up" call int%final () call eval%final () call model%final () end subroutine evaluator_2 @ %def evaluator_2 @ <>= call test (evaluator_3, "evaluator_3", & "check evaluators (3)", & u, results) <>= public :: evaluator_3 <>= subroutine evaluator_3 (u) integer, intent(in) :: u type(model_data_t), target :: model type(interaction_t), target :: int integer :: h1, h2, h3, h4 type(helicity_t), dimension(4) :: hel type(color_t), dimension(4) :: col type(flavor_t), dimension(4) :: flv1, flv2 type(quantum_numbers_t), dimension(4) :: qn type(vector4_t), dimension(4) :: p type(evaluator_t) :: eval1, eval2, eval3 type(quantum_numbers_mask_t), dimension(4) :: qn_mask integer :: i call model%init_sm_test () write (u, "(A)") "*** Creating interaction for e+/mu+ e-/mu- -> W+ W-" call flv1%init ([11, -11, 24, -24], model) call flv2%init ([13, -13, 24, -24], model) do i = 1, 4 call col (i)%init () end do call int%basic_init (2, 0, 2, set_relations=.true.) do h1 = -1, 1, 2 call hel(1)%init (h1) do h2 = -1, 1, 2 call hel(2)%init (h2) do h3 = -1, 1 call hel(3)%init (h3) do h4 = -1, 1 call hel(4)%init (h4) call qn%init (flv1, col, hel) call int%add_state (qn) call qn%init (flv2, col, hel) call int%add_state (qn) end do end do end do end do call int%freeze () call int%set_matrix_element & ([(cmplx (1, kind=default), i = 1, 72)]) p(1) = vector4_moving (1000._default, 1000._default, 3) p(2) = vector4_moving (1000._default, -1000._default, 3) p(3) = vector4_moving (1000._default, & sqrt (1E6_default - 80._default**2), 3) p(4) = p(1) + p(2) - p(3) call int%set_momenta (p) write (u, "(A)") "*** Setting up evaluators" call qn_mask%init (.false., .true., .true.) call eval1%init_qn_sum (int, qn_mask) call qn_mask%init (.true., .true., .true.) call eval2%init_qn_sum (int, qn_mask) call qn_mask%init (.false., .true., .false.) call eval3%init_qn_sum (int, qn_mask, & [.false., .false., .false., .true.]) write (u, "(A)") "*** Transferring momenta and evaluating" call eval1%receive_momenta () call eval1%evaluate () call eval2%receive_momenta () call eval2%evaluate () call eval3%receive_momenta () call eval3%evaluate () write (u, "(A)") "*******************************************************" write (u, "(A)") " Interaction dump" write (u, "(A)") "*******************************************************" call int%basic_write (unit = u) write (u, "(A)") write (u, "(A)") "*******************************************************" write (u, "(A)") " Evaluator dump --- spin sum" write (u, "(A)") "*******************************************************" call eval1%write (unit = u) call eval1%basic_write (unit = u) write (u, "(A)") "*******************************************************" write (u, "(A)") " Evaluator dump --- spin / flavor sum" write (u, "(A)") "*******************************************************" call eval2%write (unit = u) call eval2%basic_write (unit = u) write (u, "(A)") "*******************************************************" write (u, "(A)") " Evaluator dump --- flavor sum, drop last W" write (u, "(A)") "*******************************************************" call eval3%write (unit = u) call eval3%basic_write (unit = u) write (u, "(A)") write (u, "(A)") "*** cleaning up" call int%final () call eval1%final () call eval2%final () call eval3%final () call model%final () end subroutine evaluator_3 @ %def evaluator_3 @ This test evaluates a product with different quantum-number masks and filters for the linked entry. <>= call test (evaluator_4, "evaluator_4", & "check evaluator product with filter", & u, results) <>= public :: evaluator_4 <>= subroutine evaluator_4 (u) integer, intent(in) :: u type(model_data_t), target :: model type(interaction_t), target :: int1, int2 integer :: h1, h2, h3 type(helicity_t), dimension(3) :: hel type(color_t), dimension(3) :: col type(flavor_t), dimension(2) :: flv1, flv2 type(flavor_t), dimension(3) :: flv3, flv4 type(quantum_numbers_t), dimension(3) :: qn type(evaluator_t) :: eval1, eval2, eval3, eval4 type(quantum_numbers_mask_t) :: qn_mask type(flavor_t) :: flv_filter type(helicity_t) :: hel_filter type(color_t) :: col_filter type(quantum_numbers_t) :: qn_filter integer :: i write (u, "(A)") "* Test output: evaluator_4" write (u, "(A)") "* Purpose: test evaluator products & &with mask and filter" write (u, "(A)") call model%init_sm_test () write (u, "(A)") "* Creating interaction for e- -> W+/Z" write (u, "(A)") call flv1%init ([11, 24], model) call flv2%init ([11, 23], model) do i = 1, 3 call col(i)%init () end do call int1%basic_init (1, 0, 1, set_relations=.true.) do h1 = -1, 1, 2 call hel(1)%init (h1) do h2 = -1, 1 call hel(2)%init (h2) call qn(:2)%init (flv1, col(:2), hel(:2)) call int1%add_state (qn(:2)) call qn(:2)%init (flv2, col(:2), hel(:2)) call int1%add_state (qn(:2)) end do end do call int1%freeze () call int1%basic_write (u) write (u, "(A)") write (u, "(A)") "* Creating interaction for W+/Z -> u ubar/dbar" write (u, "(A)") call flv3%init ([24, 2, -1], model) call flv4%init ([23, 2, -2], model) call int2%basic_init (1, 0, 2, set_relations=.true.) do h1 = -1, 1 call hel(1)%init (h1) do h2 = -1, 1, 2 call hel(2)%init (h2) do h3 = -1, 1, 2 call hel(3)%init (h3) call qn(:3)%init (flv3, col(:3), hel(:3)) call int2%add_state (qn(:3)) call qn(:3)%init (flv4, col(:3), hel(:3)) call int2%add_state (qn(:3)) end do end do end do call int2%freeze () call int2%set_source_link (1, int1, 2) call int2%basic_write (u) write (u, "(A)") write (u, "(A)") "* Product evaluator" write (u, "(A)") call qn_mask%init (.false., .false., .false.) call eval1%init_product (int1, int2, qn_mask_conn = qn_mask) call eval1%write (u) write (u, "(A)") write (u, "(A)") "* Product evaluator with helicity mask" write (u, "(A)") call qn_mask%init (.false., .false., .true.) call eval2%init_product (int1, int2, qn_mask_conn = qn_mask) call eval2%write (u) write (u, "(A)") write (u, "(A)") "* Product with flavor filter and helicity mask" write (u, "(A)") call qn_mask%init (.false., .false., .true.) call flv_filter%init (24, model) call hel_filter%init () call col_filter%init () call qn_filter%init (flv_filter, col_filter, hel_filter) call eval3%init_product (int1, int2, & qn_mask_conn = qn_mask, qn_filter_conn = qn_filter) call eval3%write (u) write (u, "(A)") write (u, "(A)") "* Product with helicity filter and mask" write (u, "(A)") call qn_mask%init (.false., .false., .true.) call flv_filter%init () call hel_filter%init (0) call col_filter%init () call qn_filter%init (flv_filter, col_filter, hel_filter) call eval4%init_product (int1, int2, & qn_mask_conn = qn_mask, qn_filter_conn = qn_filter) call eval4%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call eval1%final () call eval2%final () call eval3%final () call eval4%final () call int1%final () call int2%final () call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: evaluator_4" end subroutine evaluator_4 @ %def evaluator_4 Index: trunk/src/whizard-core/whizard.nw =================================================================== --- trunk/src/whizard-core/whizard.nw (revision 8753) +++ trunk/src/whizard-core/whizard.nw (revision 8754) @@ -1,29231 +1,29224 @@ % -*- ess-noweb-default-code-mode: f90-mode; noweb-default-code-mode: f90-mode; -*- % WHIZARD main code as NOWEB source \includemodulegraph{whizard-core} \chapter{Integration and Simulation} @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{User-controlled File I/O} The SINDARIN language includes commands that write output to file (input may be added later). We identify files by their name, and manage the unit internally. We need procedures for opening, closing, and printing files. <<[[user_files.f90]]>>= <> module user_files <> use io_units use diagnostics use ifiles use analysis <> <> <> <> contains <> end module user_files @ %def user_files @ \subsection{The file type} This is a type that describes an open user file and its properties. The entry is part of a doubly-linked list. <>= type :: file_t private type(string_t) :: name integer :: unit = -1 logical :: reading = .false. logical :: writing = .false. type(file_t), pointer :: prev => null () type(file_t), pointer :: next => null () end type file_t @ %def file_t @ The initializer opens the file. <>= subroutine file_init (file, name, action, status, position) type(file_t), intent(out) :: file type(string_t), intent(in) :: name character(len=*), intent(in) :: action, status, position file%unit = free_unit () file%name = name open (unit = file%unit, file = char (file%name), & action = action, status = status, position = position) select case (action) case ("read") file%reading = .true. case ("write") file%writing = .true. case ("readwrite") file%reading = .true. file%writing = .true. end select end subroutine file_init @ %def file_init @ The finalizer closes it. <>= subroutine file_final (file) type(file_t), intent(inout) :: file close (unit = file%unit) file%unit = -1 end subroutine file_final @ %def file_final @ Check if a file is open with correct status. <>= function file_is_open (file, action) result (flag) logical :: flag type(file_t), intent(in) :: file character(*), intent(in) :: action select case (action) case ("read") flag = file%reading case ("write") flag = file%writing case ("readwrite") flag = file%reading .and. file%writing case default call msg_bug ("Checking file '" // char (file%name) & // "': illegal action specifier") end select end function file_is_open @ %def file_is_open @ Return the unit number of a file for direct access. It should be checked first whether the file is open. <>= function file_get_unit (file) result (unit) integer :: unit type(file_t), intent(in) :: file unit = file%unit end function file_get_unit @ %def file_get_unit @ Write to the file. Error if in wrong mode. If there is no string, just write an empty record. If there is a string, respect the [[advancing]] option. <>= subroutine file_write_string (file, string, advancing) type(file_t), intent(in) :: file type(string_t), intent(in), optional :: string logical, intent(in), optional :: advancing if (file%writing) then if (present (string)) then if (present (advancing)) then if (advancing) then write (file%unit, "(A)") char (string) else write (file%unit, "(A)", advance="no") char (string) end if else write (file%unit, "(A)") char (string) end if else write (file%unit, *) end if else call msg_error ("Writing to file: File '" // char (file%name) & // "' is not open for writing.") end if end subroutine file_write_string @ %def file_write @ Write a whole ifile, line by line. <>= subroutine file_write_ifile (file, ifile) type(file_t), intent(in) :: file type(ifile_t), intent(in) :: ifile type(line_p) :: line call line_init (line, ifile) do while (line_is_associated (line)) call file_write_string (file, line_get_string_advance (line)) end do end subroutine file_write_ifile @ %def file_write_ifile @ Write an analysis object (or all objects) to an open file. <>= subroutine file_write_analysis (file, tag) type(file_t), intent(in) :: file type(string_t), intent(in), optional :: tag if (file%writing) then if (present (tag)) then call analysis_write (tag, unit = file%unit) else call analysis_write (unit = file%unit) end if else call msg_error ("Writing analysis to file: File '" // char (file%name) & // "' is not open for writing.") end if end subroutine file_write_analysis @ %def file_write_analysis @ \subsection{The file list} We maintain a list of all open files and their attributes. The list must be doubly-linked because we may delete entries. <>= public :: file_list_t <>= type :: file_list_t type(file_t), pointer :: first => null () type(file_t), pointer :: last => null () end type file_list_t @ %def file_list_t @ There is no initialization routine, but a finalizer which deletes all: <>= public :: file_list_final <>= subroutine file_list_final (file_list) type(file_list_t), intent(inout) :: file_list type(file_t), pointer :: current do while (associated (file_list%first)) current => file_list%first file_list%first => current%next call file_final (current) deallocate (current) end do file_list%last => null () end subroutine file_list_final @ %def file_list_final @ Find an entry in the list. Return null pointer on failure. <>= function file_list_get_file_ptr (file_list, name) result (current) type(file_t), pointer :: current type(file_list_t), intent(in) :: file_list type(string_t), intent(in) :: name current => file_list%first do while (associated (current)) if (current%name == name) return current => current%next end do end function file_list_get_file_ptr @ %def file_list_get_file_ptr @ Check if a file is open, public version: <>= public :: file_list_is_open <>= function file_list_is_open (file_list, name, action) result (flag) logical :: flag type(file_list_t), intent(in) :: file_list type(string_t), intent(in) :: name character(len=*), intent(in) :: action type(file_t), pointer :: current current => file_list_get_file_ptr (file_list, name) if (associated (current)) then flag = file_is_open (current, action) else flag = .false. end if end function file_list_is_open @ %def file_list_is_open @ Return the unit number for a file. It should be checked first whether the file is open. <>= public :: file_list_get_unit <>= function file_list_get_unit (file_list, name) result (unit) integer :: unit type(file_list_t), intent(in) :: file_list type(string_t), intent(in) :: name type(file_t), pointer :: current current => file_list_get_file_ptr (file_list, name) if (associated (current)) then unit = file_get_unit (current) else unit = -1 end if end function file_list_get_unit @ %def file_list_get_unit @ Append a new file entry, i.e., open this file. Error if it is already open. <>= public :: file_list_open <>= subroutine file_list_open (file_list, name, action, status, position) type(file_list_t), intent(inout) :: file_list type(string_t), intent(in) :: name character(len=*), intent(in) :: action, status, position type(file_t), pointer :: current if (.not. associated (file_list_get_file_ptr (file_list, name))) then allocate (current) call msg_message ("Opening file '" // char (name) // "' for output") call file_init (current, name, action, status, position) if (associated (file_list%last)) then file_list%last%next => current current%prev => file_list%last else file_list%first => current end if file_list%last => current else call msg_error ("Opening file: File '" // char (name) & // "' is already open.") end if end subroutine file_list_open @ %def file_list_open @ Delete a file entry, i.e., close this file. Error if it is not open. <>= public :: file_list_close <>= subroutine file_list_close (file_list, name) type(file_list_t), intent(inout) :: file_list type(string_t), intent(in) :: name type(file_t), pointer :: current current => file_list_get_file_ptr (file_list, name) if (associated (current)) then if (associated (current%prev)) then current%prev%next => current%next else file_list%first => current%next end if if (associated (current%next)) then current%next%prev => current%prev else file_list%last => current%prev end if call msg_message ("Closing file '" // char (name) // "' for output") call file_final (current) deallocate (current) else call msg_error ("Closing file: File '" // char (name) & // "' is not open.") end if end subroutine file_list_close @ %def file_list_close @ Write a string to file. Error if it is not open. <>= public :: file_list_write <>= interface file_list_write module procedure file_list_write_string module procedure file_list_write_ifile end interface <>= subroutine file_list_write_string (file_list, name, string, advancing) type(file_list_t), intent(in) :: file_list type(string_t), intent(in) :: name type(string_t), intent(in), optional :: string logical, intent(in), optional :: advancing type(file_t), pointer :: current current => file_list_get_file_ptr (file_list, name) if (associated (current)) then call file_write_string (current, string, advancing) else call msg_error ("Writing to file: File '" // char (name) & // "'is not open.") end if end subroutine file_list_write_string subroutine file_list_write_ifile (file_list, name, ifile) type(file_list_t), intent(in) :: file_list type(string_t), intent(in) :: name type(ifile_t), intent(in) :: ifile type(file_t), pointer :: current current => file_list_get_file_ptr (file_list, name) if (associated (current)) then call file_write_ifile (current, ifile) else call msg_error ("Writing to file: File '" // char (name) & // "'is not open.") end if end subroutine file_list_write_ifile @ %def file_list_write @ Write an analysis object or all objects to data file. Error if it is not open. If the file name is empty, write to standard output. <>= public :: file_list_write_analysis <>= subroutine file_list_write_analysis (file_list, name, tag) type(file_list_t), intent(in) :: file_list type(string_t), intent(in) :: name type(string_t), intent(in), optional :: tag type(file_t), pointer :: current if (name == "") then if (present (tag)) then call analysis_write (tag) else call analysis_write end if else current => file_list_get_file_ptr (file_list, name) if (associated (current)) then call file_write_analysis (current, tag) else call msg_error ("Writing analysis to file: File '" // char (name) & // "' is not open.") end if end if end subroutine file_list_write_analysis @ %def file_list_write_analysis @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Runtime data} <<[[rt_data.f90]]>>= <> module rt_data <> <> use io_units use format_utils, only: write_separator use format_defs, only: FMT_19, FMT_12 use system_dependencies use diagnostics use os_interface use lexers use parser use models use subevents use pdg_arrays use variables, only: var_list_t use process_libraries use prclib_stacks use prc_core, only: helicity_selection_t use beam_structures use event_base, only: event_callback_t use user_files use process_stacks use iterations <> <> <> contains <> end module rt_data @ %def rt_data @ \subsection{Strategy for models and variables} The program manages its data via a main [[rt_data_t]] object. During program flow, various commands create and use local [[rt_data_t]] objects. Those transient blocks contain either pointers to global object or local copies which are deleted after use. Each [[rt_data_t]] object contains a variable list component. This lists holds (local copies of) all kinds of intrinsic or user-defined variables. The variable list is linked to the variable list contained in the local process library. This, in turn, is linked to the variable list of the [[rt_data_t]] context, and so on. A variable lookup will thus be recursively delegated to the linked variable lists, until a match is found. When modifying a variable which is not yet local, the program creates a local copy and uses this afterwards. Thus, when the local [[rt_data_t]] object is deleted, the context value is recovered. Models are kept in a model list which is separate from the variable list. Otherwise, they are treated in a similar manner: the local list is linked to the context model list. Model lookup is thus recursively delegated. When a model or any part of it is modified, the model is copied to the local [[rt_data_t]] object, so the context model is not modified. Commands such as [[integrate]] will create their own copy of the current model (and of the current variable list) at the point where they are executed. When a model is encountered for the first time, it is read from file. The reading is automatically delegated to the global context. Thus, this master copy survives until the main [[rt_data_t]] object is deleted, at program completion. If there is a currently active model, its variable list is linked to the main variable list. Variable lookups will then start from the model variable list. When the current model is switched, the new active model will get this link instead. Consequently, a change to the current model is kept as long as this model has a local copy; it survives local model switches. On the other hand, a parameter change in the current model doesn't affect any other model, even if the parameter name is identical. @ \subsection{Container for parse nodes} The runtime data set contains a bunch of parse nodes (chunks of code that have not been compiled into evaluation trees but saved for later use). We collect them here. This implementation has the useful effect that an assignment between two objects of this type will establish a pointer-target relationship for all components. <>= type :: rt_parse_nodes_t type(parse_node_t), pointer :: cuts_lexpr => null () type(parse_node_t), pointer :: scale_expr => null () type(parse_node_t), pointer :: fac_scale_expr => null () type(parse_node_t), pointer :: ren_scale_expr => null () type(parse_node_t), pointer :: weight_expr => null () type(parse_node_t), pointer :: selection_lexpr => null () type(parse_node_t), pointer :: reweight_expr => null () type(parse_node_t), pointer :: analysis_lexpr => null () type(parse_node_p), dimension(:), allocatable :: alt_setup contains <> end type rt_parse_nodes_t @ %def rt_parse_nodes_t @ Clear individual components. The parse nodes are nullified. No finalization needed since the pointer targets are part of the global parse tree. <>= procedure :: clear => rt_parse_nodes_clear <>= subroutine rt_parse_nodes_clear (rt_pn, name) class(rt_parse_nodes_t), intent(inout) :: rt_pn type(string_t), intent(in) :: name select case (char (name)) case ("cuts") rt_pn%cuts_lexpr => null () case ("scale") rt_pn%scale_expr => null () case ("factorization_scale") rt_pn%fac_scale_expr => null () case ("renormalization_scale") rt_pn%ren_scale_expr => null () case ("weight") rt_pn%weight_expr => null () case ("selection") rt_pn%selection_lexpr => null () case ("reweight") rt_pn%reweight_expr => null () case ("analysis") rt_pn%analysis_lexpr => null () end select end subroutine rt_parse_nodes_clear @ %def rt_parse_nodes_clear @ Output for the parse nodes. <>= procedure :: write => rt_parse_nodes_write <>= subroutine rt_parse_nodes_write (object, unit) class(rt_parse_nodes_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) call wrt ("Cuts", object%cuts_lexpr) call write_separator (u) call wrt ("Scale", object%scale_expr) call write_separator (u) call wrt ("Factorization scale", object%fac_scale_expr) call write_separator (u) call wrt ("Renormalization scale", object%ren_scale_expr) call write_separator (u) call wrt ("Weight", object%weight_expr) call write_separator (u, 2) call wrt ("Event selection", object%selection_lexpr) call write_separator (u) call wrt ("Event reweighting factor", object%reweight_expr) call write_separator (u) call wrt ("Event analysis", object%analysis_lexpr) if (allocated (object%alt_setup)) then call write_separator (u, 2) write (u, "(1x,A,':')") "Alternative setups" do i = 1, size (object%alt_setup) call write_separator (u) call wrt ("Commands", object%alt_setup(i)%ptr) end do end if contains subroutine wrt (title, pn) character(*), intent(in) :: title type(parse_node_t), intent(in), pointer :: pn if (associated (pn)) then write (u, "(1x,A,':')") title call write_separator (u) call parse_node_write_rec (pn, u) else write (u, "(1x,A,':',1x,A)") title, "[undefined]" end if end subroutine wrt end subroutine rt_parse_nodes_write @ %def rt_parse_nodes_write @ Screen output for individual components. (This should eventually be more condensed, currently we print the internal representation tree.) <>= procedure :: show => rt_parse_nodes_show <>= subroutine rt_parse_nodes_show (rt_pn, name, unit) class(rt_parse_nodes_t), intent(in) :: rt_pn type(string_t), intent(in) :: name integer, intent(in), optional :: unit type(parse_node_t), pointer :: pn integer :: u u = given_output_unit (unit) select case (char (name)) case ("cuts") pn => rt_pn%cuts_lexpr case ("scale") pn => rt_pn%scale_expr case ("factorization_scale") pn => rt_pn%fac_scale_expr case ("renormalization_scale") pn => rt_pn%ren_scale_expr case ("weight") pn => rt_pn%weight_expr case ("selection") pn => rt_pn%selection_lexpr case ("reweight") pn => rt_pn%reweight_expr case ("analysis") pn => rt_pn%analysis_lexpr end select if (associated (pn)) then write (u, "(A,1x,A,1x,A)") "Expression:", char (name), "(parse tree):" call parse_node_write_rec (pn, u) else write (u, "(A,1x,A,A)") "Expression:", char (name), ": [undefined]" end if end subroutine rt_parse_nodes_show @ %def rt_parse_nodes_show @ \subsection{The data type} This is a big data container which contains everything that is used and modified during the command flow. A local copy of this can be used to temporarily override defaults. The data set is transparent. <>= public :: rt_data_t <>= type :: rt_data_t type(lexer_t), pointer :: lexer => null () type(rt_data_t), pointer :: context => null () type(string_t), dimension(:), allocatable :: export type(var_list_t) :: var_list type(iterations_list_t) :: it_list type(os_data_t) :: os_data type(model_list_t) :: model_list type(model_t), pointer :: model => null () logical :: model_is_copy = .false. type(model_t), pointer :: preload_model => null () type(model_t), pointer :: fallback_model => null () type(prclib_stack_t) :: prclib_stack type(process_library_t), pointer :: prclib => null () type(beam_structure_t) :: beam_structure type(rt_parse_nodes_t) :: pn type(process_stack_t) :: process_stack type(string_t), dimension(:), allocatable :: sample_fmt class(event_callback_t), allocatable :: event_callback type(file_list_t), pointer :: out_files => null () logical :: quit = .false. integer :: quit_code = 0 type(string_t) :: logfile logical :: nlo_fixed_order = .false. logical, dimension(0:5) :: selected_nlo_parts = .false. integer, dimension(:), allocatable :: nlo_component contains <> end type rt_data_t @ %def rt_data_t @ \subsection{Output} <>= procedure :: write => rt_data_write <>= subroutine rt_data_write (object, unit, vars, pacify) class(rt_data_t), intent(in) :: object integer, intent(in), optional :: unit type(string_t), dimension(:), intent(in), optional :: vars logical, intent(in), optional :: pacify integer :: u, i u = given_output_unit (unit) call write_separator (u, 2) write (u, "(1x,A)") "Runtime data:" if (object%get_n_export () > 0) then call write_separator (u, 2) write (u, "(1x,A)") "Exported objects and variables:" call write_separator (u) call object%write_exports (u) end if if (present (vars)) then if (size (vars) /= 0) then call write_separator (u, 2) write (u, "(1x,A)") "Selected variables:" call write_separator (u) call object%write_vars (u, vars) end if else call write_separator (u, 2) if (associated (object%model)) then call object%model%write_var_list (u, follow_link=.true.) else call object%var_list%write (u, follow_link=.true.) end if end if if (object%it_list%get_n_pass () > 0) then call write_separator (u, 2) write (u, "(1x)", advance="no") call object%it_list%write (u) end if if (associated (object%model)) then call write_separator (u, 2) call object%model%write (u) end if call object%prclib_stack%write (u) call object%beam_structure%write (u) call write_separator (u, 2) call object%pn%write (u) if (allocated (object%sample_fmt)) then call write_separator (u) write (u, "(1x,A)", advance="no") "Event sample formats = " do i = 1, size (object%sample_fmt) if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "(A)", advance="no") char (object%sample_fmt(i)) end do write (u, "(A)") end if call write_separator (u) write (u, "(1x,A)", advance="no") "Event callback:" if (allocated (object%event_callback)) then call object%event_callback%write (u) else write (u, "(1x,A)") "[undefined]" end if call object%process_stack%write (u, pacify) write (u, "(1x,A,1x,L1)") "quit :", object%quit write (u, "(1x,A,1x,I0)") "quit_code:", object%quit_code call write_separator (u, 2) write (u, "(1x,A,1x,A)") "Logfile :", "'" // trim (char (object%logfile)) // "'" call write_separator (u, 2) end subroutine rt_data_write @ %def rt_data_write @ Write only selected variables. <>= procedure :: write_vars => rt_data_write_vars <>= subroutine rt_data_write_vars (object, unit, vars) class(rt_data_t), intent(in), target :: object integer, intent(in), optional :: unit type(string_t), dimension(:), intent(in) :: vars type(var_list_t), pointer :: var_list integer :: u, i u = given_output_unit (unit) var_list => object%get_var_list_ptr () do i = 1, size (vars) associate (var => vars(i)) if (var_list%contains (var, follow_link=.true.)) then call var_list%write_var (var, unit = u, & follow_link = .true., defined=.true.) end if end associate end do end subroutine rt_data_write_vars @ %def rt_data_write_vars @ Write only the model list. <>= procedure :: write_model_list => rt_data_write_model_list <>= subroutine rt_data_write_model_list (object, unit) class(rt_data_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) call object%model_list%write (u) end subroutine rt_data_write_model_list @ %def rt_data_write_model_list @ Write only the library stack. <>= procedure :: write_libraries => rt_data_write_libraries <>= subroutine rt_data_write_libraries (object, unit, libpath) class(rt_data_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: libpath integer :: u u = given_output_unit (unit) call object%prclib_stack%write (u, libpath) end subroutine rt_data_write_libraries @ %def rt_data_write_libraries @ Write only the beam data. <>= procedure :: write_beams => rt_data_write_beams <>= subroutine rt_data_write_beams (object, unit) class(rt_data_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) call write_separator (u, 2) call object%beam_structure%write (u) call write_separator (u, 2) end subroutine rt_data_write_beams @ %def rt_data_write_beams @ Write only the process and event expressions. <>= procedure :: write_expr => rt_data_write_expr <>= subroutine rt_data_write_expr (object, unit) class(rt_data_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) call write_separator (u, 2) call object%pn%write (u) call write_separator (u, 2) end subroutine rt_data_write_expr @ %def rt_data_write_expr @ Write only the process stack. <>= procedure :: write_process_stack => rt_data_write_process_stack <>= subroutine rt_data_write_process_stack (object, unit) class(rt_data_t), intent(in) :: object integer, intent(in), optional :: unit call object%process_stack%write (unit) end subroutine rt_data_write_process_stack @ %def rt_data_write_process_stack @ <>= procedure :: write_var_descriptions => rt_data_write_var_descriptions <>= subroutine rt_data_write_var_descriptions (rt_data, unit, ascii_output) class(rt_data_t), intent(in) :: rt_data integer, intent(in), optional :: unit logical, intent(in), optional :: ascii_output integer :: u logical :: ao u = given_output_unit (unit) ao = .false.; if (present (ascii_output)) ao = ascii_output call rt_data%var_list%write (u, follow_link=.true., & descriptions=.true., ascii_output=ao) end subroutine rt_data_write_var_descriptions @ %def rt_data_write_var_descriptions @ <>= procedure :: show_description_of_string => rt_data_show_description_of_string <>= subroutine rt_data_show_description_of_string (rt_data, string, & unit, ascii_output) class(rt_data_t), intent(in) :: rt_data type(string_t), intent(in) :: string integer, intent(in), optional :: unit logical, intent(in), optional :: ascii_output integer :: u logical :: ao u = given_output_unit (unit) ao = .false.; if (present (ascii_output)) ao = ascii_output call rt_data%var_list%write_var (string, unit=u, follow_link=.true., & defined=.false., descriptions=.true., ascii_output=ao) end subroutine rt_data_show_description_of_string @ %def rt_data_show_description_of_string @ \subsection{Clear} The [[clear]] command can remove the contents of various subobjects. The objects themselves should stay. <>= procedure :: clear_beams => rt_data_clear_beams <>= subroutine rt_data_clear_beams (global) class(rt_data_t), intent(inout) :: global call global%beam_structure%final_sf () call global%beam_structure%final_pol () call global%beam_structure%final_mom () end subroutine rt_data_clear_beams @ %def rt_data_clear_beams @ \subsection{Initialization} Initialize runtime data. This defines special variables such as [[sqrts]], and should be done only for the instance that is actually global. Local copies will inherit the special variables. We link the global variable list to the process stack variable list, so the latter is always available (and kept global). <>= procedure :: global_init => rt_data_global_init <>= subroutine rt_data_global_init (global, paths, logfile) class(rt_data_t), intent(out), target :: global type(paths_t), intent(in), optional :: paths type(string_t), intent(in), optional :: logfile integer :: seed call global%os_data%init (paths) if (present (logfile)) then global%logfile = logfile else global%logfile = "" end if allocate (global%out_files) call system_clock (seed) call global%var_list%init_defaults (seed, paths) call global%init_pointer_variables () call global%process_stack%init_var_list (global%var_list) end subroutine rt_data_global_init @ %def rt_data_global_init @ \subsection{Local copies} This is done at compile time when a local copy of runtime data is needed: Link the variable list and initialize all derived parameters. This allows for synchronizing them with local variable changes without affecting global data. Also re-initialize pointer variables, so they point to local copies of their targets. <>= procedure :: local_init => rt_data_local_init <>= subroutine rt_data_local_init (local, global, env) class(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(in), target :: global integer, intent(in), optional :: env local%context => global call local%process_stack%link (global%process_stack) call local%process_stack%init_var_list (local%var_list) call local%process_stack%link_var_list (global%var_list) call local%var_list%append_string (var_str ("$model_name"), & var_str (""), intrinsic=.true.) call local%init_pointer_variables () local%fallback_model => global%fallback_model local%os_data = global%os_data local%logfile = global%logfile call local%model_list%link (global%model_list) local%model => global%model if (associated (local%model)) then call local%model%link_var_list (local%var_list) end if if (allocated (global%event_callback)) then allocate (local%event_callback, source = global%event_callback) end if end subroutine rt_data_local_init @ %def rt_data_local_init @ These variables point to objects which get local copies: <>= procedure :: init_pointer_variables => rt_data_init_pointer_variables <>= subroutine rt_data_init_pointer_variables (local) class(rt_data_t), intent(inout), target :: local logical, target, save :: known = .true. call local%var_list%append_string_ptr (var_str ("$fc"), & local%os_data%fc, known, intrinsic=.true., & description=var_str('This string variable gives the ' // & '\ttt{Fortran} compiler used within \whizard. It can ' // & 'only be accessed, not set by the user. (cf. also ' // & '\ttt{\$fcflags}, \ttt{\$fclibs})')) call local%var_list%append_string_ptr (var_str ("$fcflags"), & local%os_data%fcflags, known, intrinsic=.true., & description=var_str('This string variable gives the ' // & 'compiler flags for the \ttt{Fortran} compiler used ' // & 'within \whizard. It can only be accessed, not set by ' // & 'the user. (cf. also \ttt{\$fc}, \ttt{\$fclibs})')) call local%var_list%append_string_ptr (var_str ("$fclibs"), & local%os_data%fclibs, known, intrinsic=.true., & description=var_str('This string variable gives the ' // & 'linked libraries for the \ttt{Fortran} compiler used ' // & 'within \whizard. It can only be accessed, not set by ' // & 'the user. (cf. also \ttt{\$fc}, \ttt{\$fcflags})')) end subroutine rt_data_init_pointer_variables @ %def rt_data_init_pointer_variables @ This is done at execution time: Copy data, transfer pointers. [[local]] has intent(inout) because its local variable list has already been prepared by the previous routine. To be pedantic, the local pointers to model and library should point to the entries in the local copies. (However, as long as these are just shallow copies with identical content, this is actually irrelevant.) The process library and process stacks behave as global objects. The copies of the process library and process stacks should be shallow copies, so the contents stay identical. Since objects may be pushed on the stack in the local environment, upon restoring the global environment, we should reverse the assignment. Then the added stack elements will end up on the global stack. (This should be reconsidered in a parallel environment.) <>= procedure :: activate => rt_data_activate <>= subroutine rt_data_activate (local) class(rt_data_t), intent(inout), target :: local class(rt_data_t), pointer :: global global => local%context if (associated (global)) then local%lexer => global%lexer call global%copy_globals (local) local%os_data = global%os_data local%logfile = global%logfile if (associated (global%prclib)) then local%prclib => & local%prclib_stack%get_library_ptr (global%prclib%get_name ()) end if call local%import_values () call local%process_stack%link (global%process_stack) local%it_list = global%it_list local%beam_structure = global%beam_structure local%pn = global%pn if (allocated (local%sample_fmt)) deallocate (local%sample_fmt) if (allocated (global%sample_fmt)) then allocate (local%sample_fmt (size (global%sample_fmt)), & source = global%sample_fmt) end if local%out_files => global%out_files local%model => global%model local%model_is_copy = .false. else if (.not. associated (local%model)) then local%model => local%preload_model local%model_is_copy = .false. end if if (associated (local%model)) then call local%model%link_var_list (local%var_list) call local%var_list%set_string (var_str ("$model_name"), & local%model%get_name (), is_known = .true.) else call local%var_list%set_string (var_str ("$model_name"), & var_str (""), is_known = .false.) end if end subroutine rt_data_activate @ %def rt_data_activate @ Restore the previous state of data, without actually finalizing the local environment. We also clear the local process stack. Some local modifications (model list and process library stack) are communicated to the global context, if there is any. If the [[keep_local]] flag is set, we want to retain current settings in the local environment. In particular, we create an instance of the currently selected model (which thus becomes separated from the model library!). The local variables are also kept. <>= procedure :: deactivate => rt_data_deactivate <>= subroutine rt_data_deactivate (local, global, keep_local) class(rt_data_t), intent(inout), target :: local class(rt_data_t), intent(inout), optional, target :: global logical, intent(in), optional :: keep_local type(string_t) :: local_model, local_scheme logical :: same_model, delete delete = .true.; if (present (keep_local)) delete = .not. keep_local if (present (global)) then if (associated (global%model) .and. associated (local%model)) then local_model = local%model%get_name () if (global%model%has_schemes ()) then local_scheme = local%model%get_scheme () same_model = & global%model%matches (local_model, local_scheme) else same_model = global%model%matches (local_model) end if else same_model = .false. end if if (delete) then call local%process_stack%clear () call local%unselect_model () call local%unset_values () else if (associated (local%model)) then call local%ensure_model_copy () end if if (.not. same_model .and. associated (global%model)) then if (global%model%has_schemes ()) then call msg_message ("Restoring model '" // & char (global%model%get_name ()) // "', scheme '" // & char (global%model%get_scheme ()) // "'") else call msg_message ("Restoring model '" // & char (global%model%get_name ()) // "'") end if end if if (associated (global%model)) then call global%model%link_var_list (global%var_list) end if call global%restore_globals (local) else call local%unselect_model () end if end subroutine rt_data_deactivate @ %def rt_data_deactivate @ This imports the global objects for which local modifications should be kept. Currently, this is only the process library stack. <>= procedure :: copy_globals => rt_data_copy_globals <>= subroutine rt_data_copy_globals (global, local) class(rt_data_t), intent(in) :: global class(rt_data_t), intent(inout) :: local local%prclib_stack = global%prclib_stack end subroutine rt_data_copy_globals @ %def rt_data_copy_globals @ This restores global objects for which local modifications should be kept. May also modify (remove) the local objects. <>= procedure :: restore_globals => rt_data_restore_globals <>= subroutine rt_data_restore_globals (global, local) class(rt_data_t), intent(inout) :: global class(rt_data_t), intent(inout) :: local global%prclib_stack = local%prclib_stack call local%handle_exports (global) end subroutine rt_data_restore_globals @ %def rt_data_restore_globals @ \subsection{Exported objects} Exported objects are transferred to the global state when a local environment is closed. (For the top-level global data set, there is no effect.) The current implementation handles only the [[results]] object, which resolves to the local process stack. The stack elements are appended to the global stack without modification, the local stack becomes empty. Write names of objects to be exported: <>= procedure :: write_exports => rt_data_write_exports <>= subroutine rt_data_write_exports (rt_data, unit) class(rt_data_t), intent(in) :: rt_data integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) do i = 1, rt_data%get_n_export () write (u, "(A)") char (rt_data%export(i)) end do end subroutine rt_data_write_exports @ %def rt_data_write_exports @ The number of entries in the export list. <>= procedure :: get_n_export => rt_data_get_n_export <>= function rt_data_get_n_export (rt_data) result (n) class(rt_data_t), intent(in) :: rt_data integer :: n if (allocated (rt_data%export)) then n = size (rt_data%export) else n = 0 end if end function rt_data_get_n_export @ %def rt_data_get_n_export @ Return a specific export @ Append new names to the export list. If a duplicate occurs, do not transfer it. <>= procedure :: append_exports => rt_data_append_exports <>= subroutine rt_data_append_exports (rt_data, export) class(rt_data_t), intent(inout) :: rt_data type(string_t), dimension(:), intent(in) :: export logical, dimension(:), allocatable :: mask type(string_t), dimension(:), allocatable :: tmp integer :: i, j, n if (.not. allocated (rt_data%export)) allocate (rt_data%export (0)) n = size (rt_data%export) allocate (mask (size (export)), source=.false.) do i = 1, size (export) mask(i) = all (export(i) /= rt_data%export) & .and. all (export(i) /= export(:i-1)) end do if (count (mask) > 0) then allocate (tmp (n + count (mask))) tmp(1:n) = rt_data%export(:) j = n do i = 1, size (export) if (mask(i)) then j = j + 1 tmp(j) = export(i) end if end do call move_alloc (from=tmp, to=rt_data%export) end if end subroutine rt_data_append_exports @ %def rt_data_append_exports @ Transfer export-objects from the [[local]] rt data to the [[global]] rt data, as far as supported. <>= procedure :: handle_exports => rt_data_handle_exports <>= subroutine rt_data_handle_exports (local, global) class(rt_data_t), intent(inout), target :: local class(rt_data_t), intent(inout), target :: global type(string_t) :: export integer :: i if (local%get_n_export () > 0) then do i = 1, local%get_n_export () export = local%export(i) select case (char (export)) case ("results") call msg_message ("Exporting integration results & &to outer environment") call local%transfer_process_stack (global) case default call msg_bug ("handle exports: '" & // char (export) // "' unsupported") end select end do end if end subroutine rt_data_handle_exports @ %def rt_data_handle_exports @ Export the process stack. One-by-one, take the last process from the local stack and push it on the global stack. Also handle the corresponding result variables: append if the process did not exist yet in the global stack, otherwise update. TODO: result variables do not work that way yet, require initialization in the global variable list. <>= procedure :: transfer_process_stack => rt_data_transfer_process_stack <>= subroutine rt_data_transfer_process_stack (local, global) class(rt_data_t), intent(inout), target :: local class(rt_data_t), intent(inout), target :: global type(process_entry_t), pointer :: process type(string_t) :: process_id do call local%process_stack%pop_last (process) if (.not. associated (process)) exit process_id = process%get_id () call global%process_stack%push (process) call global%process_stack%fill_result_vars (process_id) call global%process_stack%update_result_vars & (process_id, global%var_list) end do end subroutine rt_data_transfer_process_stack @ %def rt_data_transfer_process_stack @ \subsection{Finalization} Finalizer for the variable list and the structure-function list. This is done only for the global RT dataset; local copies contain pointers to this and do not need a finalizer. <>= procedure :: final => rt_data_global_final <>= subroutine rt_data_global_final (global) class(rt_data_t), intent(inout) :: global call global%process_stack%final () call global%prclib_stack%final () call global%model_list%final () call global%var_list%final (follow_link=.false.) if (associated (global%out_files)) then call file_list_final (global%out_files) deallocate (global%out_files) end if end subroutine rt_data_global_final @ %def rt_data_global_final @ The local copy needs a finalizer for the variable list, which consists of local copies. This finalizer is called only when the local environment is finally discarded. (Note that the process stack should already have been cleared after execution, which can occur many times for the same local environment.) <>= procedure :: local_final => rt_data_local_final <>= subroutine rt_data_local_final (local) class(rt_data_t), intent(inout) :: local call local%process_stack%clear () call local%model_list%final () call local%var_list%final (follow_link=.false.) end subroutine rt_data_local_final @ %def rt_data_local_final @ \subsection{Model Management} Read a model, so it becomes available for activation. No variables or model copies, this is just initialization. If this is a local environment, the model will be automatically read into the global context. <>= procedure :: read_model => rt_data_read_model <>= subroutine rt_data_read_model (global, name, model, scheme) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name type(string_t), intent(in), optional :: scheme type(model_t), pointer, intent(out) :: model type(string_t) :: filename filename = name // ".mdl" call global%model_list%read_model & (name, filename, global%os_data, model, scheme) end subroutine rt_data_read_model @ %def rt_data_read_model @ Read a UFO model. Create it on the fly if necessary. <>= procedure :: read_ufo_model => rt_data_read_ufo_model <>= subroutine rt_data_read_ufo_model (global, name, model, ufo_path) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name type(model_t), pointer, intent(out) :: model type(string_t), intent(in), optional :: ufo_path type(string_t) :: filename filename = name // ".ufo.mdl" call global%model_list%read_model & (name, filename, global%os_data, model, ufo=.true., ufo_path=ufo_path) end subroutine rt_data_read_ufo_model @ %def rt_data_read_ufo_model @ Initialize the fallback model. This model is used whenever the current model does not describe all physical particles (hadrons, mainly). It is not supposed to be modified, and the pointer should remain linked to this model. <>= procedure :: init_fallback_model => rt_data_init_fallback_model <>= subroutine rt_data_init_fallback_model (global, name, filename) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name, filename call global%model_list%read_model & (name, filename, global%os_data, global%fallback_model) end subroutine rt_data_init_fallback_model @ %def rt_data_init_fallback_model @ Activate a model: assign the current-model pointer and set the model name in the variable list. If necessary, read the model from file. Link the global variable list to the model variable list. <>= procedure :: select_model => rt_data_select_model <>= subroutine rt_data_select_model (global, name, scheme, ufo, ufo_path) class(rt_data_t), intent(inout), target :: global type(string_t), intent(in) :: name type(string_t), intent(in), optional :: scheme logical, intent(in), optional :: ufo type(string_t), intent(in), optional :: ufo_path logical :: same_model, ufo_model ufo_model = .false.; if (present (ufo)) ufo_model = ufo if (associated (global%model)) then same_model = global%model%matches (name, scheme, ufo) else same_model = .false. end if if (.not. same_model) then global%model => global%model_list%get_model_ptr (name, scheme, ufo) if (.not. associated (global%model)) then if (ufo_model) then call global%read_ufo_model (name, global%model, ufo_path) else call global%read_model (name, global%model) end if global%model_is_copy = .false. else if (associated (global%context)) then global%model_is_copy = & global%model_list%model_exists (name, scheme, ufo, & follow_link=.false.) else global%model_is_copy = .false. end if end if if (associated (global%model)) then call global%model%link_var_list (global%var_list) call global%var_list%set_string (var_str ("$model_name"), & name, is_known = .true.) if (global%model%is_ufo_model ()) then call msg_message ("Switching to model '" // char (name) // "' " & // "(generated from UFO source)") else if (global%model%has_schemes ()) then call msg_message ("Switching to model '" // char (name) // "', " & // "scheme '" // char (global%model%get_scheme ()) // "'") else call msg_message ("Switching to model '" // char (name) // "'") end if else call global%var_list%set_string (var_str ("$model_name"), & var_str (""), is_known = .false.) end if end subroutine rt_data_select_model @ %def rt_data_select_model @ Remove the model link. Do not unset the model name variable, because this may unset the variable in a parent [[rt_data]] object (via linked var lists). <>= procedure :: unselect_model => rt_data_unselect_model <>= subroutine rt_data_unselect_model (global) class(rt_data_t), intent(inout), target :: global if (associated (global%model)) then global%model => null () global%model_is_copy = .false. end if end subroutine rt_data_unselect_model @ %def rt_data_unselect_model @ Create a copy of the currently selected model and append it to the local model list. The model pointer is redirected to the copy. (Not applicable for the global model list, those models will be modified in-place.) <>= procedure :: ensure_model_copy => rt_data_ensure_model_copy <>= subroutine rt_data_ensure_model_copy (global) class(rt_data_t), intent(inout), target :: global if (associated (global%context)) then if (.not. global%model_is_copy) then call global%model_list%append_copy (global%model, global%model) global%model_is_copy = .true. call global%model%link_var_list (global%var_list) end if end if end subroutine rt_data_ensure_model_copy @ %def rt_data_ensure_model_copy @ Modify a model variable. The update mechanism will ensure that the model parameter set remains consistent. This has to take place in a local copy of the current model. If there is none yet, create one. <>= procedure :: model_set_real => rt_data_model_set_real <>= subroutine rt_data_model_set_real (global, name, rval, verbose, pacified) class(rt_data_t), intent(inout), target :: global type(string_t), intent(in) :: name real(default), intent(in) :: rval logical, intent(in), optional :: verbose, pacified call global%ensure_model_copy () call global%model%set_real (name, rval, verbose, pacified) end subroutine rt_data_model_set_real @ %def rt_data_model_set_real @ Modify particle properties. This has to take place in a local copy of the current model. If there is none yet, create one. <>= procedure :: modify_particle => rt_data_modify_particle <>= subroutine rt_data_modify_particle & (global, pdg, polarized, stable, decay, & isotropic_decay, diagonal_decay, decay_helicity) class(rt_data_t), intent(inout), target :: global integer, intent(in) :: pdg logical, intent(in), optional :: polarized, stable logical, intent(in), optional :: isotropic_decay, diagonal_decay integer, intent(in), optional :: decay_helicity type(string_t), dimension(:), intent(in), optional :: decay call global%ensure_model_copy () if (present (polarized)) then if (polarized) then call global%model%set_polarized (pdg) else call global%model%set_unpolarized (pdg) end if end if if (present (stable)) then if (stable) then call global%model%set_stable (pdg) else if (present (decay)) then call global%model%set_unstable & (pdg, decay, isotropic_decay, diagonal_decay, decay_helicity) else call msg_bug ("Setting particle unstable: missing decay processes") end if end if end subroutine rt_data_modify_particle @ %def rt_data_modify_particle @ \subsection{Managing Variables} Return a pointer to the currently active variable list. If there is no model, this is the global variable list. If there is one, it is the model variable list, which should be linked to the former. <>= procedure :: get_var_list_ptr => rt_data_get_var_list_ptr <>= function rt_data_get_var_list_ptr (global) result (var_list) class(rt_data_t), intent(in), target :: global type(var_list_t), pointer :: var_list if (associated (global%model)) then var_list => global%model%get_var_list_ptr () else var_list => global%var_list end if end function rt_data_get_var_list_ptr @ %def rt_data_get_var_list_ptr @ Initialize a local variable: append it to the current variable list. No initial value, yet. <>= procedure :: append_log => rt_data_append_log procedure :: append_int => rt_data_append_int procedure :: append_real => rt_data_append_real procedure :: append_cmplx => rt_data_append_cmplx procedure :: append_subevt => rt_data_append_subevt procedure :: append_pdg_array => rt_data_append_pdg_array procedure :: append_string => rt_data_append_string <>= subroutine rt_data_append_log (local, name, lval, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name logical, intent(in), optional :: lval logical, intent(in), optional :: intrinsic, user call local%var_list%append_log (name, lval, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_log subroutine rt_data_append_int (local, name, ival, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name integer, intent(in), optional :: ival logical, intent(in), optional :: intrinsic, user call local%var_list%append_int (name, ival, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_int subroutine rt_data_append_real (local, name, rval, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name real(default), intent(in), optional :: rval logical, intent(in), optional :: intrinsic, user call local%var_list%append_real (name, rval, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_real subroutine rt_data_append_cmplx (local, name, cval, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name complex(default), intent(in), optional :: cval logical, intent(in), optional :: intrinsic, user call local%var_list%append_cmplx (name, cval, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_cmplx subroutine rt_data_append_subevt (local, name, pval, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name type(subevt_t), intent(in), optional :: pval logical, intent(in) :: intrinsic, user call local%var_list%append_subevt (name, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_subevt subroutine rt_data_append_pdg_array (local, name, aval, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name type(pdg_array_t), intent(in), optional :: aval logical, intent(in), optional :: intrinsic, user call local%var_list%append_pdg_array (name, aval, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_pdg_array subroutine rt_data_append_string (local, name, sval, intrinsic, user) class(rt_data_t), intent(inout) :: local type(string_t), intent(in) :: name type(string_t), intent(in), optional :: sval logical, intent(in), optional :: intrinsic, user call local%var_list%append_string (name, sval, & intrinsic = intrinsic, user = user) end subroutine rt_data_append_string @ %def rt_data_append_log @ %def rt_data_append_int @ %def rt_data_append_real @ %def rt_data_append_cmplx @ %def rt_data_append_subevt @ %def rt_data_append_pdg_array @ %def rt_data_append_string @ Import values for all local variables, given a global context environment where these variables are defined. <>= procedure :: import_values => rt_data_import_values <>= subroutine rt_data_import_values (local) class(rt_data_t), intent(inout) :: local type(rt_data_t), pointer :: global global => local%context if (associated (global)) then call local%var_list%import (global%var_list) end if end subroutine rt_data_import_values @ %def rt_data_import_values @ Unset all variable values. <>= procedure :: unset_values => rt_data_unset_values <>= subroutine rt_data_unset_values (global) class(rt_data_t), intent(inout) :: global call global%var_list%undefine (follow_link=.false.) end subroutine rt_data_unset_values @ %def rt_data_unset_values @ Set a variable. (Not a model variable, these are handled separately.) We can assume that the variable has been initialized. <>= procedure :: set_log => rt_data_set_log procedure :: set_int => rt_data_set_int procedure :: set_real => rt_data_set_real procedure :: set_cmplx => rt_data_set_cmplx procedure :: set_subevt => rt_data_set_subevt procedure :: set_pdg_array => rt_data_set_pdg_array procedure :: set_string => rt_data_set_string <>= subroutine rt_data_set_log & (global, name, lval, is_known, force, verbose) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name logical, intent(in) :: lval logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose call global%var_list%set_log (name, lval, is_known, & force=force, verbose=verbose) end subroutine rt_data_set_log subroutine rt_data_set_int & (global, name, ival, is_known, force, verbose) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name integer, intent(in) :: ival logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose call global%var_list%set_int (name, ival, is_known, & force=force, verbose=verbose) end subroutine rt_data_set_int subroutine rt_data_set_real & (global, name, rval, is_known, force, verbose, pacified) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name real(default), intent(in) :: rval logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose, pacified call global%var_list%set_real (name, rval, is_known, & force=force, verbose=verbose, pacified=pacified) end subroutine rt_data_set_real subroutine rt_data_set_cmplx & (global, name, cval, is_known, force, verbose, pacified) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name complex(default), intent(in) :: cval logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose, pacified call global%var_list%set_cmplx (name, cval, is_known, & force=force, verbose=verbose, pacified=pacified) end subroutine rt_data_set_cmplx subroutine rt_data_set_subevt & (global, name, pval, is_known, force, verbose) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name type(subevt_t), intent(in) :: pval logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose call global%var_list%set_subevt (name, pval, is_known, & force=force, verbose=verbose) end subroutine rt_data_set_subevt subroutine rt_data_set_pdg_array & (global, name, aval, is_known, force, verbose) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name type(pdg_array_t), intent(in) :: aval logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose call global%var_list%set_pdg_array (name, aval, is_known, & force=force, verbose=verbose) end subroutine rt_data_set_pdg_array subroutine rt_data_set_string & (global, name, sval, is_known, force, verbose) class(rt_data_t), intent(inout) :: global type(string_t), intent(in) :: name type(string_t), intent(in) :: sval logical, intent(in) :: is_known logical, intent(in), optional :: force, verbose call global%var_list%set_string (name, sval, is_known, & force=force, verbose=verbose) end subroutine rt_data_set_string @ %def rt_data_set_log @ %def rt_data_set_int @ %def rt_data_set_real @ %def rt_data_set_cmplx @ %def rt_data_set_subevt @ %def rt_data_set_pdg_array @ %def rt_data_set_string @ Return the value of a variable, assuming that the type is correct. <>= procedure :: get_lval => rt_data_get_lval procedure :: get_ival => rt_data_get_ival procedure :: get_rval => rt_data_get_rval procedure :: get_cval => rt_data_get_cval procedure :: get_pval => rt_data_get_pval procedure :: get_aval => rt_data_get_aval procedure :: get_sval => rt_data_get_sval <>= function rt_data_get_lval (global, name) result (lval) logical :: lval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () lval = var_list%get_lval (name) end function rt_data_get_lval function rt_data_get_ival (global, name) result (ival) integer :: ival class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () ival = var_list%get_ival (name) end function rt_data_get_ival function rt_data_get_rval (global, name) result (rval) real(default) :: rval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () rval = var_list%get_rval (name) end function rt_data_get_rval function rt_data_get_cval (global, name) result (cval) complex(default) :: cval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () cval = var_list%get_cval (name) end function rt_data_get_cval function rt_data_get_aval (global, name) result (aval) type(pdg_array_t) :: aval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () aval = var_list%get_aval (name) end function rt_data_get_aval function rt_data_get_pval (global, name) result (pval) type(subevt_t) :: pval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () pval = var_list%get_pval (name) end function rt_data_get_pval function rt_data_get_sval (global, name) result (sval) type(string_t) :: sval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () sval = var_list%get_sval (name) end function rt_data_get_sval @ %def rt_data_get_lval @ %def rt_data_get_ival @ %def rt_data_get_rval @ %def rt_data_get_cval @ %def rt_data_get_pval @ %def rt_data_get_aval @ %def rt_data_get_sval @ Return true if the variable exists in the global list. <>= procedure :: contains => rt_data_contains <>= function rt_data_contains (global, name) result (lval) logical :: lval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () lval = var_list%contains (name) end function rt_data_contains @ %def rt_data_contains @ Return true if the value of the variable is known. <>= procedure :: is_known => rt_data_is_known <>= function rt_data_is_known (global, name) result (lval) logical :: lval class(rt_data_t), intent(in), target :: global type(string_t), intent(in) :: name type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () lval = var_list%is_known (name) end function rt_data_is_known @ %def rt_data_is_known @ \subsection{Further Content} Add a library (available via a pointer of type [[prclib_entry_t]]) to the stack and update the pointer and variable list to the current library. The pointer association of [[prclib_entry]] will be discarded. <>= procedure :: add_prclib => rt_data_add_prclib <>= subroutine rt_data_add_prclib (global, prclib_entry) class(rt_data_t), intent(inout) :: global type(prclib_entry_t), intent(inout), pointer :: prclib_entry call global%prclib_stack%push (prclib_entry) call global%update_prclib (global%prclib_stack%get_first_ptr ()) end subroutine rt_data_add_prclib @ %def rt_data_add_prclib @ Given a pointer to a process library, make this the currently active library. <>= procedure :: update_prclib => rt_data_update_prclib <>= subroutine rt_data_update_prclib (global, lib) class(rt_data_t), intent(inout) :: global type(process_library_t), intent(in), target :: lib global%prclib => lib if (global%var_list%contains (& var_str ("$library_name"), follow_link = .false.)) then call global%var_list%set_string (var_str ("$library_name"), & global%prclib%get_name (), is_known=.true.) else call global%var_list%append_string ( & var_str ("$library_name"), global%prclib%get_name (), & intrinsic = .true.) end if end subroutine rt_data_update_prclib @ %def rt_data_update_prclib @ \subsection{Miscellaneous} The helicity selection data are distributed among several parameters. Here, we collect them in a single record. <>= procedure :: get_helicity_selection => rt_data_get_helicity_selection <>= function rt_data_get_helicity_selection (rt_data) result (helicity_selection) class(rt_data_t), intent(in) :: rt_data type(helicity_selection_t) :: helicity_selection associate (var_list => rt_data%var_list) helicity_selection%active = var_list%get_lval (& var_str ("?helicity_selection_active")) if (helicity_selection%active) then helicity_selection%threshold = var_list%get_rval (& var_str ("helicity_selection_threshold")) helicity_selection%cutoff = var_list%get_ival (& var_str ("helicity_selection_cutoff")) end if end associate end function rt_data_get_helicity_selection @ %def rt_data_get_helicity_selection @ Show the beam setup: beam structure and relevant global variables. <>= procedure :: show_beams => rt_data_show_beams <>= subroutine rt_data_show_beams (rt_data, unit) class(rt_data_t), intent(in) :: rt_data integer, intent(in), optional :: unit type(string_t) :: s integer :: u u = given_output_unit (unit) associate (beams => rt_data%beam_structure, var_list => rt_data%var_list) call beams%write (u) if (.not. beams%asymmetric () .and. beams%get_n_beam () == 2) then write (u, "(2x,A," // FMT_19 // ",1x,'GeV')") "sqrts =", & var_list%get_rval (var_str ("sqrts")) end if if (beams%contains ("pdf_builtin")) then s = var_list%get_sval (var_str ("$pdf_builtin_set")) if (s /= "") then write (u, "(2x,A,1x,3A)") "PDF set =", '"', char (s), '"' else write (u, "(2x,A,1x,A)") "PDF set =", "[undefined]" end if end if if (beams%contains ("lhapdf")) then s = var_list%get_sval (var_str ("$lhapdf_dir")) if (s /= "") then write (u, "(2x,A,1x,3A)") "LHAPDF dir =", '"', char (s), '"' end if s = var_list%get_sval (var_str ("$lhapdf_file")) if (s /= "") then write (u, "(2x,A,1x,3A)") "LHAPDF file =", '"', char (s), '"' write (u, "(2x,A,1x,I0)") "LHAPDF member =", & var_list%get_ival (var_str ("lhapdf_member")) else write (u, "(2x,A,1x,A)") "LHAPDF file =", "[undefined]" end if end if if (beams%contains ("lhapdf_photon")) then s = var_list%get_sval (var_str ("$lhapdf_dir")) if (s /= "") then write (u, "(2x,A,1x,3A)") "LHAPDF dir =", '"', char (s), '"' end if s = var_list%get_sval (var_str ("$lhapdf_photon_file")) if (s /= "") then write (u, "(2x,A,1x,3A)") "LHAPDF file =", '"', char (s), '"' write (u, "(2x,A,1x,I0)") "LHAPDF member =", & var_list%get_ival (var_str ("lhapdf_member")) write (u, "(2x,A,1x,I0)") "LHAPDF scheme =", & var_list%get_ival (& var_str ("lhapdf_photon_scheme")) else write (u, "(2x,A,1x,A)") "LHAPDF file =", "[undefined]" end if end if if (beams%contains ("isr")) then write (u, "(2x,A," // FMT_19 // ")") "ISR alpha =", & var_list%get_rval (var_str ("isr_alpha")) write (u, "(2x,A," // FMT_19 // ")") "ISR Q max =", & var_list%get_rval (var_str ("isr_q_max")) write (u, "(2x,A," // FMT_19 // ")") "ISR mass =", & var_list%get_rval (var_str ("isr_mass")) write (u, "(2x,A,1x,I0)") "ISR order =", & var_list%get_ival (var_str ("isr_order")) write (u, "(2x,A,1x,L1)") "ISR recoil =", & var_list%get_lval (var_str ("?isr_recoil")) write (u, "(2x,A,1x,L1)") "ISR energy cons. =", & var_list%get_lval (var_str ("?isr_keep_energy")) end if if (beams%contains ("epa")) then write (u, "(2x,A," // FMT_19 // ")") "EPA alpha =", & var_list%get_rval (var_str ("epa_alpha")) write (u, "(2x,A," // FMT_19 // ")") "EPA x min =", & var_list%get_rval (var_str ("epa_x_min")) write (u, "(2x,A," // FMT_19 // ")") "EPA Q min =", & var_list%get_rval (var_str ("epa_q_min")) write (u, "(2x,A," // FMT_19 // ")") "EPA Q max =", & var_list%get_rval (var_str ("epa_q_max")) write (u, "(2x,A," // FMT_19 // ")") "EPA mass =", & var_list%get_rval (var_str ("epa_mass")) write (u, "(2x,A,1x,L1)") "EPA recoil =", & var_list%get_lval (var_str ("?epa_recoil")) write (u, "(2x,A,1x,L1)") "EPA energy cons. =", & var_list%get_lval (var_str ("?epa_keep_energy")) end if if (beams%contains ("ewa")) then write (u, "(2x,A," // FMT_19 // ")") "EWA x min =", & var_list%get_rval (var_str ("ewa_x_min")) write (u, "(2x,A," // FMT_19 // ")") "EWA Pt max =", & var_list%get_rval (var_str ("ewa_pt_max")) write (u, "(2x,A," // FMT_19 // ")") "EWA mass =", & var_list%get_rval (var_str ("ewa_mass")) write (u, "(2x,A,1x,L1)") "EWA recoil =", & var_list%get_lval (var_str ("?ewa_recoil")) write (u, "(2x,A,1x,L1)") "EWA energy cons. =", & var_list%get_lval (var_str ("ewa_keep_energy")) end if if (beams%contains ("circe1")) then write (u, "(2x,A,1x,I0)") "CIRCE1 version =", & var_list%get_ival (var_str ("circe1_ver")) write (u, "(2x,A,1x,I0)") "CIRCE1 revision =", & var_list%get_ival (var_str ("circe1_rev")) s = var_list%get_sval (var_str ("$circe1_acc")) write (u, "(2x,A,1x,A)") "CIRCE1 acceler. =", char (s) write (u, "(2x,A,1x,I0)") "CIRCE1 chattin. =", & var_list%get_ival (var_str ("circe1_chat")) write (u, "(2x,A," // FMT_19 // ")") "CIRCE1 sqrts =", & var_list%get_rval (var_str ("circe1_sqrts")) write (u, "(2x,A," // FMT_19 // ")") "CIRCE1 epsil. =", & var_list%get_rval (var_str ("circe1_eps")) write (u, "(2x,A,1x,L1)") "CIRCE1 phot. 1 =", & var_list%get_lval (var_str ("?circe1_photon1")) write (u, "(2x,A,1x,L1)") "CIRCE1 phot. 2 =", & var_list%get_lval (var_str ("?circe1_photon2")) write (u, "(2x,A,1x,L1)") "CIRCE1 generat. =", & var_list%get_lval (var_str ("?circe1_generate")) write (u, "(2x,A,1x,L1)") "CIRCE1 mapping =", & var_list%get_lval (var_str ("?circe1_map")) write (u, "(2x,A," // FMT_19 // ")") "CIRCE1 map. slope =", & var_list%get_rval (var_str ("circe1_mapping_slope")) write (u, "(2x,A,1x,L1)") "CIRCE recoil photon =", & var_list%get_lval (var_str ("?circe1_with_radiation")) end if if (beams%contains ("circe2")) then s = var_list%get_sval (var_str ("$circe2_design")) write (u, "(2x,A,1x,A)") "CIRCE2 design =", char (s) s = var_list%get_sval (var_str ("$circe2_file")) write (u, "(2x,A,1x,A)") "CIRCE2 file =", char (s) write (u, "(2x,A,1x,L1)") "CIRCE2 polarized =", & var_list%get_lval (var_str ("?circe2_polarized")) end if if (beams%contains ("gaussian")) then write (u, "(2x,A,1x," // FMT_12 // ")") "Gaussian spread 1 =", & var_list%get_rval (var_str ("gaussian_spread1")) write (u, "(2x,A,1x," // FMT_12 // ")") "Gaussian spread 2 =", & var_list%get_rval (var_str ("gaussian_spread2")) end if if (beams%contains ("beam_events")) then s = var_list%get_sval (var_str ("$beam_events_file")) write (u, "(2x,A,1x,A)") "Beam events file =", char (s) write (u, "(2x,A,1x,L1)") "Beam events EOF warn =", & var_list%get_lval (var_str ("?beam_events_warn_eof")) end if end associate end subroutine rt_data_show_beams @ %def rt_data_show_beams @ Return the collision energy as determined by the current beam settings. Without beam setup, this is the [[sqrts]] variable. If the value is meaningless for a setup, the function returns zero. <>= procedure :: get_sqrts => rt_data_get_sqrts <>= function rt_data_get_sqrts (rt_data) result (sqrts) class(rt_data_t), intent(in) :: rt_data real(default) :: sqrts sqrts = rt_data%var_list%get_rval (var_str ("sqrts")) end function rt_data_get_sqrts @ %def rt_data_get_sqrts @ For testing purposes, the [[rt_data_t]] contents can be pacified to suppress numerical fluctuations in (constant) test matrix elements. <>= procedure :: pacify => rt_data_pacify <>= subroutine rt_data_pacify (rt_data, efficiency_reset, error_reset) class(rt_data_t), intent(inout) :: rt_data logical, intent(in), optional :: efficiency_reset, error_reset type(process_entry_t), pointer :: process process => rt_data%process_stack%first do while (associated (process)) call process%pacify (efficiency_reset, error_reset) process => process%next end do end subroutine rt_data_pacify @ %def rt_data_pacify @ <>= procedure :: set_event_callback => rt_data_set_event_callback <>= subroutine rt_data_set_event_callback (global, callback) class(rt_data_t), intent(inout) :: global class(event_callback_t), intent(in) :: callback if (allocated (global%event_callback)) deallocate (global%event_callback) allocate (global%event_callback, source = callback) end subroutine rt_data_set_event_callback @ %def rt_data_set_event_callback @ <>= procedure :: has_event_callback => rt_data_has_event_callback procedure :: get_event_callback => rt_data_get_event_callback <>= function rt_data_has_event_callback (global) result (flag) class(rt_data_t), intent(in) :: global logical :: flag flag = allocated (global%event_callback) end function rt_data_has_event_callback function rt_data_get_event_callback (global) result (callback) class(rt_data_t), intent(in) :: global class(event_callback_t), allocatable :: callback if (allocated (global%event_callback)) then allocate (callback, source = global%event_callback) end if end function rt_data_get_event_callback @ %def rt_data_has_event_callback @ %def rt_data_get_event_callback @ Force system-dependent objects to well-defined values. Some of the variables are locked and therefore must be addressed directly. This is, of course, only required for testing purposes. In principle, the [[real_specimen]] variables could be set to their values in [[rt_data_t]], but this depends on the precision again, so we set them to some dummy values. <>= public :: fix_system_dependencies <>= subroutine fix_system_dependencies (global) class(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () call var_list%set_log (var_str ("?omega_openmp"), & .false., is_known = .true., force=.true.) call var_list%set_log (var_str ("?openmp_is_active"), & .false., is_known = .true., force=.true.) call var_list%set_int (var_str ("openmp_num_threads_default"), & 1, is_known = .true., force=.true.) call var_list%set_int (var_str ("openmp_num_threads"), & 1, is_known = .true., force=.true.) call var_list%set_int (var_str ("real_range"), & 307, is_known = .true., force=.true.) call var_list%set_int (var_str ("real_precision"), & 15, is_known = .true., force=.true.) call var_list%set_real (var_str ("real_epsilon"), & 1.e-16_default, is_known = .true., force=.true.) call var_list%set_real (var_str ("real_tiny"), & 1.e-300_default, is_known = .true., force=.true.) global%os_data%fc = "Fortran-compiler" global%os_data%fcflags = "Fortran-flags" global%os_data%fclibs = "Fortran-libs" end subroutine fix_system_dependencies @ %def fix_system_dependencies @ <>= public :: show_description_of_string <>= subroutine show_description_of_string (string) type(string_t), intent(in) :: string type(rt_data_t), target :: global call global%global_init () call global%show_description_of_string (string, ascii_output=.true.) end subroutine show_description_of_string @ %def show_description_of_string @ <>= public :: show_tex_descriptions <>= subroutine show_tex_descriptions () type(rt_data_t), target :: global call global%global_init () call fix_system_dependencies (global) call global%set_int (var_str ("seed"), 0, is_known=.true.) call global%var_list%sort () call global%write_var_descriptions () end subroutine show_tex_descriptions @ %def show_tex_descriptions @ \subsection{Unit Tests} Test module, followed by the corresponding implementation module. <<[[rt_data_ut.f90]]>>= <> module rt_data_ut use unit_tests use rt_data_uti <> <> contains <> end module rt_data_ut @ %def rt_data_ut @ <<[[rt_data_uti.f90]]>>= <> module rt_data_uti <> <> use format_defs, only: FMT_19 use ifiles use lexers use parser use flavors use variables, only: var_list_t, var_entry_t, var_entry_init_int use eval_trees use models use prclib_stacks use rt_data <> <> contains <> <> end module rt_data_uti @ %def rt_data_ut @ API: driver for the unit tests below. <>= public :: rt_data_test <>= subroutine rt_data_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine rt_data_test @ %def rt_data_test @ \subsubsection{Initial content} @ Display the RT data in the state just after (global) initialization. <>= call test (rt_data_1, "rt_data_1", & "initialize", & u, results) <>= public :: rt_data_1 <>= subroutine rt_data_1 (u) integer, intent(in) :: u type(rt_data_t), target :: global write (u, "(A)") "* Test output: rt_data_1" write (u, "(A)") "* Purpose: initialize global runtime data" write (u, "(A)") call global%global_init (logfile = var_str ("rt_data.log")) call fix_system_dependencies (global) call global%set_int (var_str ("seed"), 0, is_known=.true.) call global%it_list%init ([2, 3], [5000, 20000]) call global%write (u) call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_1" end subroutine rt_data_1 @ %def rt_data_1 @ \subsubsection{Fill values} Fill in empty slots in the runtime data block. <>= call test (rt_data_2, "rt_data_2", & "fill", & u, results) <>= public :: rt_data_2 <>= subroutine rt_data_2 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(flavor_t), dimension(2) :: flv type(string_t) :: cut_expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: parse_tree write (u, "(A)") "* Test output: rt_data_2" write (u, "(A)") "* Purpose: initialize global runtime data & &and fill contents" write (u, "(A)") call syntax_model_file_init () call global%global_init () call fix_system_dependencies (global) call global%select_model (var_str ("Test")) call global%set_real (var_str ("sqrts"), & 1000._default, is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call flv%init ([25,25], global%model) call global%set_string (var_str ("$run_id"), & var_str ("run1"), is_known = .true.) call global%set_real (var_str ("luminosity"), & 33._default, is_known = .true.) call syntax_pexpr_init () cut_expr_text = "all Pt > 100 [s]" call ifile_append (ifile, cut_expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (parse_tree, stream, .true.) global%pn%cuts_lexpr => parse_tree%get_root_ptr () allocate (global%sample_fmt (2)) global%sample_fmt(1) = "foo_fmt" global%sample_fmt(2) = "bar_fmt" call global%write (u) call parse_tree_final (parse_tree) call stream_final (stream) call ifile_final (ifile) call syntax_pexpr_final () call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_2" end subroutine rt_data_2 @ %def rt_data_2 @ \subsubsection{Save and restore} Set up a local runtime data block, change some contents, restore the global block. <>= call test (rt_data_3, "rt_data_3", & "save/restore", & u, results) <>= public :: rt_data_3 <>= subroutine rt_data_3 (u) use event_base, only: event_callback_nop_t integer, intent(in) :: u type(rt_data_t), target :: global, local type(flavor_t), dimension(2) :: flv type(string_t) :: cut_expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: parse_tree type(prclib_entry_t), pointer :: lib type(event_callback_nop_t) :: event_callback_nop write (u, "(A)") "* Test output: rt_data_3" write (u, "(A)") "* Purpose: initialize global runtime data & &and fill contents;" write (u, "(A)") "* copy to local block and back" write (u, "(A)") write (u, "(A)") "* Init global data" write (u, "(A)") call syntax_model_file_init () call global%global_init () call fix_system_dependencies (global) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%select_model (var_str ("Test")) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call flv%init ([25,25], global%model) call global%beam_structure%init_sf (flv%get_name (), [1]) call global%beam_structure%set_sf (1, 1, var_str ("pdf_builtin")) call global%set_string (var_str ("$run_id"), & var_str ("run1"), is_known = .true.) call global%set_real (var_str ("luminosity"), & 33._default, is_known = .true.) call syntax_pexpr_init () cut_expr_text = "all Pt > 100 [s]" call ifile_append (ifile, cut_expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (parse_tree, stream, .true.) global%pn%cuts_lexpr => parse_tree%get_root_ptr () allocate (global%sample_fmt (2)) global%sample_fmt(1) = "foo_fmt" global%sample_fmt(2) = "bar_fmt" allocate (lib) call lib%init (var_str ("library_1")) call global%add_prclib (lib) write (u, "(A)") "* Init and modify local data" write (u, "(A)") call local%local_init (global) call local%append_string (var_str ("$integration_method"), intrinsic=.true.) call local%append_string (var_str ("$phs_method"), intrinsic=.true.) call local%activate () write (u, "(1x,A,L1)") "model associated = ", associated (local%model) write (u, "(1x,A,L1)") "library associated = ", associated (local%prclib) write (u, *) call local%model_set_real (var_str ("ms"), 150._default) call local%set_string (var_str ("$integration_method"), & var_str ("midpoint"), is_known = .true.) call local%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) local%os_data%fc = "Local compiler" allocate (lib) call lib%init (var_str ("library_2")) call local%add_prclib (lib) call local%set_event_callback (event_callback_nop) call local%write (u) write (u, "(A)") write (u, "(A)") "* Restore global data" write (u, "(A)") call local%deactivate (global) write (u, "(1x,A,L1)") "model associated = ", associated (global%model) write (u, "(1x,A,L1)") "library associated = ", associated (global%prclib) write (u, *) call global%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call parse_tree_final (parse_tree) call stream_final (stream) call ifile_final (ifile) call syntax_pexpr_final () call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_3" end subroutine rt_data_3 @ %def rt_data_3 @ \subsubsection{Show variables} Display selected variables in the global record. <>= call test (rt_data_4, "rt_data_4", & "show variables", & u, results) <>= public :: rt_data_4 <>= subroutine rt_data_4 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(string_t), dimension(0) :: empty_string_array write (u, "(A)") "* Test output: rt_data_4" write (u, "(A)") "* Purpose: display selected variables" write (u, "(A)") call global%global_init () write (u, "(A)") "* No variables:" write (u, "(A)") call global%write_vars (u, empty_string_array) write (u, "(A)") "* Two variables:" write (u, "(A)") call global%write_vars (u, & [var_str ("?unweighted"), var_str ("$phs_method")]) write (u, "(A)") write (u, "(A)") "* Display whole record with selected variables" write (u, "(A)") call global%write (u, & vars = [var_str ("?unweighted"), var_str ("$phs_method")]) call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_4" end subroutine rt_data_4 @ %def rt_data_4 @ \subsubsection{Show parts} Display only selected parts in the state just after (global) initialization. <>= call test (rt_data_5, "rt_data_5", & "show parts", & u, results) <>= public :: rt_data_5 <>= subroutine rt_data_5 (u) integer, intent(in) :: u type(rt_data_t), target :: global write (u, "(A)") "* Test output: rt_data_5" write (u, "(A)") "* Purpose: display parts of rt data" write (u, "(A)") call global%global_init () call global%write_libraries (u) write (u, "(A)") call global%write_beams (u) write (u, "(A)") call global%write_process_stack (u) call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_5" end subroutine rt_data_5 @ %def rt_data_5 @ \subsubsection{Local Model} Locally modify a model and restore the global one. We need an auxiliary function to determine the status of a model particle: <>= function is_stable (pdg, global) result (flag) integer, intent(in) :: pdg type(rt_data_t), intent(in) :: global logical :: flag type(flavor_t) :: flv call flv%init (pdg, global%model) flag = flv%is_stable () end function is_stable function is_polarized (pdg, global) result (flag) integer, intent(in) :: pdg type(rt_data_t), intent(in) :: global logical :: flag type(flavor_t) :: flv call flv%init (pdg, global%model) flag = flv%is_polarized () end function is_polarized @ %def is_stable is_polarized <>= call test (rt_data_6, "rt_data_6", & "local model", & u, results) <>= public :: rt_data_6 <>= subroutine rt_data_6 (u) integer, intent(in) :: u type(rt_data_t), target :: global, local type(var_list_t), pointer :: model_vars type(string_t) :: var_name write (u, "(A)") "* Test output: rt_data_6" write (u, "(A)") "* Purpose: apply and keep local modifications to model" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%select_model (var_str ("Test")) write (u, "(A)") "* Original model" write (u, "(A)") call global%write_model_list (u) write (u, *) write (u, "(A,L1)") "s is stable = ", is_stable (25, global) write (u, "(A,L1)") "f is polarized = ", is_polarized (6, global) write (u, *) var_name = "ff" write (u, "(A)", advance="no") "Global model variable: " model_vars => global%model%get_var_list_ptr () call model_vars%write_var (var_name, u) write (u, "(A)") write (u, "(A)") "* Apply local modifications: unstable" write (u, "(A)") call local%local_init (global) call local%activate () call local%model_set_real (var_name, 0.4_default) call local%modify_particle (25, stable = .false., decay = [var_str ("d1")]) call local%modify_particle (6, stable = .false., & decay = [var_str ("f1")], isotropic_decay = .true.) call local%modify_particle (-6, stable = .false., & decay = [var_str ("f2"), var_str ("f3")], diagonal_decay = .true.) call local%model%write (u) write (u, "(A)") write (u, "(A)") "* Further modifications" write (u, "(A)") call local%modify_particle (6, stable = .false., & decay = [var_str ("f1")], & diagonal_decay = .true., isotropic_decay = .false.) call local%modify_particle (-6, stable = .false., & decay = [var_str ("f2"), var_str ("f3")], & diagonal_decay = .false., isotropic_decay = .true.) call local%model%write (u) write (u, "(A)") write (u, "(A)") "* Further modifications: f stable but polarized" write (u, "(A)") call local%modify_particle (6, stable = .true., polarized = .true.) call local%modify_particle (-6, stable = .true.) call local%model%write (u) write (u, "(A)") write (u, "(A)") "* Global model" write (u, "(A)") call global%model%write (u) write (u, *) write (u, "(A,L1)") "s is stable = ", is_stable (25, global) write (u, "(A,L1)") "f is polarized = ", is_polarized (6, global) write (u, "(A)") write (u, "(A)") "* Local model" write (u, "(A)") call local%model%write (u) write (u, *) write (u, "(A,L1)") "s is stable = ", is_stable (25, local) write (u, "(A,L1)") "f is polarized = ", is_polarized (6, local) write (u, *) write (u, "(A)", advance="no") "Global model variable: " model_vars => global%model%get_var_list_ptr () call model_vars%write_var (var_name, u) write (u, "(A)", advance="no") "Local model variable: " associate (model_var_list_ptr => local%model%get_var_list_ptr()) call model_var_list_ptr%write_var (var_name, u) end associate write (u, "(A)") write (u, "(A)") "* Restore global" call local%deactivate (global, keep_local = .true.) write (u, "(A)") write (u, "(A)") "* Global model" write (u, "(A)") call global%model%write (u) write (u, *) write (u, "(A,L1)") "s is stable = ", is_stable (25, global) write (u, "(A,L1)") "f is polarized = ", is_polarized (6, global) write (u, "(A)") write (u, "(A)") "* Local model" write (u, "(A)") call local%model%write (u) write (u, *) write (u, "(A,L1)") "s is stable = ", is_stable (25, local) write (u, "(A,L1)") "f is polarized = ", is_polarized (6, local) write (u, *) write (u, "(A)", advance="no") "Global model variable: " model_vars => global%model%get_var_list_ptr () call model_vars%write_var (var_name, u) write (u, "(A)", advance="no") "Local model variable: " associate (model_var_list_ptr => local%model%get_var_list_ptr()) call model_var_list_ptr%write_var (var_name, u) end associate write (u, "(A)") write (u, "(A)") "* Cleanup" call local%model%final () deallocate (local%model) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_6" end subroutine rt_data_6 @ %def rt_data_6 @ \subsubsection{Result variables} Initialize result variables and check that they are accessible via the global variable list. <>= call test (rt_data_7, "rt_data_7", & "result variables", & u, results) <>= public :: rt_data_7 <>= subroutine rt_data_7 (u) integer, intent(in) :: u type(rt_data_t), target :: global write (u, "(A)") "* Test output: rt_data_7" write (u, "(A)") "* Purpose: set and access result variables" write (u, "(A)") write (u, "(A)") "* Initialize process variables" write (u, "(A)") call global%global_init () call global%process_stack%init_result_vars (var_str ("testproc")) call global%var_list%write_var (& var_str ("integral(testproc)"), u, defined=.true.) call global%var_list%write_var (& var_str ("error(testproc)"), u, defined=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_7" end subroutine rt_data_7 @ %def rt_data_7 @ \subsubsection{Beam energy} If beam parameters are set, the variable [[sqrts]] is not necessarily the collision energy. The method [[get_sqrts]] fetches the correct value. <>= call test (rt_data_8, "rt_data_8", & "beam energy", & u, results) <>= public :: rt_data_8 <>= subroutine rt_data_8 (u) integer, intent(in) :: u type(rt_data_t), target :: global write (u, "(A)") "* Test output: rt_data_8" write (u, "(A)") "* Purpose: get correct collision energy" write (u, "(A)") write (u, "(A)") "* Initialize" write (u, "(A)") call global%global_init () write (u, "(A)") "* Set sqrts" write (u, "(A)") call global%set_real (var_str ("sqrts"), & 1000._default, is_known = .true.) write (u, "(1x,A," // FMT_19 // ")") "sqrts =", global%get_sqrts () write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_8" end subroutine rt_data_8 @ %def rt_data_8 @ \subsubsection{Local variable modifications} <>= call test (rt_data_9, "rt_data_9", & "local variables", & u, results) <>= public :: rt_data_9 <>= subroutine rt_data_9 (u) integer, intent(in) :: u type(rt_data_t), target :: global, local type(var_list_t), pointer :: var_list write (u, "(A)") "* Test output: rt_data_9" write (u, "(A)") "* Purpose: handle local variables" write (u, "(A)") call syntax_model_file_init () write (u, "(A)") "* Initialize global record and set some variables" write (u, "(A)") call global%global_init () call global%select_model (var_str ("Test")) call global%set_real (var_str ("sqrts"), 17._default, is_known = .true.) call global%set_real (var_str ("luminosity"), 2._default, is_known = .true.) call global%model_set_real (var_str ("ff"), 0.5_default) call global%model_set_real (var_str ("gy"), 1.2_default) var_list => global%get_var_list_ptr () call var_list%write_var (var_str ("sqrts"), u, defined=.true.) call var_list%write_var (var_str ("luminosity"), u, defined=.true.) call var_list%write_var (var_str ("ff"), u, defined=.true.) call var_list%write_var (var_str ("gy"), u, defined=.true.) call var_list%write_var (var_str ("mf"), u, defined=.true.) call var_list%write_var (var_str ("x"), u, defined=.true.) write (u, "(A)") write (u, "(1x,A,1x,F5.2)") "sqrts = ", & global%get_rval (var_str ("sqrts")) write (u, "(1x,A,1x,F5.2)") "luminosity = ", & global%get_rval (var_str ("luminosity")) write (u, "(1x,A,1x,F5.2)") "ff = ", & global%get_rval (var_str ("ff")) write (u, "(1x,A,1x,F5.2)") "gy = ", & global%get_rval (var_str ("gy")) write (u, "(1x,A,1x,F5.2)") "mf = ", & global%get_rval (var_str ("mf")) write (u, "(1x,A,1x,F5.2)") "x = ", & global%get_rval (var_str ("x")) write (u, "(A)") write (u, "(A)") "* Create local record with local variables" write (u, "(A)") call local%local_init (global) call local%append_real (var_str ("luminosity"), intrinsic = .true.) call local%append_real (var_str ("x"), user = .true.) call local%activate () var_list => local%get_var_list_ptr () call var_list%write_var (var_str ("sqrts"), u) call var_list%write_var (var_str ("luminosity"), u) call var_list%write_var (var_str ("ff"), u) call var_list%write_var (var_str ("gy"), u) call var_list%write_var (var_str ("mf"), u) call var_list%write_var (var_str ("x"), u, defined=.true.) write (u, "(A)") write (u, "(1x,A,1x,F5.2)") "sqrts = ", & local%get_rval (var_str ("sqrts")) write (u, "(1x,A,1x,F5.2)") "luminosity = ", & local%get_rval (var_str ("luminosity")) write (u, "(1x,A,1x,F5.2)") "ff = ", & local%get_rval (var_str ("ff")) write (u, "(1x,A,1x,F5.2)") "gy = ", & local%get_rval (var_str ("gy")) write (u, "(1x,A,1x,F5.2)") "mf = ", & local%get_rval (var_str ("mf")) write (u, "(1x,A,1x,F5.2)") "x = ", & local%get_rval (var_str ("x")) write (u, "(A)") write (u, "(A)") "* Modify some local variables" write (u, "(A)") call local%set_real (var_str ("luminosity"), 42._default, is_known=.true.) call local%set_real (var_str ("x"), 6.66_default, is_known=.true.) call local%model_set_real (var_str ("ff"), 0.7_default) var_list => local%get_var_list_ptr () call var_list%write_var (var_str ("sqrts"), u) call var_list%write_var (var_str ("luminosity"), u) call var_list%write_var (var_str ("ff"), u) call var_list%write_var (var_str ("gy"), u) call var_list%write_var (var_str ("mf"), u) call var_list%write_var (var_str ("x"), u, defined=.true.) write (u, "(A)") write (u, "(1x,A,1x,F5.2)") "sqrts = ", & local%get_rval (var_str ("sqrts")) write (u, "(1x,A,1x,F5.2)") "luminosity = ", & local%get_rval (var_str ("luminosity")) write (u, "(1x,A,1x,F5.2)") "ff = ", & local%get_rval (var_str ("ff")) write (u, "(1x,A,1x,F5.2)") "gy = ", & local%get_rval (var_str ("gy")) write (u, "(1x,A,1x,F5.2)") "mf = ", & local%get_rval (var_str ("mf")) write (u, "(1x,A,1x,F5.2)") "x = ", & local%get_rval (var_str ("x")) write (u, "(A)") write (u, "(A)") "* Restore globals" write (u, "(A)") call local%deactivate (global) var_list => global%get_var_list_ptr () call var_list%write_var (var_str ("sqrts"), u) call var_list%write_var (var_str ("luminosity"), u) call var_list%write_var (var_str ("ff"), u) call var_list%write_var (var_str ("gy"), u) call var_list%write_var (var_str ("mf"), u) call var_list%write_var (var_str ("x"), u, defined=.true.) write (u, "(A)") write (u, "(1x,A,1x,F5.2)") "sqrts = ", & global%get_rval (var_str ("sqrts")) write (u, "(1x,A,1x,F5.2)") "luminosity = ", & global%get_rval (var_str ("luminosity")) write (u, "(1x,A,1x,F5.2)") "ff = ", & global%get_rval (var_str ("ff")) write (u, "(1x,A,1x,F5.2)") "gy = ", & global%get_rval (var_str ("gy")) write (u, "(1x,A,1x,F5.2)") "mf = ", & global%get_rval (var_str ("mf")) write (u, "(1x,A,1x,F5.2)") "x = ", & global%get_rval (var_str ("x")) write (u, "(A)") write (u, "(A)") "* Cleanup" call local%local_final () call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_9" end subroutine rt_data_9 @ %def rt_data_9 @ \subsubsection{Descriptions} <>= call test(rt_data_10, "rt_data_10", & "descriptions", u, results) <>= public :: rt_data_10 <>= subroutine rt_data_10 (u) integer, intent(in) :: u type(rt_data_t) :: global ! type(var_list_t) :: var_list write (u, "(A)") "* Test output: rt_data_10" write (u, "(A)") "* Purpose: display descriptions" write (u, "(A)") call global%var_list%append_real (var_str ("sqrts"), & intrinsic=.true., & description=var_str ('Real variable in order to set the center-of-mass ' // & 'energy for the collisions.')) call global%var_list%append_real (var_str ("luminosity"), 0._default, & intrinsic=.true., & description=var_str ('This specifier \ttt{luminosity = {\em ' // & '}} sets the integrated luminosity (in inverse femtobarns, ' // & 'fb${}^{-1}$) for the event generation of the processes in the ' // & '\sindarin\ input files.')) call global%var_list%append_int (var_str ("seed"), 1234, & intrinsic=.true., & description=var_str ('Integer variable \ttt{seed = {\em }} ' // & 'that allows to set a specific random seed \ttt{num}.')) call global%var_list%append_string (var_str ("$method"), var_str ("omega"), & intrinsic=.true., & description=var_str ('This string variable specifies the method ' // & 'for the matrix elements to be used in the evaluation.')) call global%var_list%append_log (var_str ("?read_color_factors"), .true., & intrinsic=.true., & description=var_str ('This flag decides whether to read QCD ' // & 'color factors from the matrix element provided by each method, ' // & 'or to try and calculate the color factors in \whizard\ internally.')) call global%var_list%sort () call global%write_var_descriptions (u) call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_10" end subroutine rt_data_10 @ %def rt_data_10 @ \subsubsection{Export objects} Export objects are variables or other data that should be copied or otherwise applied to corresponding objects in the outer scope. We test appending and retrieval for the export list. <>= call test(rt_data_11, "rt_data_11", & "export objects", u, results) <>= public :: rt_data_11 <>= subroutine rt_data_11 (u) integer, intent(in) :: u type(rt_data_t) :: global type(string_t), dimension(:), allocatable :: exports integer :: i write (u, "(A)") "* Test output: rt_data_11" write (u, "(A)") "* Purpose: handle export object list" write (u, "(A)") write (u, "(A)") "* Empty export list" write (u, "(A)") call global%write_exports (u) write (u, "(A)") "* Add an entry" write (u, "(A)") allocate (exports (1)) exports(1) = var_str ("results") do i = 1, size (exports) write (u, "('+ ',A)") char (exports(i)) end do write (u, *) call global%append_exports (exports) call global%write_exports (u) write (u, "(A)") write (u, "(A)") "* Add more entries, including doubler" write (u, "(A)") deallocate (exports) allocate (exports (3)) exports(1) = var_str ("foo") exports(2) = var_str ("results") exports(3) = var_str ("bar") do i = 1, size (exports) write (u, "('+ ',A)") char (exports(i)) end do write (u, *) call global%append_exports (exports) call global%write_exports (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: rt_data_11" end subroutine rt_data_11 @ %def rt_data_11 @ @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Select implementations} For abstract types (process core, integrator, phase space, etc.), we need a way to dynamically select a concrete type, using either data given by the user or a previous selection of a concrete type. This is done by subroutines in the current module. We would like to put this in the [[me_methods]] folder but it also depends on [[gosam]] and [[openloops]], so it is unclear where to put it. <<[[dispatch_me_methods.f90]]>>= <> module dispatch_me_methods <> <> use physics_defs, only: BORN use diagnostics use sm_qcd use variables, only: var_list_t use models use model_data use prc_core_def use prc_core use prc_test_core use prc_template_me use prc_test use prc_omega use prc_external use prc_gosam use prc_openloops use prc_recola use prc_threshold <> <> contains <> end module dispatch_me_methods @ %def dispatch_me_methods @ \subsection{Process Core Definition} The [[prc_core_def_t]] abstract type can be instantiated by providing a [[$method]] string variable. <>= public :: dispatch_core_def <>= subroutine dispatch_core_def (core_def, prt_in, prt_out, & model, var_list, id, nlo_type, method) class(prc_core_def_t), allocatable, intent(out) :: core_def type(string_t), dimension(:), intent(in) :: prt_in type(string_t), dimension(:), intent(in) :: prt_out type(model_t), pointer, intent(in) :: model type(var_list_t), intent(in) :: var_list type(string_t), intent(in), optional :: id integer, intent(in), optional :: nlo_type type(string_t), intent(in), optional :: method type(string_t) :: model_name, meth type(string_t) :: ufo_path type(string_t) :: restrictions logical :: ufo logical :: cms_scheme logical :: openmp_support logical :: report_progress logical :: diags, diags_color logical :: write_phs_output type(string_t) :: extra_options, correction_type integer :: nlo integer :: alpha_power integer :: alphas_power if (present (method)) then meth = method else meth = var_list%get_sval (var_str ("$method")) end if if (debug_on) call msg_debug2 (D_CORE, "dispatch_core_def") if (associated (model)) then model_name = model%get_name () cms_scheme = model%get_scheme () == "Complex_Mass_Scheme" ufo = model%is_ufo_model () ufo_path = model%get_ufo_path () else model_name = "" cms_scheme = .false. ufo = .false. end if restrictions = var_list%get_sval (& var_str ("$restrictions")) diags = var_list%get_lval (& var_str ("?vis_diags")) diags_color = var_list%get_lval (& var_str ("?vis_diags_color")) openmp_support = var_list%get_lval (& var_str ("?omega_openmp")) report_progress = var_list%get_lval (& var_str ("?report_progress")) write_phs_output = var_list%get_lval (& var_str ("?omega_write_phs_output")) extra_options = var_list%get_sval (& var_str ("$omega_flags")) nlo = BORN; if (present (nlo_type)) nlo = nlo_type alpha_power = var_list%get_ival (var_str ("alpha_power")) alphas_power = var_list%get_ival (var_str ("alphas_power")) correction_type = var_list%get_sval (var_str ("$nlo_correction_type")) if (debug_on) call msg_debug2 (D_CORE, "dispatching core method: ", meth) select case (char (meth)) case ("unit_test") allocate (prc_test_def_t :: core_def) select type (core_def) type is (prc_test_def_t) call core_def%init (model_name, prt_in, prt_out) end select case ("template") allocate (template_me_def_t :: core_def) select type (core_def) type is (template_me_def_t) call core_def%init (model, prt_in, prt_out, unity = .false.) end select case ("template_unity") allocate (template_me_def_t :: core_def) select type (core_def) type is (template_me_def_t) call core_def%init (model, prt_in, prt_out, unity = .true.) end select case ("omega") allocate (omega_def_t :: core_def) select type (core_def) type is (omega_def_t) call core_def%init (model_name, prt_in, prt_out, & .false., ufo, ufo_path, & restrictions, cms_scheme, & openmp_support, report_progress, write_phs_output, & extra_options, diags, diags_color) end select case ("ovm") allocate (omega_def_t :: core_def) select type (core_def) type is (omega_def_t) call core_def%init (model_name, prt_in, prt_out, & .true., .false., var_str (""), & restrictions, cms_scheme, & openmp_support, report_progress, write_phs_output, & extra_options, diags, diags_color) end select case ("gosam") allocate (gosam_def_t :: core_def) select type (core_def) type is (gosam_def_t) if (present (id)) then call core_def%init (id, model_name, prt_in, & prt_out, nlo, restrictions, var_list) else call msg_fatal ("Dispatch GoSam def: No id!") end if end select case ("openloops") allocate (openloops_def_t :: core_def) select type (core_def) type is (openloops_def_t) if (present (id)) then call core_def%init (id, model_name, prt_in, & prt_out, nlo, restrictions, var_list) else call msg_fatal ("Dispatch OpenLoops def: No id!") end if end select case ("recola") call abort_if_recola_not_active () allocate (recola_def_t :: core_def) select type (core_def) type is (recola_def_t) if (present (id)) then call core_def%init (id, model_name, prt_in, prt_out, & nlo, alpha_power, alphas_power, correction_type, & restrictions) else call msg_fatal ("Dispatch RECOLA def: No id!") end if end select case ("dummy") allocate (prc_external_test_def_t :: core_def) select type (core_def) type is (prc_external_test_def_t) if (present (id)) then call core_def%init (id, model_name, prt_in, prt_out) else call msg_fatal ("Dispatch User-Defined Test def: No id!") end if end select case ("threshold") allocate (threshold_def_t :: core_def) select type (core_def) type is (threshold_def_t) if (present (id)) then call core_def%init (id, model_name, prt_in, prt_out, & nlo, restrictions) else call msg_fatal ("Dispatch Threshold def: No id!") end if end select case default call msg_fatal ("Process configuration: method '" & // char (meth) // "' not implemented") end select end subroutine dispatch_core_def @ %def dispatch_core_def @ \subsection{Process core allocation} Here we allocate an object of abstract type [[prc_core_t]] with a concrete type that matches a process definition. The [[prc_omega_t]] extension will require the current parameter set, so we take the opportunity to grab it from the model. <>= public :: dispatch_core <>= subroutine dispatch_core (core, core_def, model, & helicity_selection, qcd, use_color_factors, has_beam_pol) class(prc_core_t), allocatable, intent(inout) :: core class(prc_core_def_t), intent(in) :: core_def class(model_data_t), intent(in), target, optional :: model type(helicity_selection_t), intent(in), optional :: helicity_selection type(qcd_t), intent(in), optional :: qcd logical, intent(in), optional :: use_color_factors logical, intent(in), optional :: has_beam_pol select type (core_def) type is (prc_test_def_t) allocate (test_t :: core) type is (template_me_def_t) allocate (prc_template_me_t :: core) select type (core) type is (prc_template_me_t) call core%set_parameters (model) end select class is (omega_def_t) if (.not. allocated (core)) allocate (prc_omega_t :: core) select type (core) type is (prc_omega_t) call core%set_parameters (model, & helicity_selection, qcd, use_color_factors) end select type is (gosam_def_t) if (.not. allocated (core)) allocate (prc_gosam_t :: core) select type (core) type is (prc_gosam_t) call core%set_parameters (qcd) end select type is (openloops_def_t) if (.not. allocated (core)) allocate (prc_openloops_t :: core) select type (core) type is (prc_openloops_t) call core%set_parameters (qcd) end select type is (recola_def_t) if (.not. allocated (core)) allocate (prc_recola_t :: core) select type (core) type is (prc_recola_t) call core%set_parameters (qcd, model) end select type is (prc_external_test_def_t) if (.not. allocated (core)) allocate (prc_external_test_t :: core) select type (core) type is (prc_external_test_t) call core%set_parameters (qcd, model) end select type is (threshold_def_t) if (.not. allocated (core)) allocate (prc_threshold_t :: core) select type (core) type is (prc_threshold_t) call core%set_parameters (qcd, model) call core%set_beam_pol (has_beam_pol) end select class default call msg_bug ("Process core: unexpected process definition type") end select end subroutine dispatch_core @ %def dispatch_core @ \subsection{Process core update and restoration} Here we take an existing object of abstract type [[prc_core_t]] and update the parameters as given by the current state of [[model]]. Optionally, we can save the previous state as [[saved_core]]. The second routine restores the original from the save. (In the test case, there is no possible update.) <>= public :: dispatch_core_update public :: dispatch_core_restore <>= subroutine dispatch_core_update & (core, model, helicity_selection, qcd, saved_core) class(prc_core_t), allocatable, intent(inout) :: core class(model_data_t), intent(in), optional, target :: model type(helicity_selection_t), intent(in), optional :: helicity_selection type(qcd_t), intent(in), optional :: qcd class(prc_core_t), allocatable, intent(inout), optional :: saved_core if (present (saved_core)) then allocate (saved_core, source = core) end if select type (core) type is (test_t) type is (prc_omega_t) call core%set_parameters (model, helicity_selection, qcd) call core%activate_parameters () class is (prc_external_t) call msg_message ("Updating user defined cores is not implemented yet.") class default call msg_bug ("Process core update: unexpected process definition type") end select end subroutine dispatch_core_update subroutine dispatch_core_restore (core, saved_core) class(prc_core_t), allocatable, intent(inout) :: core class(prc_core_t), allocatable, intent(inout) :: saved_core call move_alloc (from = saved_core, to = core) select type (core) type is (test_t) type is (prc_omega_t) call core%activate_parameters () class default call msg_bug ("Process core restore: unexpected process definition type") end select end subroutine dispatch_core_restore @ %def dispatch_core_update dispatch_core_restore @ \subsection{Unit Tests} Test module, followed by the corresponding implementation module. <<[[dispatch_ut.f90]]>>= <> module dispatch_ut use unit_tests use dispatch_uti <> <> <> contains <> end module dispatch_ut @ %def dispatch_ut @ <<[[dispatch_uti.f90]]>>= <> module dispatch_uti <> <> use os_interface, only: os_data_t use physics_defs, only: ELECTRON, PROTON use sm_qcd, only: qcd_t use flavors, only: flavor_t use interactions, only: reset_interaction_counter use pdg_arrays, only: pdg_array_t, assignment(=) use prc_core_def, only: prc_core_def_t use prc_test_core, only: test_t use prc_core, only: prc_core_t use prc_test, only: prc_test_def_t use prc_omega, only: omega_def_t, prc_omega_t use sf_mappings, only: sf_channel_t use sf_base, only: sf_data_t, sf_config_t use phs_base, only: phs_channel_collection_t use variables, only: var_list_t use model_data, only: model_data_t use models, only: syntax_model_file_init, syntax_model_file_final use rt_data, only: rt_data_t use dispatch_phase_space, only: dispatch_sf_channels use dispatch_beams, only: sf_prop_t, dispatch_qcd use dispatch_beams, only: dispatch_sf_config, dispatch_sf_data use dispatch_me_methods, only: dispatch_core_def, dispatch_core use dispatch_me_methods, only: dispatch_core_update, dispatch_core_restore use sf_base_ut, only: sf_test_data_t <> <> <> contains <> <> end module dispatch_uti @ %def dispatch_uti @ API: driver for the unit tests below. <>= public :: dispatch_test <>= subroutine dispatch_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine dispatch_test @ %def dispatch_test @ \subsubsection{Select type: process definition} <>= call test (dispatch_1, "dispatch_1", & "process configuration method", & u, results) <>= public :: dispatch_1 <>= subroutine dispatch_1 (u) integer, intent(in) :: u type(string_t), dimension(2) :: prt_in, prt_out type(rt_data_t), target :: global class(prc_core_def_t), allocatable :: core_def write (u, "(A)") "* Test output: dispatch_1" write (u, "(A)") "* Purpose: select process configuration method" write (u, "(A)") call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) prt_in = [var_str ("a"), var_str ("b")] prt_out = [var_str ("c"), var_str ("d")] write (u, "(A)") "* Allocate core_def as prc_test_def" call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call dispatch_core_def (core_def, prt_in, prt_out, global%model, global%var_list) select type (core_def) type is (prc_test_def_t) call core_def%write (u) end select deallocate (core_def) write (u, "(A)") write (u, "(A)") "* Allocate core_def as omega_def" write (u, "(A)") call global%set_string (var_str ("$method"), & var_str ("omega"), is_known = .true.) call dispatch_core_def (core_def, prt_in, prt_out, global%model, global%var_list) select type (core_def) type is (omega_def_t) call core_def%write (u) end select call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: dispatch_1" end subroutine dispatch_1 @ %def dispatch_1 @ \subsubsection{Select type: process core} <>= call test (dispatch_2, "dispatch_2", & "process core", & u, results) <>= public :: dispatch_2 <>= subroutine dispatch_2 (u) integer, intent(in) :: u type(string_t), dimension(2) :: prt_in, prt_out type(rt_data_t), target :: global class(prc_core_def_t), allocatable :: core_def class(prc_core_t), allocatable :: core write (u, "(A)") "* Test output: dispatch_2" write (u, "(A)") "* Purpose: select process configuration method" write (u, "(A)") " and allocate process core" write (u, "(A)") call syntax_model_file_init () call global%global_init () prt_in = [var_str ("a"), var_str ("b")] prt_out = [var_str ("c"), var_str ("d")] write (u, "(A)") "* Allocate core as test_t" write (u, "(A)") call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call dispatch_core_def (core_def, prt_in, prt_out, global%model, global%var_list) call dispatch_core (core, core_def) select type (core) type is (test_t) call core%write (u) end select deallocate (core) deallocate (core_def) write (u, "(A)") write (u, "(A)") "* Allocate core as prc_omega_t" write (u, "(A)") call global%set_string (var_str ("$method"), & var_str ("omega"), is_known = .true.) call dispatch_core_def (core_def, prt_in, prt_out, global%model, global%var_list) call global%select_model (var_str ("Test")) call global%set_log (& var_str ("?helicity_selection_active"), & .true., is_known = .true.) call global%set_real (& var_str ("helicity_selection_threshold"), & 1e9_default, is_known = .true.) call global%set_int (& var_str ("helicity_selection_cutoff"), & 10, is_known = .true.) call dispatch_core (core, core_def, & global%model, & global%get_helicity_selection ()) call core_def%allocate_driver (core%driver, var_str ("")) select type (core) type is (prc_omega_t) call core%write (u) end select call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: dispatch_2" end subroutine dispatch_2 @ %def dispatch_2 @ \subsubsection{Select type: structure-function data} This is an extra dispatcher that enables the test structure functions. This procedure should be assigned to the [[dispatch_sf_data_extra]] hook before any tests are executed. <>= public :: dispatch_sf_data_test <>= subroutine dispatch_sf_data_test (data, sf_method, i_beam, sf_prop, & var_list, var_list_global, model, os_data, sqrts, pdg_in, pdg_prc, polarized) class(sf_data_t), allocatable, intent(inout) :: data type(string_t), intent(in) :: sf_method integer, dimension(:), intent(in) :: i_beam type(var_list_t), intent(in) :: var_list type(var_list_t), intent(inout) :: var_list_global class(model_data_t), target, intent(in) :: model type(os_data_t), intent(in) :: os_data real(default), intent(in) :: sqrts type(pdg_array_t), dimension(:), intent(inout) :: pdg_in type(pdg_array_t), dimension(:,:), intent(in) :: pdg_prc type(sf_prop_t), intent(inout) :: sf_prop logical, intent(in) :: polarized select case (char (sf_method)) case ("sf_test_0", "sf_test_1") allocate (sf_test_data_t :: data) select type (data) type is (sf_test_data_t) select case (char (sf_method)) case ("sf_test_0"); call data%init (model, pdg_in(i_beam(1))) case ("sf_test_1"); call data%init (model, pdg_in(i_beam(1)),& mode = 1) end select end select end select end subroutine dispatch_sf_data_test @ %def dispatch_sf_data_test @ The actual test. We can't move this to [[beams]] as it depends on [[model_features]] for the [[model_list_t]]. <>= call test (dispatch_7, "dispatch_7", & "structure-function data", & u, results) <>= public :: dispatch_7 <>= subroutine dispatch_7 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(os_data_t) :: os_data type(string_t) :: prt, sf_method type(sf_prop_t) :: sf_prop class(sf_data_t), allocatable :: data type(pdg_array_t), dimension(1) :: pdg_in type(pdg_array_t), dimension(1,1) :: pdg_prc type(pdg_array_t), dimension(1) :: pdg_out integer, dimension(:), allocatable :: pdg1 write (u, "(A)") "* Test output: dispatch_7" write (u, "(A)") "* Purpose: select and configure & &structure function data" write (u, "(A)") call global%global_init () call os_data%init () call syntax_model_file_init () call global%select_model (var_str ("QCD")) call reset_interaction_counter () call global%set_real (var_str ("sqrts"), & 14000._default, is_known = .true.) prt = "p" call global%beam_structure%init_sf ([prt, prt], [1]) pdg_in = 2212 write (u, "(A)") "* Allocate data as sf_pdf_builtin_t" write (u, "(A)") sf_method = "pdf_builtin" call dispatch_sf_data (data, sf_method, [1], sf_prop, & global%get_var_list_ptr (), global%var_list, & global%model, global%os_data, global%get_sqrts (), & pdg_in, pdg_prc, .false.) call data%write (u) call data%get_pdg_out (pdg_out) pdg1 = pdg_out(1) write (u, "(A)") write (u, "(1x,A,99(1x,I0))") "PDG(out) = ", pdg1 deallocate (data) write (u, "(A)") write (u, "(A)") "* Allocate data for different PDF set" write (u, "(A)") pdg_in = 2212 call global%set_string (var_str ("$pdf_builtin_set"), & var_str ("CTEQ6M"), is_known = .true.) sf_method = "pdf_builtin" call dispatch_sf_data (data, sf_method, [1], sf_prop, & global%get_var_list_ptr (), global%var_list, & global%model, global%os_data, global%get_sqrts (), & pdg_in, pdg_prc, .false.) call data%write (u) call data%get_pdg_out (pdg_out) pdg1 = pdg_out(1) write (u, "(A)") write (u, "(1x,A,99(1x,I0))") "PDG(out) = ", pdg1 deallocate (data) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: dispatch_7" end subroutine dispatch_7 @ %def dispatch_7 @ \subsubsection{Beam structure} The actual test. We can't move this to [[beams]] as it depends on [[model_features]] for the [[model_list_t]]. <>= call test (dispatch_8, "dispatch_8", & "beam structure", & u, results) <>= public :: dispatch_8 <>= subroutine dispatch_8 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(os_data_t) :: os_data type(flavor_t), dimension(2) :: flv type(sf_config_t), dimension(:), allocatable :: sf_config type(sf_prop_t) :: sf_prop type(sf_channel_t), dimension(:), allocatable :: sf_channel type(phs_channel_collection_t) :: coll type(string_t) :: sf_string integer :: i type(pdg_array_t), dimension (2,1) :: pdg_prc write (u, "(A)") "* Test output: dispatch_8" write (u, "(A)") "* Purpose: configure a structure-function chain" write (u, "(A)") call global%global_init () call os_data%init () call syntax_model_file_init () call global%select_model (var_str ("QCD")) write (u, "(A)") "* Allocate LHC beams with PDF builtin" write (u, "(A)") call flv(1)%init (PROTON, global%model) call flv(2)%init (PROTON, global%model) call reset_interaction_counter () call global%set_real (var_str ("sqrts"), & 14000._default, is_known = .true.) call global%beam_structure%init_sf (flv%get_name (), [1]) call global%beam_structure%set_sf (1, 1, var_str ("pdf_builtin")) call dispatch_sf_config (sf_config, sf_prop, global%beam_structure, & global%get_var_list_ptr (), global%var_list, & global%model, global%os_data, global%get_sqrts (), pdg_prc) do i = 1, size (sf_config) call sf_config(i)%write (u) end do call dispatch_sf_channels (sf_channel, sf_string, sf_prop, coll, & global%var_list, global%get_sqrts(), global%beam_structure) write (u, "(1x,A)") "Mapping configuration:" do i = 1, size (sf_channel) write (u, "(2x)", advance = "no") call sf_channel(i)%write (u) end do write (u, "(A)") write (u, "(A)") "* Allocate ILC beams with CIRCE1" write (u, "(A)") call global%select_model (var_str ("QED")) call flv(1)%init ( ELECTRON, global%model) call flv(2)%init (-ELECTRON, global%model) call reset_interaction_counter () call global%set_real (var_str ("sqrts"), & 500._default, is_known = .true.) call global%set_log (var_str ("?circe1_generate"), & .false., is_known = .true.) call global%beam_structure%init_sf (flv%get_name (), [1]) call global%beam_structure%set_sf (1, 1, var_str ("circe1")) call dispatch_sf_config (sf_config, sf_prop, global%beam_structure, & global%get_var_list_ptr (), global%var_list, & global%model, global%os_data, global%get_sqrts (), pdg_prc) do i = 1, size (sf_config) call sf_config(i)%write (u) end do call dispatch_sf_channels (sf_channel, sf_string, sf_prop, coll, & global%var_list, global%get_sqrts(), global%beam_structure) write (u, "(1x,A)") "Mapping configuration:" do i = 1, size (sf_channel) write (u, "(2x)", advance = "no") call sf_channel(i)%write (u) end do write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: dispatch_8" end subroutine dispatch_8 @ %def dispatch_8 @ \subsubsection{Update process core parameters} This test dispatches a process core, temporarily modifies parameters, then restores the original. <>= call test (dispatch_10, "dispatch_10", & "process core update", & u, results) <>= public :: dispatch_10 <>= subroutine dispatch_10 (u) integer, intent(in) :: u type(string_t), dimension(2) :: prt_in, prt_out type(rt_data_t), target :: global class(prc_core_def_t), allocatable :: core_def class(prc_core_t), allocatable :: core, saved_core type(var_list_t), pointer :: model_vars write (u, "(A)") "* Test output: dispatch_10" write (u, "(A)") "* Purpose: select process configuration method," write (u, "(A)") " allocate process core," write (u, "(A)") " temporarily reset parameters" write (u, "(A)") call syntax_model_file_init () call global%global_init () prt_in = [var_str ("a"), var_str ("b")] prt_out = [var_str ("c"), var_str ("d")] write (u, "(A)") "* Allocate core as prc_omega_t" write (u, "(A)") call global%set_string (var_str ("$method"), & var_str ("omega"), is_known = .true.) call dispatch_core_def (core_def, prt_in, prt_out, global%model, global%var_list) call global%select_model (var_str ("Test")) call dispatch_core (core, core_def, global%model) call core_def%allocate_driver (core%driver, var_str ("")) select type (core) type is (prc_omega_t) call core%write (u) end select write (u, "(A)") write (u, "(A)") "* Update core with modified model and helicity selection" write (u, "(A)") model_vars => global%model%get_var_list_ptr () call model_vars%set_real (var_str ("gy"), 2._default, & is_known = .true.) call global%model%update_parameters () call global%set_log (& var_str ("?helicity_selection_active"), & .true., is_known = .true.) call global%set_real (& var_str ("helicity_selection_threshold"), & 2e10_default, is_known = .true.) call global%set_int (& var_str ("helicity_selection_cutoff"), & 5, is_known = .true.) call dispatch_core_update (core, & global%model, & global%get_helicity_selection (), & saved_core = saved_core) select type (core) type is (prc_omega_t) call core%write (u) end select write (u, "(A)") write (u, "(A)") "* Restore core from save" write (u, "(A)") call dispatch_core_restore (core, saved_core) select type (core) type is (prc_omega_t) call core%write (u) end select call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: dispatch_10" end subroutine dispatch_10 @ %def dispatch_10 @ \subsubsection{QCD Coupling} This test dispatches an [[qcd]] object, which is used to compute the (running) coupling by one of several possible methods. We can't move this to [[beams]] as it depends on [[model_features]] for the [[model_list_t]]. <>= call test (dispatch_11, "dispatch_11", & "QCD coupling", & u, results) <>= public :: dispatch_11 <>= subroutine dispatch_11 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(var_list_t), pointer :: model_vars type(qcd_t) :: qcd write (u, "(A)") "* Test output: dispatch_11" write (u, "(A)") "* Purpose: select QCD coupling formula" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%select_model (var_str ("SM")) model_vars => global%get_var_list_ptr () write (u, "(A)") "* Allocate alpha_s as fixed" write (u, "(A)") call global%set_log (var_str ("?alphas_is_fixed"), & .true., is_known = .true.) call dispatch_qcd (qcd, global%get_var_list_ptr (), global%os_data) call qcd%write (u) write (u, "(A)") write (u, "(A)") "* Allocate alpha_s as running (built-in)" write (u, "(A)") call global%set_log (var_str ("?alphas_is_fixed"), & .false., is_known = .true.) call global%set_log (var_str ("?alphas_from_mz"), & .true., is_known = .true.) call global%set_int & (var_str ("alphas_order"), 1, is_known = .true.) call model_vars%set_real (var_str ("alphas"), 0.1234_default, & is_known=.true.) call model_vars%set_real (var_str ("mZ"), 91.234_default, & is_known=.true.) call dispatch_qcd (qcd, global%get_var_list_ptr (), global%os_data) call qcd%write (u) write (u, "(A)") write (u, "(A)") "* Allocate alpha_s as running (built-in, Lambda defined)" write (u, "(A)") call global%set_log (var_str ("?alphas_from_mz"), & .false., is_known = .true.) call global%set_log (& var_str ("?alphas_from_lambda_qcd"), & .true., is_known = .true.) call global%set_real & (var_str ("lambda_qcd"), 250.e-3_default, & is_known=.true.) call global%set_int & (var_str ("alphas_order"), 2, is_known = .true.) call global%set_int & (var_str ("alphas_nf"), 4, is_known = .true.) call dispatch_qcd (qcd, global%get_var_list_ptr (), global%os_data) call qcd%write (u) write (u, "(A)") write (u, "(A)") "* Allocate alpha_s as running (using builtin PDF set)" write (u, "(A)") call global%set_log (& var_str ("?alphas_from_lambda_qcd"), & .false., is_known = .true.) call global%set_log & (var_str ("?alphas_from_pdf_builtin"), & .true., is_known = .true.) call dispatch_qcd (qcd, global%get_var_list_ptr (), global%os_data) call qcd%write (u) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: dispatch_11" end subroutine dispatch_11 @ %def dispatch_11 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process Configuration} This module communicates between the toplevel command structure with its runtime data set and the process-library handling modules which collect the definition of individual processes. Its primary purpose is to select from the available matrix-element generating methods and configure the entry in the process library accordingly. <<[[process_configurations.f90]]>>= <> module process_configurations <> <> use diagnostics use io_units use physics_defs, only: BORN, NLO_VIRTUAL, NLO_REAL, NLO_DGLAP, & NLO_SUBTRACTION, NLO_MISMATCH use models use prc_core_def use particle_specifiers use process_libraries use rt_data use variables, only: var_list_t use dispatch_me_methods, only: dispatch_core_def use prc_external, only: prc_external_def_t <> <> <> contains <> end module process_configurations @ %def process_configurations @ \subsection{Data Type} <>= public :: process_configuration_t <>= type :: process_configuration_t type(process_def_entry_t), pointer :: entry => null () type(string_t) :: id integer :: num_id = 0 contains <> end type process_configuration_t @ %def process_configuration_t @ Output (for unit tests). <>= procedure :: write => process_configuration_write <>= subroutine process_configuration_write (config, unit) class(process_configuration_t), intent(in) :: config integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) write (u, "(A)") "Process configuration:" if (associated (config%entry)) then call config%entry%write (u) else write (u, "(1x,3A)") "ID = '", char (config%id), "'" write (u, "(1x,A,1x,I0)") "num ID =", config%num_id write (u, "(2x,A)") "[no entry]" end if end subroutine process_configuration_write @ %def process_configuration_write @ Initialize a process. We only need the name, the number of incoming particles, and the number of components. <>= procedure :: init => process_configuration_init <>= subroutine process_configuration_init & (config, prc_name, n_in, n_components, model, var_list, & nlo_process, negative_sf) class(process_configuration_t), intent(out) :: config type(string_t), intent(in) :: prc_name integer, intent(in) :: n_in integer, intent(in) :: n_components type(model_t), intent(in), pointer :: model type(var_list_t), intent(in) :: var_list logical, intent(in), optional :: nlo_process, negative_sf logical :: nlo_proc, neg_sf logical :: requires_resonances if (debug_on) call msg_debug (D_CORE, "process_configuration_init") config%id = prc_name if (present (nlo_process)) then nlo_proc = nlo_process else nlo_proc = .false. end if if (present (negative_sf)) then neg_sf = negative_sf else neg_sf = .false. end if requires_resonances = var_list%get_lval (var_str ("?resonance_history")) if (debug_on) call msg_debug (D_CORE, "nlo_process", nlo_proc) allocate (config%entry) if (var_list%is_known (var_str ("process_num_id"))) then config%num_id = & var_list%get_ival (var_str ("process_num_id")) call config%entry%init (prc_name, & model = model, n_in = n_in, n_components = n_components, & num_id = config%num_id, & nlo_process = nlo_proc, & negative_sf = neg_sf, & requires_resonances = requires_resonances) else call config%entry%init (prc_name, & model = model, n_in = n_in, n_components = n_components, & nlo_process = nlo_proc, & negative_sf = neg_sf, & requires_resonances = requires_resonances) end if end subroutine process_configuration_init @ %def process_configuration_init @ Initialize a process component. The details depend on the process method, which determines the type of the process component core. We set the incoming and outgoing particles (as strings, to be interpreted by the process driver). All other information is taken from the variable list. The dispatcher gets only the names of the particles. The process component definition gets the complete specifiers which contains a polarization flag and names of decay processes, where applicable. <>= procedure :: setup_component => process_configuration_setup_component <>= subroutine process_configuration_setup_component & (config, i_component, prt_in, prt_out, model, var_list, & nlo_type, can_be_integrated) class(process_configuration_t), intent(inout) :: config integer, intent(in) :: i_component type(prt_spec_t), dimension(:), intent(in) :: prt_in type(prt_spec_t), dimension(:), intent(in) :: prt_out type(model_t), pointer, intent(in) :: model type(var_list_t), intent(in) :: var_list integer, intent(in), optional :: nlo_type logical, intent(in), optional :: can_be_integrated type(string_t), dimension(:), allocatable :: prt_str_in type(string_t), dimension(:), allocatable :: prt_str_out class(prc_core_def_t), allocatable :: core_def type(string_t) :: method type(string_t) :: born_me_method type(string_t) :: real_tree_me_method type(string_t) :: loop_me_method type(string_t) :: correlation_me_method type(string_t) :: dglap_me_method integer :: i if (debug_on) call msg_debug2 (D_CORE, "process_configuration_setup_component") allocate (prt_str_in (size (prt_in))) allocate (prt_str_out (size (prt_out))) forall (i = 1:size (prt_in)) prt_str_in(i) = prt_in(i)% get_name () forall (i = 1:size (prt_out)) prt_str_out(i) = prt_out(i)%get_name () method = var_list%get_sval (var_str ("$method")) if (present (nlo_type)) then select case (nlo_type) case (BORN) born_me_method = var_list%get_sval (var_str ("$born_me_method")) if (born_me_method /= var_str ("")) then method = born_me_method end if case (NLO_VIRTUAL) loop_me_method = var_list%get_sval (var_str ("$loop_me_method")) if (loop_me_method /= var_str ("")) then method = loop_me_method end if case (NLO_REAL) real_tree_me_method = & var_list%get_sval (var_str ("$real_tree_me_method")) if (real_tree_me_method /= var_str ("")) then method = real_tree_me_method end if case (NLO_DGLAP) dglap_me_method = & var_list%get_sval (var_str ("$dglap_me_method")) if (dglap_me_method /= var_str ("")) then method = dglap_me_method end if case (NLO_SUBTRACTION,NLO_MISMATCH) correlation_me_method = & var_list%get_sval (var_str ("$correlation_me_method")) if (correlation_me_method /= var_str ("")) then method = correlation_me_method end if case default end select end if call dispatch_core_def (core_def, prt_str_in, prt_str_out, & model, var_list, config%id, nlo_type, method) select type (core_def) class is (prc_external_def_t) if (present (can_be_integrated)) then call core_def%set_active_writer (can_be_integrated) else call msg_fatal ("Cannot decide if external core is integrated!") end if end select if (debug_on) call msg_debug2 (D_CORE, "import_component with method ", method) call config%entry%import_component (i_component, & n_out = size (prt_out), & prt_in = prt_in, & prt_out = prt_out, & method = method, & variant = core_def, & nlo_type = nlo_type, & can_be_integrated = can_be_integrated) end subroutine process_configuration_setup_component @ %def process_configuration_setup_component @ <>= procedure :: set_fixed_emitter => process_configuration_set_fixed_emitter <>= subroutine process_configuration_set_fixed_emitter (config, i, emitter) class(process_configuration_t), intent(inout) :: config integer, intent(in) :: i, emitter call config%entry%set_fixed_emitter (i, emitter) end subroutine process_configuration_set_fixed_emitter @ %def process_configuration_set_fixed_emitter @ <>= procedure :: set_coupling_powers => process_configuration_set_coupling_powers <>= subroutine process_configuration_set_coupling_powers & (config, alpha_power, alphas_power) class(process_configuration_t), intent(inout) :: config integer, intent(in) :: alpha_power, alphas_power call config%entry%set_coupling_powers (alpha_power, alphas_power) end subroutine process_configuration_set_coupling_powers @ %def process_configuration_set_coupling_powers @ <>= procedure :: set_component_associations => & process_configuration_set_component_associations <>= subroutine process_configuration_set_component_associations & (config, i_list, remnant, use_real_finite, mismatch) class(process_configuration_t), intent(inout) :: config integer, dimension(:), intent(in) :: i_list logical, intent(in) :: remnant, use_real_finite, mismatch integer :: i_component do i_component = 1, config%entry%get_n_components () if (any (i_list == i_component)) then call config%entry%set_associated_components (i_component, & i_list, remnant, use_real_finite, mismatch) end if end do end subroutine process_configuration_set_component_associations @ %def process_configuration_set_component_associations @ Record a process configuration: append it to the currently selected process definition library. <>= procedure :: record => process_configuration_record <>= subroutine process_configuration_record (config, global) class(process_configuration_t), intent(inout) :: config type(rt_data_t), intent(inout) :: global if (associated (global%prclib)) then call global%prclib%open () call global%prclib%append (config%entry) if (config%num_id /= 0) then write (msg_buffer, "(5A,I0,A)") "Process library '", & char (global%prclib%get_name ()), & "': recorded process '", char (config%id), "' (", & config%num_id, ")" else write (msg_buffer, "(5A)") "Process library '", & char (global%prclib%get_name ()), & "': recorded process '", char (config%id), "'" end if call msg_message () else call msg_fatal ("Recording process '" // char (config%id) & // "': active process library undefined") end if end subroutine process_configuration_record @ %def process_configuration_record @ \subsection{Unit Tests} Test module, followed by the corresponding implementation module. <<[[process_configurations_ut.f90]]>>= <> module process_configurations_ut use unit_tests use process_configurations_uti <> <> <> contains <> end module process_configurations_ut @ %def process_configurations_ut @ <<[[process_configurations_uti.f90]]>>= <> module process_configurations_uti <> use particle_specifiers, only: new_prt_spec use prclib_stacks use models use rt_data use process_configurations <> <> <> contains <> <> end module process_configurations_uti @ %def process_configurations_uti @ API: driver for the unit tests below. <>= public :: process_configurations_test <>= subroutine process_configurations_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine process_configurations_test @ %def process_configurations_test @ \subsubsection{Minimal setup} The workflow for setting up a minimal process configuration with the test matrix element method. We wrap this in a public procedure, so we can reuse it in later modules. The procedure prepares a process definition list for two processes (one [[prc_test]] and one [[omega]] type) and appends this to the process library stack in the global data set. The [[mode]] argument determines which processes to build. The [[procname]] argument replaces the predefined procname(s). This is re-exported by the UT module. <>= public :: prepare_test_library <>= subroutine prepare_test_library (global, libname, mode, procname) type(rt_data_t), intent(inout), target :: global type(string_t), intent(in) :: libname integer, intent(in) :: mode type(string_t), intent(in), dimension(:), optional :: procname type(prclib_entry_t), pointer :: lib type(string_t) :: prc_name type(string_t), dimension(:), allocatable :: prt_in, prt_out integer :: n_components type(process_configuration_t) :: prc_config if (.not. associated (global%prclib_stack%get_first_ptr ())) then allocate (lib) call lib%init (libname) call global%add_prclib (lib) end if if (btest (mode, 0)) then call global%select_model (var_str ("Test")) if (present (procname)) then prc_name = procname(1) else prc_name = "prc_config_a" end if n_components = 1 allocate (prt_in (2), prt_out (2)) prt_in = [var_str ("s"), var_str ("s")] prt_out = [var_str ("s"), var_str ("s")] call global%set_string (var_str ("$method"),& var_str ("unit_test"), is_known = .true.) call prc_config%init (prc_name, & size (prt_in), n_components, & global%model, global%var_list) call prc_config%setup_component (1, & new_prt_spec (prt_in), new_prt_spec (prt_out), & global%model, global%var_list) call prc_config%record (global) deallocate (prt_in, prt_out) end if if (btest (mode, 1)) then call global%select_model (var_str ("QED")) if (present (procname)) then prc_name = procname(2) else prc_name = "prc_config_b" end if n_components = 1 allocate (prt_in (2), prt_out (2)) prt_in = [var_str ("e+"), var_str ("e-")] prt_out = [var_str ("m+"), var_str ("m-")] call global%set_string (var_str ("$method"),& var_str ("omega"), is_known = .true.) call prc_config%init (prc_name, & size (prt_in), n_components, & global%model, global%var_list) call prc_config%setup_component (1, & new_prt_spec (prt_in), new_prt_spec (prt_out), & global%model, global%var_list) call prc_config%record (global) deallocate (prt_in, prt_out) end if if (btest (mode, 2)) then call global%select_model (var_str ("Test")) if (present (procname)) then prc_name = procname(1) else prc_name = "prc_config_a" end if n_components = 1 allocate (prt_in (1), prt_out (2)) prt_in = [var_str ("s")] prt_out = [var_str ("f"), var_str ("fbar")] call global%set_string (var_str ("$method"),& var_str ("unit_test"), is_known = .true.) call prc_config%init (prc_name, & size (prt_in), n_components, & global%model, global%var_list) call prc_config%setup_component (1, & new_prt_spec (prt_in), new_prt_spec (prt_out), & global%model, global%var_list) call prc_config%record (global) deallocate (prt_in, prt_out) end if end subroutine prepare_test_library @ %def prepare_test_library @ The actual test: the previous procedure with some prelude and postlude. In the global variable list, just before printing we reset the variables where the value may depend on the system and run environment. <>= call test (process_configurations_1, "process_configurations_1", & "test processes", & u, results) <>= public :: process_configurations_1 <>= subroutine process_configurations_1 (u) integer, intent(in) :: u type(rt_data_t), target :: global write (u, "(A)") "* Test output: process_configurations_1" write (u, "(A)") "* Purpose: configure test processes" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) write (u, "(A)") "* Configure processes as prc_test, model Test" write (u, "(A)") "* and omega, model QED" write (u, *) call global%set_int (var_str ("process_num_id"), & 42, is_known = .true.) call prepare_test_library (global, var_str ("prc_config_lib_1"), 3) global%os_data%fc = "Fortran-compiler" global%os_data%fcflags = "Fortran-flags" global%os_data%fclibs = "Fortran-libs" call global%write_libraries (u) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: process_configurations_1" end subroutine process_configurations_1 @ %def process_configurations_1 @ \subsubsection{\oMega\ options} Slightly extended example where we pass \oMega\ options to the library. The [[prepare_test_library]] contents are spelled out. <>= call test (process_configurations_2, "process_configurations_2", & "omega options", & u, results) <>= public :: process_configurations_2 <>= subroutine process_configurations_2 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(string_t) :: libname type(prclib_entry_t), pointer :: lib type(string_t) :: prc_name type(string_t), dimension(:), allocatable :: prt_in, prt_out integer :: n_components type(process_configuration_t) :: prc_config write (u, "(A)") "* Test output: process_configurations_2" write (u, "(A)") "* Purpose: configure test processes with options" write (u, "(A)") call syntax_model_file_init () call global%global_init () write (u, "(A)") "* Configure processes as omega, model QED" write (u, *) libname = "prc_config_lib_2" allocate (lib) call lib%init (libname) call global%add_prclib (lib) call global%select_model (var_str ("QED")) prc_name = "prc_config_c" n_components = 2 allocate (prt_in (2), prt_out (2)) prt_in = [var_str ("e+"), var_str ("e-")] prt_out = [var_str ("m+"), var_str ("m-")] call global%set_string (var_str ("$method"),& var_str ("omega"), is_known = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call prc_config%init (prc_name, size (prt_in), n_components, & global%model, global%var_list) call global%set_log (var_str ("?report_progress"), & .true., is_known = .true.) call prc_config%setup_component (1, & new_prt_spec (prt_in), new_prt_spec (prt_out), global%model, global%var_list) call global%set_log (var_str ("?report_progress"), & .false., is_known = .true.) call global%set_log (var_str ("?omega_openmp"), & .true., is_known = .true.) call global%set_string (var_str ("$restrictions"),& var_str ("3+4~A"), is_known = .true.) call global%set_string (var_str ("$omega_flags"), & var_str ("-fusion:progress_file omega_prc_config.log"), & is_known = .true.) call prc_config%setup_component (2, & new_prt_spec (prt_in), new_prt_spec (prt_out), global%model, global%var_list) call prc_config%record (global) deallocate (prt_in, prt_out) global%os_data%fc = "Fortran-compiler" global%os_data%fcflags = "Fortran-flags" global%os_data%fclibs = "Fortran-libs" call global%write_vars (u, [ & var_str ("$model_name"), & var_str ("$method"), & var_str ("?report_progress"), & var_str ("$restrictions"), & var_str ("$omega_flags")]) write (u, "(A)") call global%write_libraries (u) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: process_configurations_2" end subroutine process_configurations_2 @ %def process_configurations_2 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Compilation} This module manages compilation and loading of of process libraries. It is needed as a separate module because integration depends on it. <<[[compilations.f90]]>>= <> module compilations <> use io_units use system_defs, only: TAB use system_dependencies, only: OS_IS_DARWIN use diagnostics use os_interface use variables, only: var_list_t use model_data use process_libraries use prclib_stacks use rt_data <> <> <> <> contains <> end module compilations @ %def compilations @ \subsection{The data type} The compilation item handles the compilation and loading of a single process library. <>= public :: compilation_item_t <>= type :: compilation_item_t private type(string_t) :: libname type(string_t) :: static_external_tag type(process_library_t), pointer :: lib => null () logical :: recompile_library = .false. logical :: verbose = .false. logical :: use_workspace = .false. type(string_t) :: workspace contains <> end type compilation_item_t @ %def compilation_item_t @ Initialize. Set flags and global properties of the library. Establish the workspace name, if defined. <>= procedure :: init => compilation_item_init <>= subroutine compilation_item_init (comp, libname, stack, var_list) class(compilation_item_t), intent(out) :: comp type(string_t), intent(in) :: libname type(prclib_stack_t), intent(inout) :: stack type(var_list_t), intent(in) :: var_list comp%libname = libname comp%lib => stack%get_library_ptr (comp%libname) if (.not. associated (comp%lib)) then call msg_fatal ("Process library '" // char (comp%libname) & // "' has not been declared.") end if comp%recompile_library = & var_list%get_lval (var_str ("?recompile_library")) comp%verbose = & var_list%get_lval (var_str ("?me_verbose")) comp%use_workspace = & var_list%is_known (var_str ("$compile_workspace")) if (comp%use_workspace) then comp%workspace = & var_list%get_sval (var_str ("$compile_workspace")) if (comp%workspace == "") comp%use_workspace = .false. else comp%workspace = "" end if end subroutine compilation_item_init @ %def compilation_item_init @ Compile the current library. The [[force]] flag has the effect that we first delete any previous files, as far as accessible by the current makefile. It also guarantees that previous files not accessible by a makefile will be overwritten. <>= procedure :: compile => compilation_item_compile <>= subroutine compilation_item_compile (comp, model, os_data, force, recompile) class(compilation_item_t), intent(inout) :: comp class(model_data_t), intent(in), target :: model type(os_data_t), intent(in) :: os_data logical, intent(in) :: force, recompile if (associated (comp%lib)) then if (comp%use_workspace) call setup_workspace (comp%workspace, os_data) call msg_message ("Process library '" & // char (comp%libname) // "': compiling ...") call comp%lib%configure (os_data) if (signal_is_pending ()) return call comp%lib%compute_md5sum (model) call comp%lib%write_makefile & (os_data, force, verbose=comp%verbose, workspace=comp%workspace) if (signal_is_pending ()) return if (force) then call comp%lib%clean & (os_data, distclean = .false., workspace=comp%workspace) if (signal_is_pending ()) return end if call comp%lib%write_driver (force, workspace=comp%workspace) if (signal_is_pending ()) return if (recompile) then call comp%lib%load & (os_data, keep_old_source = .true., workspace=comp%workspace) if (signal_is_pending ()) return end if call comp%lib%update_status (os_data, workspace=comp%workspace) end if end subroutine compilation_item_compile @ %def compilation_item_compile @ The workspace directory is created if it does not exist. (Applies only if the use has set the workspace directory.) <>= character(*), parameter :: ALLOWED_IN_DIRNAME = & "abcdefghijklmnopqrstuvwxyz& &ABCDEFGHIJKLMNOPQRSTUVWXYZ& &1234567890& &.,_-+=" @ %def ALLOWED_IN_DIRNAME <>= subroutine setup_workspace (workspace, os_data) type(string_t), intent(in) :: workspace type(os_data_t), intent(in) :: os_data if (verify (workspace, ALLOWED_IN_DIRNAME) == 0) then call msg_message ("Compile: preparing workspace directory '" & // char (workspace) // "'") call os_system_call ("mkdir -p '" // workspace // "'") else call msg_fatal ("compile: workspace name '" & // char (workspace) // "' contains illegal characters") end if end subroutine setup_workspace @ %def setup_workspace @ Load the current library, just after compiling it. <>= procedure :: load => compilation_item_load <>= subroutine compilation_item_load (comp, os_data) class(compilation_item_t), intent(inout) :: comp type(os_data_t), intent(in) :: os_data if (associated (comp%lib)) then call comp%lib%load (os_data, workspace=comp%workspace) end if end subroutine compilation_item_load @ %def compilation_item_load @ Message as a separate call: <>= procedure :: success => compilation_item_success <>= subroutine compilation_item_success (comp) class(compilation_item_t), intent(in) :: comp if (associated (comp%lib)) then call msg_message ("Process library '" // char (comp%libname) & // "': ... success.") else call msg_fatal ("Process library '" // char (comp%libname) & // "': ... failure.") end if end subroutine compilation_item_success @ %def compilation_item_success @ %def compilation_item_failure @ \subsection{API for library compilation and loading} This is a shorthand for compiling and loading a single library. The [[compilation_item]] object is used only internally. The [[global]] data set may actually be local to the caller. The compilation affects the library specified by its name if it is on the stack, but it does not reset the currently selected library. <>= public :: compile_library <>= subroutine compile_library (libname, global) type(string_t), intent(in) :: libname type(rt_data_t), intent(inout), target :: global type(compilation_item_t) :: comp logical :: force, recompile force = & global%var_list%get_lval (var_str ("?rebuild_library")) recompile = & global%var_list%get_lval (var_str ("?recompile_library")) if (associated (global%model)) then call comp%init (libname, global%prclib_stack, global%var_list) call comp%compile (global%model, global%os_data, force, recompile) if (signal_is_pending ()) return call comp%load (global%os_data) if (signal_is_pending ()) return else call msg_fatal ("Process library compilation: " & // " model is undefined.") end if call comp%success () end subroutine compile_library @ %def compile_library @ \subsection{Compiling static executable} This object handles the creation of a static executable which should contain a set of static process libraries. <>= public :: compilation_t <>= type :: compilation_t private type(string_t) :: exe_name type(string_t), dimension(:), allocatable :: lib_name contains <> end type compilation_t @ %def compilation_t @ Output. <>= procedure :: write => compilation_write <>= subroutine compilation_write (object, unit) class(compilation_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) write (u, "(1x,A)") "Compilation object:" write (u, "(3x,3A)") "executable = '", & char (object%exe_name), "'" write (u, "(3x,A)", advance="no") "process libraries =" do i = 1, size (object%lib_name) write (u, "(1x,3A)", advance="no") "'", char (object%lib_name(i)), "'" end do write (u, *) end subroutine compilation_write @ %def compilation_write @ Initialize: we know the names of the executable and of the libraries. Optionally, we may provide a workspace directory. <>= procedure :: init => compilation_init <>= subroutine compilation_init (compilation, exe_name, lib_name) class(compilation_t), intent(out) :: compilation type(string_t), intent(in) :: exe_name type(string_t), dimension(:), intent(in) :: lib_name compilation%exe_name = exe_name allocate (compilation%lib_name (size (lib_name))) compilation%lib_name = lib_name end subroutine compilation_init @ %def compilation_init @ Write the dispatcher subroutine for the compiled libraries. Also write a subroutine which returns the names of the compiled libraries. <>= procedure :: write_dispatcher => compilation_write_dispatcher <>= subroutine compilation_write_dispatcher (compilation) class(compilation_t), intent(in) :: compilation type(string_t) :: file integer :: u, i file = compilation%exe_name // "_prclib_dispatcher.f90" call msg_message ("Static executable '" // char (compilation%exe_name) & // "': writing library dispatcher") u = free_unit () open (u, file = char (file), status="replace", action="write") write (u, "(3A)") "! Whizard: process libraries for executable '", & char (compilation%exe_name), "'" write (u, "(A)") "! Automatically generated file, do not edit" write (u, "(A)") "subroutine dispatch_prclib_static " // & "(driver, basename, modellibs_ldflags)" write (u, "(A)") " use iso_varying_string, string_t => varying_string" write (u, "(A)") " use prclib_interfaces" do i = 1, size (compilation%lib_name) associate (lib_name => compilation%lib_name(i)) write (u, "(A)") " use " // char (lib_name) // "_driver" end associate end do write (u, "(A)") " implicit none" write (u, "(A)") " class(prclib_driver_t), intent(inout), allocatable & &:: driver" write (u, "(A)") " type(string_t), intent(in) :: basename" write (u, "(A)") " logical, intent(in), optional :: " // & "modellibs_ldflags" write (u, "(A)") " select case (char (basename))" do i = 1, size (compilation%lib_name) associate (lib_name => compilation%lib_name(i)) write (u, "(3A)") " case ('", char (lib_name), "')" write (u, "(3A)") " allocate (", char (lib_name), "_driver_t & &:: driver)" end associate end do write (u, "(A)") " end select" write (u, "(A)") "end subroutine dispatch_prclib_static" write (u, *) write (u, "(A)") "subroutine get_prclib_static (libname)" write (u, "(A)") " use iso_varying_string, string_t => varying_string" write (u, "(A)") " implicit none" write (u, "(A)") " type(string_t), dimension(:), intent(inout), & &allocatable :: libname" write (u, "(A,I0,A)") " allocate (libname (", & size (compilation%lib_name), "))" do i = 1, size (compilation%lib_name) associate (lib_name => compilation%lib_name(i)) write (u, "(A,I0,A,A,A)") " libname(", i, ") = '", & char (lib_name), "'" end associate end do write (u, "(A)") "end subroutine get_prclib_static" close (u) end subroutine compilation_write_dispatcher @ %def compilation_write_dispatcher @ Write the Makefile subroutine for the compiled libraries. <>= procedure :: write_makefile => compilation_write_makefile <>= subroutine compilation_write_makefile & (compilation, os_data, ext_libtag, verbose, overwrite_os) class(compilation_t), intent(in) :: compilation type(os_data_t), intent(in) :: os_data logical, intent(in) :: verbose logical, intent(in), optional :: overwrite_os logical :: overwrite type(string_t), intent(in), optional :: ext_libtag type(string_t) :: file, ext_tag integer :: u, i overwrite = .false. if (present (overwrite_os)) overwrite = overwrite_os if (present (ext_libtag)) then ext_tag = ext_libtag else ext_tag = "" end if file = compilation%exe_name // ".makefile" call msg_message ("Static executable '" // char (compilation%exe_name) & // "': writing makefile") u = free_unit () open (u, file = char (file), status="replace", action="write") write (u, "(3A)") "# WHIZARD: Makefile for executable '", & char (compilation%exe_name), "'" write (u, "(A)") "# Automatically generated file, do not edit" write (u, "(A)") "" write (u, "(A)") "# Executable name" write (u, "(A)") "EXE = " // char (compilation%exe_name) write (u, "(A)") "" write (u, "(A)") "# Compiler" write (u, "(A)") "FC = " // char (os_data%fc) write (u, "(A)") "CXX = " // char (os_data%cxx) write (u, "(A)") "" write (u, "(A)") "# Included libraries" write (u, "(A)") "FCINCL = " // char (os_data%whizard_includes) write (u, "(A)") "" write (u, "(A)") "# Compiler flags" write (u, "(A)") "FCFLAGS = " // char (os_data%fcflags) write (u, "(A)") "FCLIBS = " // char (os_data%fclibs) write (u, "(A)") "CXXFLAGS = " // char (os_data%cxxflags) write (u, "(A)") "CXXLIBSS = " // char (os_data%cxxlibs) write (u, "(A)") "LDFLAGS = " // char (os_data%ldflags) write (u, "(A)") "LDFLAGS_STATIC = " // char (os_data%ldflags_static) write (u, "(A)") "LDFLAGS_HEPMC = " // char (os_data%ldflags_hepmc) write (u, "(A)") "LDFLAGS_LCIO = " // char (os_data%ldflags_lcio) write (u, "(A)") "LDFLAGS_HOPPET = " // char (os_data%ldflags_hoppet) write (u, "(A)") "LDFLAGS_LOOPTOOLS = " // char (os_data%ldflags_looptools) write (u, "(A)") "LDWHIZARD = " // char (os_data%whizard_ldflags) write (u, "(A)") "" write (u, "(A)") "# Libtool" write (u, "(A)") "LIBTOOL = " // char (os_data%whizard_libtool) if (verbose) then write (u, "(A)") "FCOMPILE = $(LIBTOOL) --tag=FC --mode=compile" if (OS_IS_DARWIN .and. .not. overwrite) then write (u, "(A)") "LINK = $(LIBTOOL) --tag=CXX --mode=link" else write (u, "(A)") "LINK = $(LIBTOOL) --tag=FC --mode=link" end if else write (u, "(A)") "FCOMPILE = @$(LIBTOOL) --silent --tag=FC --mode=compile" if (OS_IS_DARWIN .and. .not. overwrite) then write (u, "(A)") "LINK = @$(LIBTOOL) --silent --tag=CXX --mode=link" else write (u, "(A)") "LINK = @$(LIBTOOL) --silent --tag=FC --mode=link" end if end if write (u, "(A)") "" write (u, "(A)") "# Compile commands (default)" write (u, "(A)") "LTFCOMPILE = $(FCOMPILE) $(FC) -c $(FCINCL) $(FCFLAGS)" write (u, "(A)") "" write (u, "(A)") "# Default target" write (u, "(A)") "all: link" write (u, "(A)") "" write (u, "(A)") "# Libraries" do i = 1, size (compilation%lib_name) associate (lib_name => compilation%lib_name(i)) write (u, "(A)") "LIBRARIES += " // char (lib_name) // ".la" write (u, "(A)") char (lib_name) // ".la:" write (u, "(A)") TAB // "$(MAKE) -f " // char (lib_name) // ".makefile" end associate end do write (u, "(A)") "" write (u, "(A)") "# Library dispatcher" write (u, "(A)") "DISP = $(EXE)_prclib_dispatcher" write (u, "(A)") "$(DISP).lo: $(DISP).f90 $(LIBRARIES)" if (.not. verbose) then write (u, "(A)") TAB // '@echo " FC " $@' end if write (u, "(A)") TAB // "$(LTFCOMPILE) $<" write (u, "(A)") "" write (u, "(A)") "# Executable" write (u, "(A)") "$(EXE): $(DISP).lo $(LIBRARIES)" if (.not. verbose) then if (OS_IS_DARWIN .and. .not. overwrite) then write (u, "(A)") TAB // '@echo " CXXLD " $@' else write (u, "(A)") TAB // '@echo " FCLD " $@' end if end if if (OS_IS_DARWIN .and. .not. overwrite) then write (u, "(A)") TAB // "$(LINK) $(CXX) -static $(CXXFLAGS) \" else write (u, "(A)") TAB // "$(LINK) $(FC) -static $(FCFLAGS) \" end if write (u, "(A)") TAB // " $(LDWHIZARD) $(LDFLAGS) \" write (u, "(A)") TAB // " -o $(EXE) $^ \" write (u, "(A)") TAB // " $(LDFLAGS_HEPMC) $(LDFLAGS_LCIO) $(LDFLAGS_HOPPET) \" if (OS_IS_DARWIN .and. .not. overwrite) then write (u, "(A)") TAB // " $(LDFLAGS_LOOPTOOLS) $(LDFLAGS_STATIC) \" write (u, "(A)") TAB // " $(CXXLIBS) $(FCLIBS)" // char (ext_tag) else write (u, "(A)") TAB // " $(LDFLAGS_LOOPTOOLS) $(LDFLAGS_STATIC)" // char (ext_tag) end if write (u, "(A)") "" write (u, "(A)") "# Main targets" write (u, "(A)") "link: compile $(EXE)" write (u, "(A)") "compile: $(LIBRARIES) $(DISP).lo" write (u, "(A)") ".PHONY: link compile" write (u, "(A)") "" write (u, "(A)") "# Cleanup targets" write (u, "(A)") "clean-exe:" write (u, "(A)") TAB // "rm -f $(EXE)" write (u, "(A)") "clean-objects:" write (u, "(A)") TAB // "rm -f $(DISP).lo" write (u, "(A)") "clean-source:" write (u, "(A)") TAB // "rm -f $(DISP).f90" write (u, "(A)") "clean-makefile:" write (u, "(A)") TAB // "rm -f $(EXE).makefile" write (u, "(A)") "" write (u, "(A)") "clean: clean-exe clean-objects clean-source" write (u, "(A)") "distclean: clean clean-makefile" write (u, "(A)") ".PHONY: clean distclean" close (u) end subroutine compilation_write_makefile @ %def compilation_write_makefile @ Compile the dispatcher source code. <>= procedure :: make_compile => compilation_make_compile <>= subroutine compilation_make_compile (compilation, os_data) class(compilation_t), intent(in) :: compilation type(os_data_t), intent(in) :: os_data call os_system_call ("make compile " // os_data%makeflags & // " -f " // compilation%exe_name // ".makefile") end subroutine compilation_make_compile @ %def compilation_make_compile @ Link the dispatcher together with all matrix-element code and the \whizard\ and \oMega\ main libraries, to generate a static executable. <>= procedure :: make_link => compilation_make_link <>= subroutine compilation_make_link (compilation, os_data) class(compilation_t), intent(in) :: compilation type(os_data_t), intent(in) :: os_data call os_system_call ("make link " // os_data%makeflags & // " -f " // compilation%exe_name // ".makefile") end subroutine compilation_make_link @ %def compilation_make_link @ Cleanup. <>= procedure :: make_clean_exe => compilation_make_clean_exe <>= subroutine compilation_make_clean_exe (compilation, os_data) class(compilation_t), intent(in) :: compilation type(os_data_t), intent(in) :: os_data call os_system_call ("make clean-exe " // os_data%makeflags & // " -f " // compilation%exe_name // ".makefile") end subroutine compilation_make_clean_exe @ %def compilation_make_clean_exe @ \subsection{API for executable compilation} This is a shorthand for compiling and loading an executable, including the enclosed libraries. The [[compilation]] object is used only internally. The [[global]] data set may actually be local to the caller. The compilation affects the library specified by its name if it is on the stack, but it does not reset the currently selected library. <>= public :: compile_executable <>= subroutine compile_executable (exename, libname, global) type(string_t), intent(in) :: exename type(string_t), dimension(:), intent(in) :: libname type(rt_data_t), intent(inout), target :: global type(compilation_t) :: compilation type(compilation_item_t) :: item type(string_t) :: ext_libtag logical :: force, recompile, verbose integer :: i ext_libtag = "" force = & global%var_list%get_lval (var_str ("?rebuild_library")) recompile = & global%var_list%get_lval (var_str ("?recompile_library")) verbose = & global%var_list%get_lval (var_str ("?me_verbose")) call compilation%init (exename, [libname]) if (signal_is_pending ()) return call compilation%write_dispatcher () if (signal_is_pending ()) return do i = 1, size (libname) call item%init (libname(i), global%prclib_stack, global%var_list) call item%compile (global%model, global%os_data, & force=force, recompile=recompile) ext_libtag = "" // item%lib%get_static_modelname (global%os_data) if (signal_is_pending ()) return call item%success () end do call compilation%write_makefile & (global%os_data, ext_libtag=ext_libtag, verbose=verbose) if (signal_is_pending ()) return call compilation%make_compile (global%os_data) if (signal_is_pending ()) return call compilation%make_link (global%os_data) end subroutine compile_executable @ %def compile_executable @ \subsection{Unit Tests} Test module, followed by the stand-alone unit-test procedures. <<[[compilations_ut.f90]]>>= <> module compilations_ut use unit_tests use compilations_uti <> <> contains <> end module compilations_ut @ %def compilations_ut @ <<[[compilations_uti.f90]]>>= <> module compilations_uti <> use io_units use models use rt_data use process_configurations_ut, only: prepare_test_library use compilations <> <> contains <> end module compilations_uti @ %def compilations_uti @ API: driver for the unit tests below. <>= public :: compilations_test <>= subroutine compilations_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine compilations_test @ %def compilations_test @ \subsubsection{Intrinsic Matrix Element} Compile an intrinsic test matrix element ([[prc_test]] type). Note: In this and the following test, we reset the Fortran compiler and flag variables immediately before they are printed, so the test is portable. <>= call test (compilations_1, "compilations_1", & "intrinsic test processes", & u, results) <>= public :: compilations_1 <>= subroutine compilations_1 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global write (u, "(A)") "* Test output: compilations_1" write (u, "(A)") "* Purpose: configure and compile test process" write (u, "(A)") call syntax_model_file_init () call global%global_init () libname = "compilation_1" procname = "prc_comp_1" call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%write_libraries (u) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: compilations_1" end subroutine compilations_1 @ %def compilations_1 @ \subsubsection{External Matrix Element} Compile an external test matrix element ([[omega]] type) <>= call test (compilations_2, "compilations_2", & "external process (omega)", & u, results) <>= public :: compilations_2 <>= subroutine compilations_2 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global write (u, "(A)") "* Test output: compilations_2" write (u, "(A)") "* Purpose: configure and compile test process" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) libname = "compilation_2" procname = "prc_comp_2" call prepare_test_library (global, libname, 2, [procname,procname]) call compile_library (libname, global) call global%write_libraries (u, libpath = .false.) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: compilations_2" end subroutine compilations_2 @ %def compilations_2 @ \subsubsection{External Matrix Element} Compile an external test matrix element ([[omega]] type) and create driver files for a static executable. <>= call test (compilations_3, "compilations_3", & "static executable: driver", & u, results) <>= public :: compilations_3 <>= subroutine compilations_3 (u) integer, intent(in) :: u type(string_t) :: libname, procname, exename type(rt_data_t), target :: global type(compilation_t) :: compilation integer :: u_file character(80) :: buffer write (u, "(A)") "* Test output: compilations_3" write (u, "(A)") "* Purpose: make static executable" write (u, "(A)") write (u, "(A)") "* Initialize library" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) libname = "compilations_3_lib" procname = "prc_comp_3" exename = "compilations_3" call prepare_test_library (global, libname, 2, [procname,procname]) call compilation%init (exename, [libname]) call compilation%write (u) write (u, "(A)") write (u, "(A)") "* Write dispatcher" write (u, "(A)") call compilation%write_dispatcher () u_file = free_unit () open (u_file, file = char (exename) // "_prclib_dispatcher.f90", & status = "old", action = "read") do read (u_file, "(A)", end = 1) buffer write (u, "(A)") trim (buffer) end do 1 close (u_file) write (u, "(A)") write (u, "(A)") "* Write Makefile" write (u, "(A)") associate (os_data => global%os_data) os_data%fc = "fortran-compiler" os_data%cxx = "c++-compiler" os_data%whizard_includes = "my-includes" os_data%fcflags = "my-fcflags" os_data%fclibs = "my-fclibs" os_data%cxxflags = "my-cxxflags" os_data%cxxlibs = "my-cxxlibs" os_data%ldflags = "my-ldflags" os_data%ldflags_static = "my-ldflags-static" os_data%ldflags_hepmc = "my-ldflags-hepmc" os_data%ldflags_lcio = "my-ldflags-lcio" os_data%ldflags_hoppet = "my-ldflags-hoppet" os_data%ldflags_looptools = "my-ldflags-looptools" os_data%whizard_ldflags = "my-ldwhizard" os_data%whizard_libtool = "my-libtool" end associate call compilation%write_makefile & (global%os_data, verbose = .true., overwrite_os = .true.) open (u_file, file = char (exename) // ".makefile", & status = "old", action = "read") do read (u_file, "(A)", end = 2) buffer write (u, "(A)") trim (buffer) end do 2 close (u_file) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: compilations_3" end subroutine compilations_3 @ %def compilations_3 @ \subsection{Test static build} The tests for building a static executable are separate, since they should be skipped if the \whizard\ build itself has static libraries disabled. <>= public :: compilations_static_test <>= subroutine compilations_static_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine compilations_static_test @ %def compilations_static_test @ \subsubsection{External Matrix Element} Compile an external test matrix element ([[omega]] type) and incorporate this in a new static WHIZARD executable. <>= call test (compilations_static_1, "compilations_static_1", & "static executable: compilation", & u, results) <>= public :: compilations_static_1 <>= subroutine compilations_static_1 (u) integer, intent(in) :: u type(string_t) :: libname, procname, exename type(rt_data_t), target :: global type(compilation_item_t) :: item type(compilation_t) :: compilation logical :: exist write (u, "(A)") "* Test output: compilations_static_1" write (u, "(A)") "* Purpose: make static executable" write (u, "(A)") write (u, "(A)") "* Initialize library" call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) libname = "compilations_static_1_lib" procname = "prc_comp_stat_1" exename = "compilations_static_1" call prepare_test_library (global, libname, 2, [procname,procname]) call compilation%init (exename, [libname]) write (u, "(A)") write (u, "(A)") "* Write dispatcher" call compilation%write_dispatcher () write (u, "(A)") write (u, "(A)") "* Write Makefile" call compilation%write_makefile (global%os_data, verbose = .true.) write (u, "(A)") write (u, "(A)") "* Build libraries" call item%init (libname, global%prclib_stack, global%var_list) call item%compile & (global%model, global%os_data, force=.true., recompile=.false.) call item%success () write (u, "(A)") write (u, "(A)") "* Check executable (should be absent)" write (u, "(A)") call compilation%make_clean_exe (global%os_data) inquire (file = char (exename), exist = exist) write (u, "(A,A,L1)") char (exename), " exists = ", exist write (u, "(A)") write (u, "(A)") "* Build executable" write (u, "(A)") call compilation%make_compile (global%os_data) call compilation%make_link (global%os_data) write (u, "(A)") "* Check executable (should be present)" write (u, "(A)") inquire (file = char (exename), exist = exist) write (u, "(A,A,L1)") char (exename), " exists = ", exist write (u, "(A)") write (u, "(A)") "* Cleanup" call compilation%make_clean_exe (global%os_data) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: compilations_static_1" end subroutine compilations_static_1 @ %def compilations_static_1 @ \subsubsection{External Matrix Element} Compile an external test matrix element ([[omega]] type) and incorporate this in a new static WHIZARD executable. In this version, we use the wrapper [[compile_executable]] procedure. <>= call test (compilations_static_2, "compilations_static_2", & "static executable: shortcut", & u, results) <>= public :: compilations_static_2 <>= subroutine compilations_static_2 (u) integer, intent(in) :: u type(string_t) :: libname, procname, exename type(rt_data_t), target :: global logical :: exist integer :: u_file write (u, "(A)") "* Test output: compilations_static_2" write (u, "(A)") "* Purpose: make static executable" write (u, "(A)") write (u, "(A)") "* Initialize library and compile" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) libname = "compilations_static_2_lib" procname = "prc_comp_stat_2" exename = "compilations_static_2" call prepare_test_library (global, libname, 2, [procname,procname]) call compile_executable (exename, [libname], global) write (u, "(A)") "* Check executable (should be present)" write (u, "(A)") inquire (file = char (exename), exist = exist) write (u, "(A,A,L1)") char (exename), " exists = ", exist write (u, "(A)") write (u, "(A)") "* Cleanup" u_file = free_unit () open (u_file, file = char (exename), status = "old", action = "write") close (u_file, status = "delete") call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: compilations_static_2" end subroutine compilations_static_2 @ %def compilations_static_2 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Integration} This module manages phase space setup, matrix-element evaluation and integration, as far as it is not done by lower-level routines, in particular in the [[processes]] module. <<[[integrations.f90]]>>= <> module integrations <> <> <> use io_units use diagnostics use os_interface use cputime use sm_qcd use physics_defs use model_data use pdg_arrays use variables, only: var_list_t use eval_trees use sf_mappings use sf_base use phs_base use rng_base use mci_base use process_libraries use prc_core use process_config, only: COMP_MASTER, COMP_REAL_FIN, & COMP_MISMATCH, COMP_PDF, COMP_REAL, COMP_SUB, COMP_VIRT, & COMP_REAL_SING use process use pcm_base, only: pcm_t use instances use process_stacks use models use iterations use rt_data use dispatch_me_methods, only: dispatch_core use dispatch_beams, only: dispatch_qcd, sf_prop_t, dispatch_sf_config use dispatch_phase_space, only: dispatch_sf_channels use dispatch_phase_space, only: dispatch_phs use dispatch_mci, only: dispatch_mci_s, setup_grid_path use dispatch_transforms, only: dispatch_evt_shower_hook use compilations, only: compile_library use dispatch_fks, only: dispatch_fks_s use nlo_data <> <> <> <> contains <> end module integrations @ %def integrations @ \subsection{The integration type} This type holds all relevant data, the integration methods operates on this. In contrast to the [[simulation_t]] introduced later, the [[integration_t]] applies to a single process. <>= public :: integration_t <>= type :: integration_t private type(string_t) :: process_id type(string_t) :: run_id type(process_t), pointer :: process => null () logical :: rebuild_phs = .false. logical :: ignore_phs_mismatch = .false. logical :: phs_only = .false. logical :: process_has_me = .true. integer :: n_calls_test = 0 logical :: vis_history = .true. type(string_t) :: history_filename type(string_t) :: log_filename type(helicity_selection_t) :: helicity_selection logical :: use_color_factors = .false. logical :: has_beam_pol = .false. logical :: combined_integration = .false. type(iteration_multipliers_t) :: iteration_multipliers type(nlo_settings_t) :: nlo_settings contains <> end type integration_t @ %def integration_t @ @ \subsection{Initialization} Initialization, first part: Create a process entry. Push it on the stack if the [[global]] environment is supplied. <>= procedure :: create_process => integration_create_process <>= subroutine integration_create_process (intg, process_id, global) class(integration_t), intent(out) :: intg type(rt_data_t), intent(inout), optional, target :: global type(string_t), intent(in) :: process_id type(process_entry_t), pointer :: process_entry if (debug_on) call msg_debug (D_CORE, "integration_create_process") intg%process_id = process_id if (present (global)) then allocate (process_entry) intg%process => process_entry%process_t call global%process_stack%push (process_entry) else allocate (process_t :: intg%process) end if end subroutine integration_create_process @ %def integration_create_process @ Initialization, second part: Initialize the process object, using the local environment. We allocate a RNG factory and a QCD object. We also fetch a pointer to the model that the process uses. The process initializer will create a snapshot of that model. This procedure does not modify the [[local]] stack directly. The intent(inout) attribute for the [[local]] data set is due to the random generator seed which may be incremented during initialization. NOTE: Changes to model parameters within the current context are respected only if the process model coincides with the current model. This is the usual case. If not, we read the model from the global model library, which has default parameters. To become more flexible, we should implement a local model library which records local changes to currently inactive models. <>= procedure :: init_process => integration_init_process <>= subroutine integration_init_process (intg, local) class(integration_t), intent(inout) :: intg type(rt_data_t), intent(inout), target :: local type(string_t) :: model_name type(model_t), pointer :: model class(model_data_t), pointer :: model_instance type(var_list_t), pointer :: var_list if (debug_on) call msg_debug (D_CORE, "integration_init_process") if (.not. local%prclib%contains (intg%process_id)) then call msg_fatal ("Process '" // char (intg%process_id) // "' not found" & // " in library '" // char (local%prclib%get_name ()) // "'") return end if model_name = local%prclib%get_model_name (intg%process_id) if (local%get_sval (var_str ("$model_name")) == model_name) then model => local%model else model => local%model_list%get_model_ptr (model_name) end if var_list => local%get_var_list_ptr () call intg%process%init (intg%process_id, & local%prclib, & local%os_data, & model, & var_list, & local%beam_structure) intg%run_id = intg%process%get_run_id () end subroutine integration_init_process @ %def integration_init_process @ Initialization, third part: complete process configuration. <>= procedure :: setup_process => integration_setup_process <>= subroutine integration_setup_process (intg, local, verbose, init_only) class(integration_t), intent(inout) :: intg type(rt_data_t), intent(inout), target :: local logical, intent(in), optional :: verbose logical, intent(in), optional :: init_only type(var_list_t), pointer :: var_list class(mci_t), allocatable :: mci_template type(sf_config_t), dimension(:), allocatable :: sf_config type(sf_prop_t) :: sf_prop type(sf_channel_t), dimension(:), allocatable :: sf_channel type(phs_channel_collection_t) :: phs_channel_collection logical :: sf_trace logical :: verb, initialize_only type(string_t) :: sf_string type(string_t) :: workspace real(default) :: sqrts verb = .true.; if (present (verbose)) verb = verbose initialize_only = .false. if (present (init_only)) initialize_only = init_only call display_init_message (verb) var_list => local%get_var_list_ptr () call setup_log_and_history () associate (process => intg%process) call set_intg_parameters (process) call process%setup_cores (dispatch_core, & intg%helicity_selection, intg%use_color_factors, intg%has_beam_pol) call process%init_phs_config () call process%init_components () call process%record_inactive_components () intg%process_has_me = process%has_matrix_element () if (.not. intg%process_has_me) then call msg_warning ("Process '" & // char (intg%process_id) // "': matrix element vanishes") end if call setup_beams () call setup_structure_functions () workspace = var_list%get_sval (var_str ("$integrate_workspace")) if (workspace == "") then call process%configure_phs & (intg%rebuild_phs, & intg%ignore_phs_mismatch, & intg%combined_integration) else call setup_grid_path (workspace) call process%configure_phs & (intg%rebuild_phs, & intg%ignore_phs_mismatch, & intg%combined_integration, & workspace) end if call process%complete_pcm_setup () call process%prepare_blha_cores () call process%create_blha_interface () call process%prepare_any_external_code () call process%setup_terms (with_beams = intg%has_beam_pol) call process%check_masses () call process%optimize_nlo_singular_regions () if (verb) then call process%write (screen = .true.) call process%print_phs_startup_message () end if if (intg%process_has_me) then if (size (sf_config) > 0) then call process%collect_channels (phs_channel_collection) else if (.not. initialize_only & .and. process%contains_trivial_component ()) then call msg_fatal ("Integrate: 2 -> 1 process can't be handled & &with fixed-energy beams") end if if (local%beam_structure%asymmetric ()) then sqrts = process%get_sqrts () else sqrts = local%get_sqrts () end if call dispatch_sf_channels & (sf_channel, sf_string, sf_prop, phs_channel_collection, & local%var_list, sqrts, local%beam_structure) if (allocated (sf_channel)) then if (size (sf_channel) > 0) then call process%set_sf_channel (sf_channel) end if end if call phs_channel_collection%final () if (verb) call process%sf_startup_message (sf_string) end if call process%setup_mci (dispatch_mci_s) call setup_expressions () call process%compute_md5sum () end associate contains subroutine setup_log_and_history () if (intg%run_id /= "") then intg%history_filename = intg%process_id // "." // intg%run_id & // ".history" intg%log_filename = intg%process_id // "." // intg%run_id // ".log" else intg%history_filename = intg%process_id // ".history" intg%log_filename = intg%process_id // ".log" end if intg%vis_history = & var_list%get_lval (var_str ("?vis_history")) end subroutine setup_log_and_history subroutine set_intg_parameters (process) type(process_t), intent(in) :: process intg%n_calls_test = & var_list%get_ival (var_str ("n_calls_test")) intg%combined_integration = & var_list%get_lval (var_str ('?combined_nlo_integration')) & .and. process%is_nlo_calculation () intg%use_color_factors = & var_list%get_lval (var_str ("?read_color_factors")) intg%has_beam_pol = & local%beam_structure%has_polarized_beams () intg%helicity_selection = & local%get_helicity_selection () intg%rebuild_phs = & var_list%get_lval (var_str ("?rebuild_phase_space")) intg%ignore_phs_mismatch = & .not. var_list%get_lval (var_str ("?check_phs_file")) intg%phs_only = & var_list%get_lval (var_str ("?phs_only")) end subroutine set_intg_parameters subroutine display_init_message (verb) logical, intent(in) :: verb if (verb) then call msg_message ("Initializing integration for process " & // char (intg%process_id) // ":") if (intg%run_id /= "") & call msg_message ("Run ID = " // '"' // char (intg%run_id) // '"') end if end subroutine display_init_message subroutine setup_beams () real(default) :: sqrts logical :: decay_rest_frame sqrts = local%get_sqrts () decay_rest_frame = & var_list%get_lval (var_str ("?decay_rest_frame")) if (intg%process_has_me) then call intg%process%setup_beams_beam_structure & (local%beam_structure, sqrts, decay_rest_frame) end if if (verb .and. intg%process_has_me) then call intg%process%beams_startup_message & (beam_structure = local%beam_structure) end if end subroutine setup_beams subroutine setup_structure_functions () integer :: n_in type(pdg_array_t), dimension(:,:), allocatable :: pdg_prc type(string_t) :: sf_trace_file if (intg%process_has_me) then call intg%process%get_pdg_in (pdg_prc) else n_in = intg%process%get_n_in () allocate (pdg_prc (n_in, intg%process%get_n_components ())) pdg_prc = 0 end if call dispatch_sf_config (sf_config, sf_prop, local%beam_structure, & local%get_var_list_ptr (), local%var_list, & local%model, local%os_data, local%get_sqrts (), pdg_prc) sf_trace = & var_list%get_lval (var_str ("?sf_trace")) sf_trace_file = & var_list%get_sval (var_str ("$sf_trace_file")) if (sf_trace) then call intg%process%init_sf_chain (sf_config, sf_trace_file) else call intg%process%init_sf_chain (sf_config) end if end subroutine setup_structure_functions subroutine setup_expressions () type(eval_tree_factory_t) :: expr_factory if (associated (local%pn%cuts_lexpr)) then if (verb) call msg_message ("Applying user-defined cuts.") call expr_factory%init (local%pn%cuts_lexpr) call intg%process%set_cuts (expr_factory) else if (verb) call msg_warning ("No cuts have been defined.") end if if (associated (local%pn%scale_expr)) then if (verb) call msg_message ("Using user-defined general scale.") call expr_factory%init (local%pn%scale_expr) call intg%process%set_scale (expr_factory) end if if (associated (local%pn%fac_scale_expr)) then if (verb) call msg_message ("Using user-defined factorization scale.") call expr_factory%init (local%pn%fac_scale_expr) call intg%process%set_fac_scale (expr_factory) end if if (associated (local%pn%ren_scale_expr)) then if (verb) call msg_message ("Using user-defined renormalization scale.") call expr_factory%init (local%pn%ren_scale_expr) call intg%process%set_ren_scale (expr_factory) end if if (associated (local%pn%weight_expr)) then if (verb) call msg_message ("Using user-defined reweighting factor.") call expr_factory%init (local%pn%weight_expr) call intg%process%set_weight (expr_factory) end if end subroutine setup_expressions end subroutine integration_setup_process @ %def integration_setup_process @ \subsection{Integration} Integrate: do the final integration. Here, we do a multi-iteration integration. Again, we skip iterations that are already on file. Record the results in the global variable list. <>= procedure :: evaluate => integration_evaluate <>= subroutine integration_evaluate & (intg, process_instance, i_mci, pass, it_list, pacify) class(integration_t), intent(inout) :: intg type(process_instance_t), intent(inout), target :: process_instance integer, intent(in) :: i_mci integer, intent(in) :: pass type(iterations_list_t), intent(in) :: it_list logical, intent(in), optional :: pacify integer :: n_calls, n_it logical :: adapt_grids, adapt_weights, final n_it = it_list%get_n_it (pass) n_calls = it_list%get_n_calls (pass) adapt_grids = it_list%adapt_grids (pass) adapt_weights = it_list%adapt_weights (pass) final = pass == it_list%get_n_pass () call process_instance%integrate ( & i_mci, n_it, n_calls, adapt_grids, adapt_weights, & final, pacify) end subroutine integration_evaluate @ %def integration_evaluate @ In case the user has not provided a list of iterations, make a reasonable default. This can depend on the process. The usual approach is to define two distinct passes, one for adaptation and one for integration. <>= procedure :: make_iterations_list => integration_make_iterations_list <>= subroutine integration_make_iterations_list (intg, it_list) class(integration_t), intent(in) :: intg type(iterations_list_t), intent(out) :: it_list integer :: pass, n_pass integer, dimension(:), allocatable :: n_it, n_calls logical, dimension(:), allocatable :: adapt_grids, adapt_weights n_pass = intg%process%get_n_pass_default () allocate (n_it (n_pass), n_calls (n_pass)) allocate (adapt_grids (n_pass), adapt_weights (n_pass)) do pass = 1, n_pass n_it(pass) = intg%process%get_n_it_default (pass) n_calls(pass) = intg%process%get_n_calls_default (pass) adapt_grids(pass) = intg%process%adapt_grids_default (pass) adapt_weights(pass) = intg%process%adapt_weights_default (pass) end do call it_list%init (n_it, n_calls, & adapt_grids = adapt_grids, adapt_weights = adapt_weights) end subroutine integration_make_iterations_list @ %def integration_make_iterations_list @ In NLO calculations, the individual components might scale very differently with the number of calls. This especially applies to the real-subtracted component, which usually fluctuates more than the Born and virtual component, making it a bottleneck of the calculation. Thus, the calculation is throttled twice, first by the number of calls for the real component, second by the number of surplus calls of computation-intense virtual matrix elements. Therefore, we want to set a different number of calls for each component, which is done by the subroutine [[integration_apply_call_multipliers]]. <>= procedure :: init_iteration_multipliers => integration_init_iteration_multipliers <>= subroutine integration_init_iteration_multipliers (intg, local) class(integration_t), intent(inout) :: intg type(rt_data_t), intent(in) :: local integer :: n_pass, pass type(iterations_list_t) :: it_list n_pass = local%it_list%get_n_pass () if (n_pass == 0) then call intg%make_iterations_list (it_list) n_pass = it_list%get_n_pass () end if associate (it_multipliers => intg%iteration_multipliers) allocate (it_multipliers%n_calls0 (n_pass)) do pass = 1, n_pass it_multipliers%n_calls0(pass) = local%it_list%get_n_calls (pass) end do it_multipliers%mult_real = local%var_list%get_rval & (var_str ("mult_call_real")) it_multipliers%mult_virt = local%var_list%get_rval & (var_str ("mult_call_virt")) it_multipliers%mult_dglap = local%var_list%get_rval & (var_str ("mult_call_dglap")) end associate end subroutine integration_init_iteration_multipliers @ %def integration_init_iteration_multipliers @ <>= procedure :: apply_call_multipliers => integration_apply_call_multipliers <>= subroutine integration_apply_call_multipliers (intg, n_pass, i_component, it_list) class(integration_t), intent(in) :: intg integer, intent(in) :: n_pass, i_component type(iterations_list_t), intent(inout) :: it_list integer :: nlo_type integer :: n_calls0, n_calls integer :: pass real(default) :: multiplier nlo_type = intg%process%get_component_nlo_type (i_component) do pass = 1, n_pass associate (multipliers => intg%iteration_multipliers) select case (nlo_type) case (NLO_REAL) multiplier = multipliers%mult_real case (NLO_VIRTUAL) multiplier = multipliers%mult_virt case (NLO_DGLAP) multiplier = multipliers%mult_dglap case default return end select end associate if (n_pass <= size (intg%iteration_multipliers%n_calls0)) then n_calls0 = intg%iteration_multipliers%n_calls0 (pass) n_calls = floor (multiplier * n_calls0) call it_list%set_n_calls (pass, n_calls) end if end do end subroutine integration_apply_call_multipliers @ %def integration_apply_call_multipliers @ \subsection{API for integration objects} This initializer does everything except assigning cuts/scale/weight expressions. <>= procedure :: init => integration_init <>= subroutine integration_init & (intg, process_id, local, global, local_stack, init_only) class(integration_t), intent(out) :: intg type(string_t), intent(in) :: process_id type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global logical, intent(in), optional :: init_only logical, intent(in), optional :: local_stack logical :: use_local use_local = .false.; if (present (local_stack)) use_local = local_stack if (present (global)) then call intg%create_process (process_id, global) else if (use_local) then call intg%create_process (process_id, local) else call intg%create_process (process_id) end if call intg%init_process (local) call intg%setup_process (local, init_only = init_only) call intg%init_iteration_multipliers (local) end subroutine integration_init @ %def integration_init @ Do the integration for a single process, both warmup and final evaluation. The [[eff_reset]] flag is to suppress numerical noise in the graphical output of the integration history. <>= procedure :: integrate => integration_integrate <>= subroutine integration_integrate (intg, local, eff_reset) class(integration_t), intent(inout) :: intg type(rt_data_t), intent(in), target :: local logical, intent(in), optional :: eff_reset type(string_t) :: log_filename type(var_list_t), pointer :: var_list type(process_instance_t), allocatable, target :: process_instance type(iterations_list_t) :: it_list logical :: pacify integer :: pass, i_mci, n_mci, n_pass integer :: i_component integer :: nlo_type logical :: display_summed logical :: nlo_active type(string_t) :: component_output allocate (process_instance) call process_instance%init (intg%process) var_list => intg%process%get_var_list_ptr () call openmp_set_num_threads_verbose & (var_list%get_ival (var_str ("openmp_num_threads")), & var_list%get_lval (var_str ("?openmp_logging"))) pacify = var_list%get_lval (var_str ("?pacify")) display_summed = .true. n_mci = intg%process%get_n_mci () if (n_mci == 1) then write (msg_buffer, "(A,A,A)") & "Starting integration for process '", & char (intg%process%get_id ()), "'" call msg_message () end if call setup_hooks () nlo_active = any (intg%process%get_component_nlo_type & ([(i_mci, i_mci = 1, n_mci)]) /= BORN) do i_mci = 1, n_mci i_component = intg%process%get_master_component (i_mci) nlo_type = intg%process%get_component_nlo_type (i_component) if (intg%process%component_can_be_integrated (i_component)) then if (n_mci > 1) then if (nlo_active) then if (intg%combined_integration .and. nlo_type == BORN) then component_output = var_str ("Combined") else component_output = component_status (nlo_type) end if write (msg_buffer, "(A,A,A,A,A)") & "Starting integration for process '", & char (intg%process%get_id ()), "' part '", & char (component_output), "'" else write (msg_buffer, "(A,A,A,I0)") & "Starting integration for process '", & char (intg%process%get_id ()), "' part ", i_mci end if call msg_message () end if n_pass = local%it_list%get_n_pass () if (n_pass == 0) then call msg_message ("Integrate: iterations not specified, & &using default") call intg%make_iterations_list (it_list) n_pass = it_list%get_n_pass () else it_list = local%it_list end if call intg%apply_call_multipliers (n_pass, i_mci, it_list) call msg_message ("Integrate: " // char (it_list%to_string ())) do pass = 1, n_pass call intg%evaluate (process_instance, i_mci, pass, it_list, pacify) if (signal_is_pending ()) return end do call intg%process%final_integration (i_mci) if (intg%vis_history) then call intg%process%display_integration_history & (i_mci, intg%history_filename, local%os_data, eff_reset) end if if (local%logfile == intg%log_filename) then if (intg%run_id /= "") then log_filename = intg%process_id // "." // intg%run_id // & ".var.log" else log_filename = intg%process_id // ".var.log" end if call msg_message ("Name clash for global logfile and process log: ", & arr =[var_str ("| Renaming log file from ") // local%logfile, & var_str ("| to ") // log_filename // var_str (" .")]) else log_filename = intg%log_filename end if call intg%process%write_logfile (i_mci, log_filename) end if end do if (n_mci > 1 .and. display_summed) then call msg_message ("Integrate: sum of all components") call intg%process%display_summed_results (pacify) end if call process_instance%final () deallocate (process_instance) contains subroutine setup_hooks () class(process_instance_hook_t), pointer :: hook call dispatch_evt_shower_hook (hook, var_list, process_instance) if (associated (hook)) then call process_instance%append_after_hook (hook) end if end subroutine setup_hooks end subroutine integration_integrate @ %def integration_integrate @ Do a dummy integration for a process which could not be initialized (e.g., has no matrix element). The result is zero. <>= procedure :: integrate_dummy => integration_integrate_dummy <>= subroutine integration_integrate_dummy (intg) class(integration_t), intent(inout) :: intg call intg%process%integrate_dummy () end subroutine integration_integrate_dummy @ %def integration_integrate_dummy @ Just sample the matrix element under realistic conditions (but no cuts); throw away the results. <>= procedure :: sampler_test => integration_sampler_test <>= subroutine integration_sampler_test (intg) class(integration_t), intent(inout) :: intg type(process_instance_t), allocatable, target :: process_instance integer :: n_mci, i_mci type(timer_t) :: timer_mci, timer_tot real(default) :: t_mci, t_tot allocate (process_instance) call process_instance%init (intg%process) n_mci = intg%process%get_n_mci () if (n_mci == 1) then write (msg_buffer, "(A,A,A)") & "Test: probing process '", & char (intg%process%get_id ()), "'" call msg_message () end if call timer_tot%start () do i_mci = 1, n_mci if (n_mci > 1) then write (msg_buffer, "(A,A,A,I0)") & "Test: probing process '", & char (intg%process%get_id ()), "' part ", i_mci call msg_message () end if call timer_mci%start () call process_instance%sampler_test (i_mci, intg%n_calls_test) call timer_mci%stop () t_mci = timer_mci write (msg_buffer, "(A,ES12.5)") "Test: " & // "time in seconds (wallclock): ", t_mci call msg_message () end do call timer_tot%stop () t_tot = timer_tot if (n_mci > 1) then write (msg_buffer, "(A,ES12.5)") "Test: " & // "total time (wallclock): ", t_tot call msg_message () end if call process_instance%final () end subroutine integration_sampler_test @ %def integration_sampler_test @ Return the process pointer (needed by simulate): <>= procedure :: get_process_ptr => integration_get_process_ptr <>= function integration_get_process_ptr (intg) result (ptr) class(integration_t), intent(in) :: intg type(process_t), pointer :: ptr ptr => intg%process end function integration_get_process_ptr @ %def integration_get_process_ptr @ Simply integrate, do a dummy integration if necessary. The [[integration]] object exists only internally. If the [[global]] environment is provided, the process object is appended to the global stack. Otherwise, if [[local_stack]] is set, we append to the local process stack. If this is unset, the [[process]] object is not recorded permanently. The [[init_only]] flag can be used to skip the actual integration part. We will end up with a process object that is completely initialized, including phase space configuration. The [[eff_reset]] flag is to suppress numerical noise in the visualization of the integration history. <>= public :: integrate_process <>= subroutine integrate_process (process_id, local, global, local_stack, init_only, eff_reset) type(string_t), intent(in) :: process_id type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global logical, intent(in), optional :: local_stack, init_only, eff_reset type(string_t) :: prclib_name type(integration_t) :: intg character(32) :: buffer <> <> if (.not. associated (local%prclib)) then call msg_fatal ("Integrate: current process library is undefined") return end if if (.not. local%prclib%is_active ()) then call msg_message ("Integrate: current process library needs compilation") prclib_name = local%prclib%get_name () call compile_library (prclib_name, local) if (signal_is_pending ()) return call msg_message ("Integrate: compilation done") end if call intg%init (process_id, local, global, local_stack, init_only) if (signal_is_pending ()) return if (present (init_only)) then if (init_only) return end if if (intg%n_calls_test > 0) then write (buffer, "(I0)") intg%n_calls_test call msg_message ("Integrate: test (" // trim (buffer) // " calls) ...") call intg%sampler_test () call msg_message ("Integrate: ... test complete.") if (signal_is_pending ()) return end if <> if (intg%phs_only) then call msg_message ("Integrate: phase space only, skipping integration") else if (intg%process_has_me) then call intg%integrate (local, eff_reset) else call intg%integrate_dummy () end if end if end subroutine integrate_process @ %def integrate_process <>= @ <>= @ <>= @ @ The parallelization leads to undefined behavior while writing simultaneously to one file. The master worker has to initialize single-handed the corresponding library files and the phase space file. The slave worker will wait with a blocking [[MPI_BCAST]] until they receive a logical flag. <>= type(var_list_t), pointer :: var_list logical :: mpi_logging, process_init integer :: rank, n_size <>= if (debug_on) call msg_debug (D_MPI, "integrate_process") var_list => local%get_var_list_ptr () process_init = .false. call mpi_get_comm_id (n_size, rank) mpi_logging = (("vamp2" == char (var_list%get_sval (var_str ("$integration_method"))) .and. & & (n_size > 1)) .or. var_list%get_lval (var_str ("?mpi_logging"))) if (debug_on) call msg_debug (D_MPI, "n_size", rank) if (debug_on) call msg_debug (D_MPI, "rank", rank) if (debug_on) call msg_debug (D_MPI, "mpi_logging", mpi_logging) if (rank /= 0) then if (mpi_logging) then call msg_message ("MPI: wait for master to finish process initialization ...") end if call MPI_bcast (process_init, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD) else process_init = .true. end if if (process_init) then <>= if (rank == 0) then if (mpi_logging) then call msg_message ("MPI: finish process initialization, load slaves ...") end if call MPI_bcast (process_init, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD) end if end if call MPI_barrier (MPI_COMM_WORLD) call mpi_set_logging (mpi_logging) @ %def integrate_process_mpi @ \subsection{Unit Tests} Test module, followed by the stand-alone unit-test procedures. <<[[integrations_ut.f90]]>>= <> module integrations_ut use unit_tests use integrations_uti <> <> contains <> end module integrations_ut @ %def integrations_ut @ <<[[integrations_uti.f90]]>>= <> module integrations_uti <> <> use io_units use ifiles use lexers use parser use flavors use interactions, only: reset_interaction_counter use phs_forests use eval_trees use models use rt_data use process_configurations_ut, only: prepare_test_library use compilations, only: compile_library use integrations use phs_wood_ut, only: write_test_phs_file <> <> contains <> end module integrations_uti @ %def integrations_uti @ API: driver for the unit tests below. <>= public :: integrations_test <>= subroutine integrations_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine integrations_test @ %def integrations_test @ <>= public :: integrations_history_test <>= subroutine integrations_history_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine integrations_history_test @ %def integrations_history_test @ \subsubsection{Integration of test process} Compile and integrate an intrinsic test matrix element ([[prc_test]] type). The phase-space implementation is [[phs_single]] (single-particle phase space), the integrator is [[mci_midpoint]]. The cross section for the $2\to 2$ process $ss\to ss$ with its constant matrix element is given by \begin{equation} \sigma = c\times f\times \Phi_2 \times |M|^2. \end{equation} $c$ is the conversion constant \begin{equation} c = 0.3894\times 10^{12}\;\mathrm{fb}\,\mathrm{GeV}^2. \end{equation} $f$ is the flux of the incoming particles with mass $m=125\,\mathrm{GeV}$ and energy $\sqrt{s}=1000\,\mathrm{GeV}$ \begin{equation} f = \frac{(2\pi)^4}{2\lambda^{1/2}(s,m^2,m^2)} = \frac{(2\pi)^4}{2\sqrt{s}\,\sqrt{s - 4m^2}} = 8.048\times 10^{-4}\;\mathrm{GeV}^{-2} \end{equation} $\Phi_2$ is the volume of the two-particle phase space \begin{equation} \Phi_2 = \frac{1}{4(2\pi)^5} = 2.5529\times 10^{-5}. \end{equation} The squared matrix element $|M|^2$ is unity. Combining everything, we obtain \begin{equation} \sigma = 8000\;\mathrm{fb} \end{equation} This number should appear as the final result. Note: In this and the following test, we reset the Fortran compiler and flag variables immediately before they are printed, so the test is portable. <>= call test (integrations_1, "integrations_1", & "intrinsic test process", & u, results) <>= public :: integrations_1 <>= subroutine integrations_1 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global write (u, "(A)") "* Test output: integrations_1" write (u, "(A)") "* Purpose: integrate test process" write (u, "(A)") call syntax_model_file_init () call global%global_init () libname = "integration_1" procname = "prc_config_a" call prepare_test_library (global, libname, 1) call compile_library (libname, global) call global%set_string (var_str ("$run_id"), & var_str ("integrations1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%write (u, vars = [ & var_str ("$method"), & var_str ("sqrts"), & var_str ("$integration_method"), & var_str ("$phs_method"), & var_str ("$run_id")]) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_1" end subroutine integrations_1 @ %def integrations_1 @ \subsubsection{Integration with cuts} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) with cuts set. <>= call test (integrations_2, "integrations_2", & "intrinsic test process with cut", & u, results) <>= public :: integrations_2 <>= subroutine integrations_2 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global type(string_t) :: cut_expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: parse_tree type(string_t), dimension(0) :: empty_string_array write (u, "(A)") "* Test output: integrations_2" write (u, "(A)") "* Purpose: integrate test process with cut" write (u, "(A)") call syntax_model_file_init () call global%global_init () write (u, "(A)") "* Prepare a cut expression" write (u, "(A)") call syntax_pexpr_init () cut_expr_text = "all Pt > 100 [s]" call ifile_append (ifile, cut_expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (parse_tree, stream, .true.) global%pn%cuts_lexpr => parse_tree%get_root_ptr () write (u, "(A)") "* Build and initialize a test process" write (u, "(A)") libname = "integration_3" procname = "prc_config_a" call prepare_test_library (global, libname, 1) call compile_library (libname, global) call global%set_string (var_str ("$run_id"), & var_str ("integrations1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%write (u, vars = empty_string_array) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_2" end subroutine integrations_2 @ %def integrations_2 @ \subsubsection{Standard phase space} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) using the default ([[phs_wood]]) phase-space implementation. We use an explicit phase-space configuration file with a single channel and integrate by [[mci_midpoint]]. <>= call test (integrations_3, "integrations_3", & "standard phase space", & u, results) <>= public :: integrations_3 <>= subroutine integrations_3 (u) <> <> use interactions, only: reset_interaction_counter use models use rt_data use process_configurations_ut, only: prepare_test_library use compilations, only: compile_library use integrations implicit none integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global integer :: u_phs write (u, "(A)") "* Test output: integrations_3" write (u, "(A)") "* Purpose: integrate test process" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () libname = "integration_3" procname = "prc_config_a" call prepare_test_library (global, libname, 1) call compile_library (libname, global) call global%set_string (var_str ("$run_id"), & var_str ("integrations1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("default"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?phs_s_mapping"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) write (u, "(A)") "* Create a scratch phase-space file" write (u, "(A)") u_phs = free_unit () open (u_phs, file = "integrations_3.phs", & status = "replace", action = "write") call write_test_phs_file (u_phs, var_str ("prc_config_a_i1")) close (u_phs) call global%set_string (var_str ("$phs_file"),& var_str ("integrations_3.phs"), is_known = .true.) call global%it_list%init ([1], [1000]) write (u, "(A)") "* Integrate" write (u, "(A)") call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%write (u, vars = [ & var_str ("$phs_method"), & var_str ("$phs_file")]) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_3" end subroutine integrations_3 @ %def integrations_3 @ \subsubsection{VAMP integration} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) using the single-channel ([[phs_single]]) phase-space implementation. The integration method is [[vamp]]. <>= call test (integrations_4, "integrations_4", & "VAMP integration (one iteration)", & u, results) <>= public :: integrations_4 <>= subroutine integrations_4 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global write (u, "(A)") "* Test output: integrations_4" write (u, "(A)") "* Purpose: integrate test process using VAMP" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call global%global_init () libname = "integrations_4_lib" procname = "integrations_4" call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .false., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) write (u, "(A)") "* Integrate" write (u, "(A)") call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%pacify (efficiency_reset = .true., error_reset = .true.) call global%write (u, vars = [var_str ("$integration_method")], & pacify = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_4" end subroutine integrations_4 @ %def integrations_4 @ \subsubsection{Multiple iterations integration} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) using the single-channel ([[phs_single]]) phase-space implementation. The integration method is [[vamp]]. We launch three iterations. <>= call test (integrations_5, "integrations_5", & "VAMP integration (three iterations)", & u, results) <>= public :: integrations_5 <>= subroutine integrations_5 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global write (u, "(A)") "* Test output: integrations_5" write (u, "(A)") "* Purpose: integrate test process using VAMP" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call global%global_init () libname = "integrations_5_lib" procname = "integrations_5" call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .false., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([3], [1000]) write (u, "(A)") "* Integrate" write (u, "(A)") call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%pacify (efficiency_reset = .true., error_reset = .true.) call global%write (u, vars = [var_str ("$integration_method")], & pacify = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_5" end subroutine integrations_5 @ %def integrations_5 @ \subsubsection{Multiple passes integration} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) using the single-channel ([[phs_single]]) phase-space implementation. The integration method is [[vamp]]. We launch three passes with three iterations each. <>= call test (integrations_6, "integrations_6", & "VAMP integration (three passes)", & u, results) <>= public :: integrations_6 <>= subroutine integrations_6 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global type(string_t), dimension(0) :: no_vars write (u, "(A)") "* Test output: integrations_6" write (u, "(A)") "* Purpose: integrate test process using VAMP" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call global%global_init () libname = "integrations_6_lib" procname = "integrations_6" call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .false., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([3, 3, 3], [1000, 1000, 1000], & adapt = [.true., .true., .false.], & adapt_code = [var_str ("wg"), var_str ("g"), var_str ("")]) write (u, "(A)") "* Integrate" write (u, "(A)") call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%pacify (efficiency_reset = .true., error_reset = .true.) call global%write (u, vars = no_vars, pacify = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_6" end subroutine integrations_6 @ %def integrations_6 @ \subsubsection{VAMP and default phase space} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) using the default ([[phs_wood]]) phase-space implementation. The integration method is [[vamp]]. We launch three passes with three iterations each. We enable channel equivalences and groves. <>= call test (integrations_7, "integrations_7", & "VAMP integration with wood phase space", & u, results) <>= public :: integrations_7 <>= subroutine integrations_7 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global type(string_t), dimension(0) :: no_vars integer :: iostat, u_phs character(95) :: buffer type(string_t) :: phs_file logical :: exist write (u, "(A)") "* Test output: integrations_7" write (u, "(A)") "* Purpose: integrate test process using VAMP" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () libname = "integrations_7_lib" procname = "integrations_7" call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?phs_s_mapping"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([3, 3, 3], [1000, 1000, 1000], & adapt = [.true., .true., .false.], & adapt_code = [var_str ("wg"), var_str ("g"), var_str ("")]) write (u, "(A)") "* Integrate" write (u, "(A)") call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%pacify (efficiency_reset = .true., error_reset = .true.) call global%write (u, vars = no_vars, pacify = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Generated phase-space file" write (u, "(A)") phs_file = procname // ".r1.i1.phs" inquire (file = char (phs_file), exist = exist) if (exist) then u_phs = free_unit () open (u_phs, file = char (phs_file), action = "read", status = "old") iostat = 0 do while (iostat == 0) read (u_phs, "(A)", iostat = iostat) buffer if (iostat == 0) write (u, "(A)") trim (buffer) end do close (u_phs) else write (u, "(A)") "[file is missing]" end if write (u, "(A)") write (u, "(A)") "* Test output end: integrations_7" end subroutine integrations_7 @ %def integrations_7 @ \subsubsection{Structure functions} Compile and integrate an intrinsic test matrix element ([[prc_test]] type) using the default ([[phs_wood]]) phase-space implementation. The integration method is [[vamp]]. There is a structure function of type [[unit_test]]. We use a test structure function $f(x)=x$ for both beams. Together with the $1/x_1x_2$ factor from the phase-space flux and a unit matrix element, we should get the same result as previously for the process without structure functions. There is a slight correction due to the $m_s$ mass which we set to zero here. <>= call test (integrations_8, "integrations_8", & "integration with structure function", & u, results) <>= public :: integrations_8 <>= subroutine integrations_8 (u) <> <> use interactions, only: reset_interaction_counter use phs_forests use models use rt_data use process_configurations_ut, only: prepare_test_library use compilations, only: compile_library use integrations implicit none integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global type(flavor_t) :: flv type(string_t) :: name write (u, "(A)") "* Test output: integrations_8" write (u, "(A)") "* Purpose: integrate test process using VAMP & &with structure function" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () libname = "integrations_8_lib" procname = "integrations_8" call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?phs_s_mapping"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), 0._default) call reset_interaction_counter () call flv%init (25, global%model) name = flv%get_name () call global%beam_structure%init_sf ([name, name], [1]) call global%beam_structure%set_sf (1, 1, var_str ("sf_test_1")) write (u, "(A)") "* Integrate" write (u, "(A)") call global%it_list%init ([1], [1000]) call integrate_process (procname, global, local_stack=.true.) call global%write (u, vars = [var_str ("ms")]) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_8" end subroutine integrations_8 @ %def integrations_8 @ \subsubsection{Integration with sign change} Compile and integrate an intrinsic test matrix element ([[prc_test]] type). The phase-space implementation is [[phs_single]] (single-particle phase space), the integrator is [[mci_midpoint]]. The weight that is applied changes the sign in half of phase space. The weight is $-3$ and $1$, respectively, so the total result is equal to the original, but negative sign. The efficiency should (approximately) become the average of $1$ and $1/3$, that is $2/3$. <>= call test (integrations_9, "integrations_9", & "handle sign change", & u, results) <>= public :: integrations_9 <>= subroutine integrations_9 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global type(string_t) :: wgt_expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: parse_tree write (u, "(A)") "* Test output: integrations_9" write (u, "(A)") "* Purpose: integrate test process" write (u, "(A)") call syntax_model_file_init () call global%global_init () write (u, "(A)") "* Prepare a weight expression" write (u, "(A)") call syntax_pexpr_init () wgt_expr_text = "eval 2 * sgn (Pz) - 1 [s]" call ifile_append (ifile, wgt_expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (parse_tree, stream, .true.) global%pn%weight_expr => parse_tree%get_root_ptr () write (u, "(A)") "* Build and evaluate a test process" write (u, "(A)") libname = "integration_9" procname = "prc_config_a" call prepare_test_library (global, libname, 1) call compile_library (libname, global) call global%set_string (var_str ("$run_id"), & var_str ("integrations1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true.) call global%write (u, vars = [ & var_str ("$method"), & var_str ("sqrts"), & var_str ("$integration_method"), & var_str ("$phs_method"), & var_str ("$run_id")]) call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_9" end subroutine integrations_9 @ %def integrations_9 @ \subsubsection{Integration history for VAMP integration with default phase space} This test is only run when event analysis can be done. <>= call test (integrations_history_1, "integrations_history_1", & "Test integration history files", & u, results) <>= public :: integrations_history_1 <>= subroutine integrations_history_1 (u) integer, intent(in) :: u type(string_t) :: libname, procname type(rt_data_t), target :: global type(string_t), dimension(0) :: no_vars integer :: iostat, u_his character(91) :: buffer type(string_t) :: his_file, ps_file, pdf_file logical :: exist, exist_ps, exist_pdf write (u, "(A)") "* Test output: integrations_history_1" write (u, "(A)") "* Purpose: test integration history files" write (u, "(A)") write (u, "(A)") "* Initialize process and parameters" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () libname = "integrations_history_1_lib" procname = "integrations_history_1" call global%set_log (var_str ("?vis_history"), & .true., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?phs_s_mapping"),& .false., is_known = .true.) call prepare_test_library (global, libname, 1, [procname]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_real (var_str ("error_threshold"),& 5E-6_default, is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known=.true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([2, 2, 2], [1000, 1000, 1000], & adapt = [.true., .true., .false.], & adapt_code = [var_str ("wg"), var_str ("g"), var_str ("")]) write (u, "(A)") "* Integrate" write (u, "(A)") call reset_interaction_counter () call integrate_process (procname, global, local_stack=.true., & eff_reset = .true.) call global%pacify (efficiency_reset = .true., error_reset = .true.) call global%write (u, vars = no_vars, pacify = .true.) write (u, "(A)") write (u, "(A)") "* Generated history files" write (u, "(A)") his_file = procname // ".r1.history.tex" ps_file = procname // ".r1.history.ps" pdf_file = procname // ".r1.history.pdf" inquire (file = char (his_file), exist = exist) if (exist) then u_his = free_unit () open (u_his, file = char (his_file), action = "read", status = "old") iostat = 0 do while (iostat == 0) read (u_his, "(A)", iostat = iostat) buffer if (iostat == 0) write (u, "(A)") trim (buffer) end do close (u_his) else write (u, "(A)") "[History LaTeX file is missing]" end if inquire (file = char (ps_file), exist = exist_ps) if (exist_ps) then write (u, "(A)") "[History Postscript file exists and is nonempty]" else write (u, "(A)") "[History Postscript file is missing/non-regular]" end if inquire (file = char (pdf_file), exist = exist_pdf) if (exist_pdf) then write (u, "(A)") "[History PDF file exists and is nonempty]" else write (u, "(A)") "[History PDF file is missing/non-regular]" end if write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: integrations_history_1" end subroutine integrations_history_1 @ %def integrations_history_1 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Event Streams} This module manages I/O from/to multiple concurrent event streams. Usually, there is at most one input stream, but several output streams. For the latter, we set up an array which can hold [[eio_t]] (event I/O) objects of different dynamic types simultaneously. One of them may be marked as an input channel. <<[[event_streams.f90]]>>= <> module event_streams <> use io_units use diagnostics use events use event_handles, only: event_handle_t use eio_data use eio_base use rt_data use dispatch_transforms, only: dispatch_eio <> <> <> contains <> end module event_streams @ %def event_streams @ \subsection{Event Stream Array} Each entry is an [[eio_t]] object. Since the type is dynamic, we need a wrapper: <>= type :: event_stream_entry_t class(eio_t), allocatable :: eio end type event_stream_entry_t @ %def event_stream_entry_t @ An array of event-stream entry objects. If one of the entries is an input channel, [[i_in]] is the corresponding index. <>= public :: event_stream_array_t <>= type :: event_stream_array_t type(event_stream_entry_t), dimension(:), allocatable :: entry integer :: i_in = 0 contains <> end type event_stream_array_t @ %def event_stream_array_t @ Output. <>= procedure :: write => event_stream_array_write <>= subroutine event_stream_array_write (object, unit) class(event_stream_array_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) write (u, "(1x,A)") "Event stream array:" if (allocated (object%entry)) then select case (size (object%entry)) case (0) write (u, "(3x,A)") "[empty]" case default do i = 1, size (object%entry) if (i == object%i_in) write (u, "(1x,A)") "Input stream:" call object%entry(i)%eio%write (u) end do end select else write (u, "(3x,A)") "[undefined]" end if end subroutine event_stream_array_write @ %def event_stream_array_write @ Check if there is content. <>= procedure :: is_valid => event_stream_array_is_valid <>= function event_stream_array_is_valid (es_array) result (flag) class(event_stream_array_t), intent(in) :: es_array logical :: flag flag = allocated (es_array%entry) end function event_stream_array_is_valid @ %def event_stream_array_is_valid @ Finalize all streams. <>= procedure :: final => event_stream_array_final <>= subroutine event_stream_array_final (es_array) class(event_stream_array_t), intent(inout) :: es_array integer :: i if (allocated (es_array%entry)) then do i = 1, size (es_array%entry) call es_array%entry(i)%eio%final () end do end if end subroutine event_stream_array_final @ %def event_stream_array_final @ Initialization. We use a generic [[sample]] name, open event I/O objects for all provided stream types (using the [[dispatch_eio]] routine), and initialize for the given list of process pointers. If there is an [[input]] argument, this channel is initialized as an input channel and appended to the array. The [[input_data]] or, if not present, [[data]] may be modified. This happens if we open a stream for reading and get new information there. <>= procedure :: init => event_stream_array_init <>= subroutine event_stream_array_init & (es_array, sample, stream_fmt, global, & data, input, input_sample, input_data, allow_switch, & checkpoint, callback, & error) class(event_stream_array_t), intent(out) :: es_array type(string_t), intent(in) :: sample type(string_t), dimension(:), intent(in) :: stream_fmt type(rt_data_t), intent(in) :: global type(event_sample_data_t), intent(inout), optional :: data type(string_t), intent(in), optional :: input type(string_t), intent(in), optional :: input_sample type(event_sample_data_t), intent(inout), optional :: input_data logical, intent(in), optional :: allow_switch integer, intent(in), optional :: checkpoint integer, intent(in), optional :: callback logical, intent(out), optional :: error type(string_t) :: sample_in integer :: n, i, n_output, i_input, i_checkpoint, i_callback logical :: success, switch if (present (input_sample)) then sample_in = input_sample else sample_in = sample end if if (present (allow_switch)) then switch = allow_switch else switch = .true. end if if (present (error)) then error = .false. end if n = size (stream_fmt) n_output = n if (present (input)) then n = n + 1 i_input = n else i_input = 0 end if if (present (checkpoint)) then n = n + 1 i_checkpoint = n else i_checkpoint = 0 end if if (present (callback)) then n = n + 1 i_callback = n else i_callback = 0 end if allocate (es_array%entry (n)) if (i_checkpoint > 0) then call dispatch_eio & (es_array%entry(i_checkpoint)%eio, var_str ("checkpoint"), & global%var_list, global%fallback_model, & global%event_callback) call es_array%entry(i_checkpoint)%eio%init_out (sample, data) end if if (i_callback > 0) then call dispatch_eio & (es_array%entry(i_callback)%eio, var_str ("callback"), & global%var_list, global%fallback_model, & global%event_callback) call es_array%entry(i_callback)%eio%init_out (sample, data) end if if (i_input > 0) then call dispatch_eio (es_array%entry(i_input)%eio, input, & global%var_list, global%fallback_model, & global%event_callback) if (present (input_data)) then call es_array%entry(i_input)%eio%init_in & (sample_in, input_data, success) else call es_array%entry(i_input)%eio%init_in & (sample_in, data, success) end if if (success) then es_array%i_in = i_input else if (present (input_sample)) then if (present (error)) then error = .true. else call msg_fatal ("Events: & ¶meter mismatch in input, aborting") end if else call msg_message ("Events: & ¶meter mismatch, discarding old event set") call es_array%entry(i_input)%eio%final () if (switch) then call msg_message ("Events: generating new events") call es_array%entry(i_input)%eio%init_out (sample, data) end if end if end if do i = 1, n_output call dispatch_eio (es_array%entry(i)%eio, stream_fmt(i), & global%var_list, global%fallback_model, & global%event_callback) call es_array%entry(i)%eio%init_out (sample, data) end do end subroutine event_stream_array_init @ %def event_stream_array_init @ Switch the (only) input channel to an output channel, so further events are appended to the respective stream. <>= procedure :: switch_inout => event_stream_array_switch_inout <>= subroutine event_stream_array_switch_inout (es_array) class(event_stream_array_t), intent(inout) :: es_array integer :: n if (es_array%has_input ()) then n = es_array%i_in call es_array%entry(n)%eio%switch_inout () es_array%i_in = 0 else call msg_bug ("Reading events: switch_inout: no input stream selected") end if end subroutine event_stream_array_switch_inout @ %def event_stream_array_switch_inout @ Output an event (with given process number) to all output streams. If there is no output stream, do nothing. <>= procedure :: output => event_stream_array_output <>= subroutine event_stream_array_output & (es_array, event, i_prc, event_index, passed, pacify, event_handle) class(event_stream_array_t), intent(inout) :: es_array type(event_t), intent(in), target :: event integer, intent(in) :: i_prc, event_index logical, intent(in), optional :: passed, pacify class(event_handle_t), intent(inout), optional :: event_handle logical :: increased integer :: i do i = 1, size (es_array%entry) if (i /= es_array%i_in) then associate (eio => es_array%entry(i)%eio) if (eio%split) then if (eio%split_n_evt > 0 .and. event_index > 1) then if (mod (event_index, eio%split_n_evt) == 1) then call eio%split_out () end if else if (eio%split_n_kbytes > 0) then call eio%update_split_count (increased) if (increased) call eio%split_out () end if end if call eio%output (event, i_prc, reading = es_array%i_in /= 0, & passed = passed, & pacify = pacify, & event_handle = event_handle) end associate end if end do end subroutine event_stream_array_output @ %def event_stream_array_output @ Input the [[i_prc]] index which selects the process for the current event. This is separated from reading the event, because it determines which event record to read. [[iostat]] may indicate an error or an EOF condition, as usual. <>= procedure :: input_i_prc => event_stream_array_input_i_prc <>= subroutine event_stream_array_input_i_prc (es_array, i_prc, iostat) class(event_stream_array_t), intent(inout) :: es_array integer, intent(out) :: i_prc integer, intent(out) :: iostat integer :: n if (es_array%has_input ()) then n = es_array%i_in call es_array%entry(n)%eio%input_i_prc (i_prc, iostat) else call msg_fatal ("Reading events: no input stream selected") end if end subroutine event_stream_array_input_i_prc @ %def event_stream_array_input_i_prc @ Input an event from the selected input stream. [[iostat]] may indicate an error or an EOF condition, as usual. <>= procedure :: input_event => event_stream_array_input_event <>= subroutine event_stream_array_input_event & (es_array, event, iostat, event_handle) class(event_stream_array_t), intent(inout) :: es_array type(event_t), intent(inout), target :: event integer, intent(out) :: iostat class(event_handle_t), intent(inout), optional :: event_handle integer :: n if (es_array%has_input ()) then n = es_array%i_in call es_array%entry(n)%eio%input_event (event, iostat, event_handle) else call msg_fatal ("Reading events: no input stream selected") end if end subroutine event_stream_array_input_event @ %def event_stream_array_input_event @ Skip an entry of eio\_t. Used to synchronize the event read-in for NLO events. <>= procedure :: skip_eio_entry => event_stream_array_skip_eio_entry <>= subroutine event_stream_array_skip_eio_entry (es_array, iostat) class(event_stream_array_t), intent(inout) :: es_array integer, intent(out) :: iostat integer :: n if (es_array%has_input ()) then n = es_array%i_in call es_array%entry(n)%eio%skip (iostat) else call msg_fatal ("Reading events: no input stream selected") end if end subroutine event_stream_array_skip_eio_entry @ %def event_stream_array_skip_eio_entry @ Return true if there is an input channel among the event streams. <>= procedure :: has_input => event_stream_array_has_input <>= function event_stream_array_has_input (es_array) result (flag) class(event_stream_array_t), intent(in) :: es_array logical :: flag flag = es_array%i_in /= 0 end function event_stream_array_has_input @ %def event_stream_array_has_input @ \subsection{Unit Tests} Test module, followed by the stand-alone unit-test procedures. <<[[event_streams_ut.f90]]>>= <> module event_streams_ut use unit_tests use event_streams_uti <> <> contains <> end module event_streams_ut @ <<[[event_streams_uti.f90]]>>= <> module event_streams_uti <> <> use model_data use eio_data use process, only: process_t use instances, only: process_instance_t use models use rt_data use events use event_streams <> <> contains <> end module event_streams_uti @ %def event_streams_uti @ API: driver for the unit tests below. <>= public :: event_streams_test <>= subroutine event_streams_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine event_streams_test @ %def event_streams_test @ \subsubsection{Empty event stream} This should set up an empty event output stream array, including initialization, output, and finalization (which are all no-ops). <>= call test (event_streams_1, "event_streams_1", & "empty event stream array", & u, results) <>= public :: event_streams_1 <>= subroutine event_streams_1 (u) integer, intent(in) :: u type(event_stream_array_t) :: es_array type(rt_data_t) :: global type(event_t) :: event type(string_t) :: sample type(string_t), dimension(0) :: empty_string_array write (u, "(A)") "* Test output: event_streams_1" write (u, "(A)") "* Purpose: handle empty event stream array" write (u, "(A)") sample = "event_streams_1" call es_array%init (sample, empty_string_array, global) call es_array%output (event, 42, 1) call es_array%write (u) call es_array%final () write (u, "(A)") write (u, "(A)") "* Test output end: event_streams_1" end subroutine event_streams_1 @ %def event_streams_1 @ \subsubsection{Nontrivial event stream} Here we generate a trivial event and choose [[raw]] output as an entry in the stream array. <>= call test (event_streams_2, "event_streams_2", & "nontrivial event stream array", & u, results) <>= public :: event_streams_2 <>= subroutine event_streams_2 (u) use processes_ut, only: prepare_test_process integer, intent(in) :: u type(event_stream_array_t) :: es_array type(rt_data_t) :: global type(model_data_t), target :: model type(event_t), allocatable, target :: event type(process_t), allocatable, target :: process type(process_instance_t), allocatable, target :: process_instance type(string_t) :: sample type(string_t), dimension(0) :: empty_string_array integer :: i_prc, iostat write (u, "(A)") "* Test output: event_streams_2" write (u, "(A)") "* Purpose: handle empty event stream array" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call model%init_test () write (u, "(A)") "* Generate test process event" write (u, "(A)") allocate (process) allocate (process_instance) call prepare_test_process (process, process_instance, model, & run_id = var_str ("run_test")) call process_instance%setup_event_data () allocate (event) call event%basic_init () call event%connect (process_instance, process%get_model_ptr ()) call event%generate (1, [0.4_default, 0.4_default]) call event%set_index (42) call event%evaluate_expressions () call event%write (u) write (u, "(A)") write (u, "(A)") "* Allocate raw eio stream and write event to file" write (u, "(A)") sample = "event_streams_2" call es_array%init (sample, [var_str ("raw")], global) call es_array%output (event, 1, 1) call es_array%write (u) call es_array%final () write (u, "(A)") write (u, "(A)") "* Reallocate raw eio stream for reading" write (u, "(A)") sample = "foo" call es_array%init (sample, empty_string_array, global, & input = var_str ("raw"), input_sample = var_str ("event_streams_2")) call es_array%write (u) write (u, "(A)") write (u, "(A)") "* Reread event" write (u, "(A)") call es_array%input_i_prc (i_prc, iostat) write (u, "(1x,A,I0)") "i_prc = ", i_prc write (u, "(A)") call es_array%input_event (event, iostat) call es_array%final () call event%write (u) call global%final () call model%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: event_streams_2" end subroutine event_streams_2 @ %def event_streams_2 @ \subsubsection{Switch in/out} Here we generate an event file and test switching from writing to reading when the file is exhausted. <>= call test (event_streams_3, "event_streams_3", & "switch input/output", & u, results) <>= public :: event_streams_3 <>= subroutine event_streams_3 (u) use processes_ut, only: prepare_test_process integer, intent(in) :: u type(event_stream_array_t) :: es_array type(rt_data_t) :: global type(model_data_t), target :: model type(event_t), allocatable, target :: event type(process_t), allocatable, target :: process type(process_instance_t), allocatable, target :: process_instance type(string_t) :: sample type(string_t), dimension(0) :: empty_string_array integer :: i_prc, iostat write (u, "(A)") "* Test output: event_streams_3" write (u, "(A)") "* Purpose: handle in/out switching" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call model%init_test () write (u, "(A)") "* Generate test process event" write (u, "(A)") allocate (process) allocate (process_instance) call prepare_test_process (process, process_instance, model, & run_id = var_str ("run_test")) call process_instance%setup_event_data () allocate (event) call event%basic_init () call event%connect (process_instance, process%get_model_ptr ()) call event%generate (1, [0.4_default, 0.4_default]) call event%increment_index () call event%evaluate_expressions () write (u, "(A)") "* Allocate raw eio stream and write event to file" write (u, "(A)") sample = "event_streams_3" call es_array%init (sample, [var_str ("raw")], global) call es_array%output (event, 1, 1) call es_array%write (u) call es_array%final () write (u, "(A)") write (u, "(A)") "* Reallocate raw eio stream for reading" write (u, "(A)") call es_array%init (sample, empty_string_array, global, & input = var_str ("raw")) call es_array%write (u) write (u, "(A)") write (u, "(A)") "* Reread event" write (u, "(A)") call es_array%input_i_prc (i_prc, iostat) call es_array%input_event (event, iostat) write (u, "(A)") "* Attempt to read another event (fail), then generate" write (u, "(A)") call es_array%input_i_prc (i_prc, iostat) if (iostat < 0) then call es_array%switch_inout () call event%generate (1, [0.3_default, 0.3_default]) call event%increment_index () call event%evaluate_expressions () call es_array%output (event, 1, 2) end if call es_array%write (u) call es_array%final () write (u, "(A)") call event%write (u) write (u, "(A)") write (u, "(A)") "* Reallocate raw eio stream for reading" write (u, "(A)") call es_array%init (sample, empty_string_array, global, & input = var_str ("raw")) call es_array%write (u) write (u, "(A)") write (u, "(A)") "* Reread two events and display 2nd event" write (u, "(A)") call es_array%input_i_prc (i_prc, iostat) call es_array%input_event (event, iostat) call es_array%input_i_prc (i_prc, iostat) call es_array%input_event (event, iostat) call es_array%final () call event%write (u) call global%final () call model%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: event_streams_3" end subroutine event_streams_3 @ %def event_streams_3 @ \subsubsection{Checksum} Here we generate an event file and repeat twice, once with identical parameters and once with modified parameters. <>= call test (event_streams_4, "event_streams_4", & "check MD5 sum", & u, results) <>= public :: event_streams_4 <>= subroutine event_streams_4 (u) integer, intent(in) :: u type(event_stream_array_t) :: es_array type(rt_data_t) :: global type(process_t), allocatable, target :: process type(string_t) :: sample type(string_t), dimension(0) :: empty_string_array type(event_sample_data_t) :: data write (u, "(A)") "* Test output: event_streams_4" write (u, "(A)") "* Purpose: handle in/out switching" write (u, "(A)") write (u, "(A)") "* Generate test process event" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%set_log (var_str ("?check_event_file"), & .true., is_known = .true.) allocate (process) write (u, "(A)") "* Allocate raw eio stream for writing" write (u, "(A)") sample = "event_streams_4" data%md5sum_cfg = "1234567890abcdef1234567890abcdef" call es_array%init (sample, [var_str ("raw")], global, data) call es_array%write (u) call es_array%final () write (u, "(A)") write (u, "(A)") "* Reallocate raw eio stream for reading" write (u, "(A)") call es_array%init (sample, empty_string_array, global, & data, input = var_str ("raw")) call es_array%write (u) call es_array%final () write (u, "(A)") write (u, "(A)") "* Reallocate modified raw eio stream for reading (fail)" write (u, "(A)") data%md5sum_cfg = "1234567890______1234567890______" call es_array%init (sample, empty_string_array, global, & data, input = var_str ("raw")) call es_array%write (u) call es_array%final () write (u, "(A)") write (u, "(A)") "* Repeat ignoring checksum" write (u, "(A)") call global%set_log (var_str ("?check_event_file"), & .false., is_known = .true.) call es_array%init (sample, empty_string_array, global, & data, input = var_str ("raw")) call es_array%write (u) call es_array%final () call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: event_streams_4" end subroutine event_streams_4 @ %def event_streams_4 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Restricted Subprocesses} This module provides an automatic means to construct restricted subprocesses of a current process object. A restricted subprocess has the same initial and final state as the current process, but a restricted set of Feynman graphs. The actual application extracts the set of resonance histories that apply to the process and uses this to construct subprocesses that are restricted to one of those histories, respectively. The resonance histories are derived from the phase-space setup. This implies that the method is tied to the OMega matrix element generator and to the wood phase space method. The processes are collected in a new process library that is generated on-the-fly. The [[resonant_subprocess_t]] object is intended as a component of the event record, which manages all operations regarding resonance handling. The run-time calculations are delegated to an event transform ([[evt_resonance_t]]), as a part of the event transform chain. The transform selects one (or none) of the resonance histories, given the momentum configuration, computes matrix elements and inserts resonances into the particle set. <<[[restricted_subprocesses.f90]]>>= <> module restricted_subprocesses <> <> use diagnostics, only: msg_message, msg_fatal, msg_bug use diagnostics, only: signal_is_pending use io_units, only: given_output_unit use format_defs, only: FMT_14, FMT_19 use string_utils, only: str use lorentz, only: vector4_t use particle_specifiers, only: prt_spec_t use particles, only: particle_set_t use resonances, only: resonance_history_t, resonance_history_set_t use variables, only: var_list_t use models, only: model_t use process_libraries, only: process_component_def_t use process_libraries, only: process_library_t use process_libraries, only: STAT_ACTIVE use prclib_stacks, only: prclib_entry_t use event_transforms, only: evt_t use resonance_insertion, only: evt_resonance_t use rt_data, only: rt_data_t use compilations, only: compile_library use process_configurations, only: process_configuration_t use process, only: process_t, process_ptr_t use instances, only: process_instance_t, process_instance_ptr_t use integrations, only: integrate_process <> <> <> <> <> contains <> end module restricted_subprocesses @ %def restricted_subprocesses @ \subsection{Process configuration} We extend the [[process_configuration_t]] by another method for initialization that takes into account a resonance history. <>= public :: restricted_process_configuration_t <>= type, extends (process_configuration_t) :: restricted_process_configuration_t private contains <> end type restricted_process_configuration_t @ %def restricted_process_configuration_t @ Resonance history as an argument. We use it to override the [[restrictions]] setting in a local variable list. Since we can construct the restricted process only by using OMega, we enforce it as the ME method. Other settings are taken from the variable list. The model will most likely be set, but we insert a safeguard just in case. Also, the resonant subprocess should not itself spawn resonant subprocesses, so we unset [[?resonance_history]]. We have to create a local copy of the model here, via pointer allocation. The reason is that the model as stored (via pointer) in the base type will be finalized and deallocated. The current implementation will generate a LO process, the optional [[nlo_process]] is unset. (It is not obvious whether the construction makes sense beyond LO.) <>= procedure :: init_resonant_process <>= subroutine init_resonant_process & (prc_config, prc_name, prt_in, prt_out, res_history, model, var_list) class(restricted_process_configuration_t), intent(out) :: prc_config type(string_t), intent(in) :: prc_name type(prt_spec_t), dimension(:), intent(in) :: prt_in type(prt_spec_t), dimension(:), intent(in) :: prt_out type(resonance_history_t), intent(in) :: res_history type(model_t), intent(in), target :: model type(var_list_t), intent(in), target :: var_list type(model_t), pointer :: local_model type(var_list_t) :: local_var_list allocate (local_model) call local_model%init_instance (model) call local_var_list%link (var_list) call local_var_list%append_string (var_str ("$model_name"), & sval = local_model%get_name (), & intrinsic=.true.) call local_var_list%append_string (var_str ("$method"), & sval = var_str ("omega"), & intrinsic=.true.) call local_var_list%append_string (var_str ("$restrictions"), & sval = res_history%as_omega_string (size (prt_in)), & intrinsic = .true.) call local_var_list%append_log (var_str ("?resonance_history"), & lval = .false., & intrinsic = .true.) call prc_config%init (prc_name, size (prt_in), 1, & local_model, local_var_list) call prc_config%setup_component (1, & prt_in, prt_out, & local_model, local_var_list) end subroutine init_resonant_process @ %def init_resonant_process @ \subsection{Resonant-subprocess set manager} This data type enables generation of a library of resonant subprocesses for a given master process, and it allows for convenient access. The matrix elements from the subprocesses can be used as channel weights to activate a selector, which then returns a preferred channel via some random number generator. <>= public :: resonant_subprocess_set_t <>= type :: resonant_subprocess_set_t private integer, dimension(:), allocatable :: n_history type(resonance_history_set_t), dimension(:), allocatable :: res_history_set logical :: lib_active = .false. type(string_t) :: libname type(string_t), dimension(:), allocatable :: proc_id type(process_ptr_t), dimension(:), allocatable :: subprocess type(process_instance_ptr_t), dimension(:), allocatable :: instance logical :: filled = .false. type(evt_resonance_t), pointer :: evt => null () contains <> end type resonant_subprocess_set_t @ %def resonant_subprocess_set_t @ Output <>= procedure :: write => resonant_subprocess_set_write <>= subroutine resonant_subprocess_set_write (prc_set, unit, testflag) class(resonant_subprocess_set_t), intent(in) :: prc_set integer, intent(in), optional :: unit logical, intent(in), optional :: testflag logical :: truncate integer :: u, i u = given_output_unit (unit) truncate = .false.; if (present (testflag)) truncate = testflag write (u, "(1x,A)") "Resonant subprocess set:" if (allocated (prc_set%n_history)) then if (any (prc_set%n_history > 0)) then do i = 1, size (prc_set%n_history) if (prc_set%n_history(i) > 0) then write (u, "(1x,A,I0)") "Component #", i call prc_set%res_history_set(i)%write (u, indent=1) end if end do if (prc_set%lib_active) then write (u, "(3x,A,A,A)") "Process library = '", & char (prc_set%libname), "'" else write (u, "(3x,A)") "Process library: [inactive]" end if if (associated (prc_set%evt)) then if (truncate) then write (u, "(3x,A,1x," // FMT_14 // ")") & "Process sqme =", prc_set%get_master_sqme () else write (u, "(3x,A,1x," // FMT_19 // ")") & "Process sqme =", prc_set%get_master_sqme () end if end if if (associated (prc_set%evt)) then write (u, "(3x,A)") "Event transform: associated" write (u, "(2x)", advance="no") call prc_set%evt%write_selector (u, testflag) else write (u, "(3x,A)") "Event transform: not associated" end if else write (u, "(2x,A)") "[empty]" end if else write (u, "(3x,A)") "[not allocated]" end if end subroutine resonant_subprocess_set_write @ %def resonant_subprocess_set_write @ \subsection{Resonance history set} Initialize subprocess set with an array of pre-created resonance history sets. Safeguard: if there are no resonances in the input, initialize the local set as empty, but complete. <>= procedure :: init => resonant_subprocess_set_init procedure :: fill_resonances => resonant_subprocess_set_fill_resonances <>= subroutine resonant_subprocess_set_init (prc_set, n_component) class(resonant_subprocess_set_t), intent(out) :: prc_set integer, intent(in) :: n_component allocate (prc_set%res_history_set (n_component)) allocate (prc_set%n_history (n_component), source = 0) end subroutine resonant_subprocess_set_init subroutine resonant_subprocess_set_fill_resonances (prc_set, & res_history_set, i_component) class(resonant_subprocess_set_t), intent(inout) :: prc_set type(resonance_history_set_t), intent(in) :: res_history_set integer, intent(in) :: i_component prc_set%n_history(i_component) = res_history_set%get_n_history () if (prc_set%n_history(i_component) > 0) then prc_set%res_history_set(i_component) = res_history_set else call prc_set%res_history_set(i_component)%init (initial_size = 0) call prc_set%res_history_set(i_component)%freeze () end if end subroutine resonant_subprocess_set_fill_resonances @ %def resonant_subprocess_set_init @ %def resonant_subprocess_set_fill_resonances @ Return the resonance history set. <>= procedure :: get_resonance_history_set & => resonant_subprocess_set_get_resonance_history_set <>= function resonant_subprocess_set_get_resonance_history_set (prc_set) & result (res_history_set) class(resonant_subprocess_set_t), intent(in) :: prc_set type(resonance_history_set_t), dimension(:), allocatable :: res_history_set res_history_set = prc_set%res_history_set end function resonant_subprocess_set_get_resonance_history_set @ %def resonant_subprocess_set_get_resonance_history_set @ \subsection{Library for the resonance history set} The recommended library name: append [[_R]] to the process name. <>= public :: get_libname_res <>= elemental function get_libname_res (proc_id) result (libname) type(string_t), intent(in) :: proc_id type(string_t) :: libname libname = proc_id // "_R" end function get_libname_res @ %def get_libname_res @ Here we scan the global process library whether any processes require resonant subprocesses to be constructed. If yes, create process objects with phase space and construct the process libraries as usual. Then append the library names to the array. The temporary integration objects should carry the [[phs_only]] flag. We set this in the local environment. Once a process object with resonance histories (derived from phase space) has been created, we extract the resonance histories and use them, together with the process definition, to create the new library. Finally, compile the library. <>= public :: spawn_resonant_subprocess_libraries <>= subroutine spawn_resonant_subprocess_libraries & (libname, local, global, libname_res) type(string_t), intent(in) :: libname type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), target :: global type(string_t), dimension(:), allocatable, intent(inout) :: libname_res type(process_library_t), pointer :: lib type(string_t), dimension(:), allocatable :: process_id_res type(process_t), pointer :: process type(resonance_history_set_t) :: res_history_set type(process_component_def_t), pointer :: process_component_def logical :: phs_only_saved, exist integer :: i_proc, i_component lib => global%prclib_stack%get_library_ptr (libname) call lib%get_process_id_req_resonant (process_id_res) if (size (process_id_res) > 0) then call msg_message ("Creating resonant-subprocess libraries & &for library '" // char (libname) // "'") libname_res = get_libname_res (process_id_res) phs_only_saved = local%var_list%get_lval (var_str ("?phs_only")) call local%var_list%set_log & (var_str ("?phs_only"), .true., is_known=.true.) do i_proc = 1, size (process_id_res) associate (proc_id => process_id_res (i_proc)) call msg_message ("Process '" // char (proc_id) // "': & &constructing phase space for resonance structure") call integrate_process (proc_id, local, global) process => global%process_stack%get_process_ptr (proc_id) call create_library (libname_res(i_proc), global, exist) if (.not. exist) then do i_component = 1, process%get_n_components () call process%extract_resonance_history_set & (res_history_set, i_component = i_component) process_component_def & => process%get_component_def_ptr (i_component) call add_to_library (libname_res(i_proc), & res_history_set, & process_component_def%get_prt_spec_in (), & process_component_def%get_prt_spec_out (), & global) end do call msg_message ("Process library '" & // char (libname_res(i_proc)) & // "': created") end if call global%update_prclib (lib) end associate end do call local%var_list%set_log & (var_str ("?phs_only"), phs_only_saved, is_known=.true.) end if end subroutine spawn_resonant_subprocess_libraries @ %def spawn_resonant_subprocess_libraries @ This is another version of the library constructor, bound to a restricted-subprocess set object. Create the appropriate process library, add processes, and close the library. <>= procedure :: create_library => resonant_subprocess_set_create_library procedure :: add_to_library => resonant_subprocess_set_add_to_library procedure :: freeze_library => resonant_subprocess_set_freeze_library <>= subroutine resonant_subprocess_set_create_library (prc_set, & libname, global, exist) class(resonant_subprocess_set_t), intent(inout) :: prc_set type(string_t), intent(in) :: libname type(rt_data_t), intent(inout), target :: global logical, intent(out) :: exist prc_set%libname = libname call create_library (prc_set%libname, global, exist) end subroutine resonant_subprocess_set_create_library subroutine resonant_subprocess_set_add_to_library (prc_set, & i_component, prt_in, prt_out, global) class(resonant_subprocess_set_t), intent(inout) :: prc_set integer, intent(in) :: i_component type(prt_spec_t), dimension(:), intent(in) :: prt_in type(prt_spec_t), dimension(:), intent(in) :: prt_out type(rt_data_t), intent(inout), target :: global call add_to_library (prc_set%libname, & prc_set%res_history_set(i_component), & prt_in, prt_out, global) end subroutine resonant_subprocess_set_add_to_library subroutine resonant_subprocess_set_freeze_library (prc_set, global) class(resonant_subprocess_set_t), intent(inout) :: prc_set type(rt_data_t), intent(inout), target :: global type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib lib => global%prclib_stack%get_library_ptr (prc_set%libname) call lib%get_process_id_list (prc_set%proc_id) prc_set%lib_active = .true. end subroutine resonant_subprocess_set_freeze_library @ %def resonant_subprocess_set_create_library @ %def resonant_subprocess_set_add_to_library @ %def resonant_subprocess_set_freeze_library @ The common parts of the procedures above: (i) create a new process library or recover it, (ii) for each history, create a process configuration and record it. <>= subroutine create_library (libname, global, exist) type(string_t), intent(in) :: libname type(rt_data_t), intent(inout), target :: global logical, intent(out) :: exist type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib type(resonance_history_t) :: res_history type(string_t), dimension(:), allocatable :: proc_id type(restricted_process_configuration_t) :: prc_config integer :: i lib => global%prclib_stack%get_library_ptr (libname) exist = associated (lib) if (.not. exist) then call msg_message ("Creating library for resonant subprocesses '" & // char (libname) // "'") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) else call msg_message ("Using library for resonant subprocesses '" & // char (libname) // "'") call global%update_prclib (lib) end if end subroutine create_library subroutine add_to_library (libname, res_history_set, prt_in, prt_out, global) type(string_t), intent(in) :: libname type(resonance_history_set_t), intent(in) :: res_history_set type(prt_spec_t), dimension(:), intent(in) :: prt_in type(prt_spec_t), dimension(:), intent(in) :: prt_out type(rt_data_t), intent(inout), target :: global type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib type(resonance_history_t) :: res_history type(string_t), dimension(:), allocatable :: proc_id type(restricted_process_configuration_t) :: prc_config integer :: n0, i lib => global%prclib_stack%get_library_ptr (libname) if (associated (lib)) then n0 = lib%get_n_processes () allocate (proc_id (res_history_set%get_n_history ())) do i = 1, size (proc_id) proc_id(i) = libname // str (n0 + i) res_history = res_history_set%get_history(i) call prc_config%init_resonant_process (proc_id(i), & prt_in, prt_out, & res_history, & global%model, global%var_list) call msg_message ("Resonant subprocess #" & // char (str(n0+i)) // ": " & // char (res_history%as_omega_string (size (prt_in)))) call prc_config%record (global) if (signal_is_pending ()) return end do else call msg_bug ("Adding subprocesses: library '" & // char (libname) // "' not found") end if end subroutine add_to_library @ %def create_library @ %def add_to_library @ Compile the generated library, required settings taken from the [[global]] data set. <>= procedure :: compile_library => resonant_subprocess_set_compile_library <>= subroutine resonant_subprocess_set_compile_library (prc_set, global) class(resonant_subprocess_set_t), intent(in) :: prc_set type(rt_data_t), intent(inout), target :: global type(process_library_t), pointer :: lib lib => global%prclib_stack%get_library_ptr (prc_set%libname) if (lib%get_status () < STAT_ACTIVE) then call compile_library (prc_set%libname, global) end if end subroutine resonant_subprocess_set_compile_library @ %def resonant_subprocess_set_compile_library @ Check if the library has been created / the process has been evaluated. <>= procedure :: is_active => resonant_subprocess_set_is_active <>= function resonant_subprocess_set_is_active (prc_set) result (flag) class(resonant_subprocess_set_t), intent(in) :: prc_set logical :: flag flag = prc_set%lib_active end function resonant_subprocess_set_is_active @ %def resonant_subprocess_set_is_active @ Return number of generated process objects, library, and process IDs. <>= procedure :: get_n_process => resonant_subprocess_set_get_n_process procedure :: get_libname => resonant_subprocess_set_get_libname procedure :: get_proc_id => resonant_subprocess_set_get_proc_id <>= function resonant_subprocess_set_get_n_process (prc_set) result (n) class(resonant_subprocess_set_t), intent(in) :: prc_set integer :: n if (prc_set%lib_active) then n = size (prc_set%proc_id) else n = 0 end if end function resonant_subprocess_set_get_n_process function resonant_subprocess_set_get_libname (prc_set) result (libname) class(resonant_subprocess_set_t), intent(in) :: prc_set type(string_t) :: libname if (prc_set%lib_active) then libname = prc_set%libname else libname = "" end if end function resonant_subprocess_set_get_libname function resonant_subprocess_set_get_proc_id (prc_set, i) result (proc_id) class(resonant_subprocess_set_t), intent(in) :: prc_set integer, intent(in) :: i type(string_t) :: proc_id if (allocated (prc_set%proc_id)) then proc_id = prc_set%proc_id(i) else proc_id = "" end if end function resonant_subprocess_set_get_proc_id @ %def resonant_subprocess_set_get_n_process @ %def resonant_subprocess_set_get_libname @ %def resonant_subprocess_set_get_proc_id @ \subsection{Process objects and instances} Prepare process objects for all entries in the resonant-subprocesses library. The process objects are appended to the global process stack. A local environment can be used where we place temporary variable settings that affect process-object generation. We initialize the processes, such that we can evaluate matrix elements, but we do not need to integrate them. The internal procedure [[prepare_process]] is an abridged version of the procedure with this name in the [[simulations]] module. <>= procedure :: prepare_process_objects & => resonant_subprocess_set_prepare_process_objects <>= subroutine resonant_subprocess_set_prepare_process_objects & (prc_set, local, global) class(resonant_subprocess_set_t), intent(inout) :: prc_set type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global type(rt_data_t), pointer :: current type(process_library_t), pointer :: lib type(string_t) :: phs_method_saved, integration_method_saved type(string_t) :: proc_id, libname_cur, libname_res integer :: i, n if (.not. prc_set%is_active ()) return if (present (global)) then current => global else current => local end if libname_cur = current%prclib%get_name () libname_res = prc_set%get_libname () lib => current%prclib_stack%get_library_ptr (libname_res) if (associated (lib)) call current%update_prclib (lib) phs_method_saved = local%get_sval (var_str ("$phs_method")) integration_method_saved = local%get_sval (var_str ("$integration_method")) call local%set_string (var_str ("$phs_method"), & var_str ("none"), is_known = .true.) call local%set_string (var_str ("$integration_method"), & var_str ("none"), is_known = .true.) n = prc_set%get_n_process () allocate (prc_set%subprocess (n)) do i = 1, n proc_id = prc_set%get_proc_id (i) call prepare_process (prc_set%subprocess(i)%p, proc_id) if (signal_is_pending ()) return end do call local%set_string (var_str ("$phs_method"), & phs_method_saved, is_known = .true.) call local%set_string (var_str ("$integration_method"), & integration_method_saved, is_known = .true.) lib => current%prclib_stack%get_library_ptr (libname_cur) if (associated (lib)) call current%update_prclib (lib) contains subroutine prepare_process (process, process_id) type(process_t), pointer, intent(out) :: process type(string_t), intent(in) :: process_id call msg_message ("Simulate: initializing resonant subprocess '" & // char (process_id) // "'") if (present (global)) then call integrate_process (process_id, local, global, & init_only = .true.) else call integrate_process (process_id, local, local_stack = .true., & init_only = .true.) end if process => current%process_stack%get_process_ptr (process_id) if (.not. associated (process)) then call msg_fatal ("Simulate: resonant subprocess '" & // char (process_id) // "' could not be initialized: aborting") end if end subroutine prepare_process end subroutine resonant_subprocess_set_prepare_process_objects @ %def resonant_subprocess_set_prepare_process_objects @ Workspace for the resonant subprocesses. <>= procedure :: prepare_process_instances & => resonant_subprocess_set_prepare_process_instances <>= subroutine resonant_subprocess_set_prepare_process_instances (prc_set, global) class(resonant_subprocess_set_t), intent(inout) :: prc_set type(rt_data_t), intent(in), target :: global integer :: i, n if (.not. prc_set%is_active ()) return n = size (prc_set%subprocess) allocate (prc_set%instance (n)) do i = 1, n allocate (prc_set%instance(i)%p) call prc_set%instance(i)%p%init (prc_set%subprocess(i)%p) call prc_set%instance(i)%p%setup_event_data (global%model) end do end subroutine resonant_subprocess_set_prepare_process_instances @ %def resonant_subprocess_set_prepare_process_instances @ \subsection{Event transform connection} The idea is that the resonance-insertion event transform has been allocated somewhere (namely, in the standard event-transform chain), but we maintain a link such that we can inject matrix-element results event by event. The event transform holds a selector, to choose one of the resonance histories (or none), and it manages resonance insertion for the particle set. The data that the event transform requires can be provided here. The resonance history set has already been assigned with the [[dispatch]] initializer. Here, we supply the set of subprocess instances that we have generated (see above). The master-process instance is set when we [[connect]] the transform by the standard method. <>= procedure :: connect_transform => & resonant_subprocess_set_connect_transform <>= subroutine resonant_subprocess_set_connect_transform (prc_set, evt) class(resonant_subprocess_set_t), intent(inout) :: prc_set class(evt_t), intent(in), target :: evt select type (evt) type is (evt_resonance_t) prc_set%evt => evt call prc_set%evt%set_subprocess_instances (prc_set%instance) class default call msg_bug ("Resonant subprocess set: event transform has wrong type") end select end subroutine resonant_subprocess_set_connect_transform @ %def resonant_subprocess_set_connect_transform @ Set the on-shell limit value in the connected transform. <>= procedure :: set_on_shell_limit => resonant_subprocess_set_on_shell_limit <>= subroutine resonant_subprocess_set_on_shell_limit (prc_set, on_shell_limit) class(resonant_subprocess_set_t), intent(inout) :: prc_set real(default), intent(in) :: on_shell_limit call prc_set%evt%set_on_shell_limit (on_shell_limit) end subroutine resonant_subprocess_set_on_shell_limit @ %def resonant_subprocess_set_on_shell_limit @ Set the Gaussian turnoff parameter in the connected transform. <>= procedure :: set_on_shell_turnoff => resonant_subprocess_set_on_shell_turnoff <>= subroutine resonant_subprocess_set_on_shell_turnoff & (prc_set, on_shell_turnoff) class(resonant_subprocess_set_t), intent(inout) :: prc_set real(default), intent(in) :: on_shell_turnoff call prc_set%evt%set_on_shell_turnoff (on_shell_turnoff) end subroutine resonant_subprocess_set_on_shell_turnoff @ %def resonant_subprocess_set_on_shell_turnoff @ Reweight (suppress) the background contribution probability, for the kinematics where a resonance history is active. <>= procedure :: set_background_factor & => resonant_subprocess_set_background_factor <>= subroutine resonant_subprocess_set_background_factor & (prc_set, background_factor) class(resonant_subprocess_set_t), intent(inout) :: prc_set real(default), intent(in) :: background_factor call prc_set%evt%set_background_factor (background_factor) end subroutine resonant_subprocess_set_background_factor @ %def resonant_subprocess_set_background_factor @ \subsection{Wrappers for runtime calculations} All runtime calculations are delegated to the event transform. The following procedures are essentially redundant wrappers. We retain them for a unit test below. Debugging aid: <>= procedure :: dump_instances => resonant_subprocess_set_dump_instances <>= subroutine resonant_subprocess_set_dump_instances (prc_set, unit, testflag) class(resonant_subprocess_set_t), intent(inout) :: prc_set integer, intent(in), optional :: unit logical, intent(in), optional :: testflag integer :: i, n, u u = given_output_unit (unit) write (u, "(A)") "*** Process instances of resonant subprocesses" write (u, *) n = size (prc_set%subprocess) do i = 1, n associate (instance => prc_set%instance(i)%p) call instance%write (u, testflag) write (u, *) write (u, *) end associate end do end subroutine resonant_subprocess_set_dump_instances @ %def resonant_subprocess_set_dump_instances @ Inject the current kinematics configuration, reading from the previous event transform or from the process instance. <>= procedure :: fill_momenta => resonant_subprocess_set_fill_momenta <>= subroutine resonant_subprocess_set_fill_momenta (prc_set) class(resonant_subprocess_set_t), intent(inout) :: prc_set integer :: i, n call prc_set%evt%fill_momenta () end subroutine resonant_subprocess_set_fill_momenta @ %def resonant_subprocess_set_fill_momenta @ Determine the indices of the resonance histories that can be considered on-shell for the current kinematics. <>= procedure :: determine_on_shell_histories & => resonant_subprocess_set_determine_on_shell_histories <>= subroutine resonant_subprocess_set_determine_on_shell_histories & (prc_set, i_component, index_array) class(resonant_subprocess_set_t), intent(in) :: prc_set integer, intent(in) :: i_component integer, dimension(:), allocatable, intent(out) :: index_array call prc_set%evt%determine_on_shell_histories (index_array) end subroutine resonant_subprocess_set_determine_on_shell_histories @ %def resonant_subprocess_set_determine_on_shell_histories @ Evaluate selected subprocesses. (In actual operation, the ones that have been tagged as on-shell.) <>= procedure :: evaluate_subprocess & => resonant_subprocess_set_evaluate_subprocess <>= subroutine resonant_subprocess_set_evaluate_subprocess (prc_set, index_array) class(resonant_subprocess_set_t), intent(inout) :: prc_set integer, dimension(:), intent(in) :: index_array call prc_set%evt%evaluate_subprocess (index_array) end subroutine resonant_subprocess_set_evaluate_subprocess @ %def resonant_subprocess_set_evaluate_subprocess @ Extract the matrix elements of the master process / the resonant subprocesses. After the previous routine has been executed, they should be available and stored in the corresponding process instances. <>= procedure :: get_master_sqme & => resonant_subprocess_set_get_master_sqme procedure :: get_subprocess_sqme & => resonant_subprocess_set_get_subprocess_sqme <>= function resonant_subprocess_set_get_master_sqme (prc_set) result (sqme) class(resonant_subprocess_set_t), intent(in) :: prc_set real(default) :: sqme sqme = prc_set%evt%get_master_sqme () end function resonant_subprocess_set_get_master_sqme subroutine resonant_subprocess_set_get_subprocess_sqme (prc_set, sqme) class(resonant_subprocess_set_t), intent(in) :: prc_set real(default), dimension(:), intent(inout) :: sqme integer :: i call prc_set%evt%get_subprocess_sqme (sqme) end subroutine resonant_subprocess_set_get_subprocess_sqme @ %def resonant_subprocess_set_get_master_sqme @ %def resonant_subprocess_set_get_subprocess_sqme @ We use the calculations of resonant matrix elements to determine probabilities for all resonance configurations. <>= procedure :: compute_probabilities & => resonant_subprocess_set_compute_probabilities <>= subroutine resonant_subprocess_set_compute_probabilities (prc_set, prob_array) class(resonant_subprocess_set_t), intent(inout) :: prc_set real(default), dimension(:), allocatable, intent(out) :: prob_array integer, dimension(:), allocatable :: index_array real(default) :: sqme, sqme_sum, sqme_bg real(default), dimension(:), allocatable :: sqme_res integer :: n n = size (prc_set%subprocess) allocate (prob_array (0:n), source = 0._default) call prc_set%evt%compute_probabilities () call prc_set%evt%get_selector_weights (prob_array) end subroutine resonant_subprocess_set_compute_probabilities @ %def resonant_subprocess_set_compute_probabilities @ \subsection{Unit tests} Test module, followed by the stand-alone unit-test procedures. <<[[restricted_subprocesses_ut.f90]]>>= <> module restricted_subprocesses_ut use unit_tests use restricted_subprocesses_uti <> <> contains <> end module restricted_subprocesses_ut @ %def restricted_subprocesses_ut @ <<[[restricted_subprocesses_uti.f90]]>>= <> module restricted_subprocesses_uti <> <> use io_units, only: free_unit use format_defs, only: FMT_10, FMT_12 use lorentz, only: vector4_t, vector3_moving, vector4_moving use particle_specifiers, only: new_prt_spec use process_libraries, only: process_library_t use resonances, only: resonance_info_t use resonances, only: resonance_history_t use resonances, only: resonance_history_set_t use state_matrices, only: FM_IGNORE_HELICITY use particles, only: particle_set_t use model_data, only: model_data_t use models, only: syntax_model_file_init, syntax_model_file_final use models, only: model_t use rng_base_ut, only: rng_test_factory_t use mci_base, only: mci_t use phs_base, only: phs_config_t use phs_forests, only: syntax_phs_forest_init, syntax_phs_forest_final use phs_wood, only: phs_wood_config_t use process_libraries, only: process_def_entry_t use process_libraries, only: process_component_def_t use prclib_stacks, only: prclib_entry_t use prc_core_def, only: prc_core_def_t use prc_omega, only: omega_def_t use process, only: process_t use instances, only: process_instance_t use process_stacks, only: process_entry_t use event_transforms, only: evt_trivial_t use resonance_insertion, only: evt_resonance_t use integrations, only: integrate_process use rt_data, only: rt_data_t use restricted_subprocesses <> <> <> <> contains <> <> end module restricted_subprocesses_uti @ %def restricted_subprocesses_uti @ API: driver for the unit tests below. <>= public :: restricted_subprocesses_test <>= subroutine restricted_subprocesses_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine restricted_subprocesses_test @ %def restricted_subprocesses_test @ \subsubsection{subprocess configuration} Initialize a [[restricted_subprocess_configuration_t]] object which represents a given process with a defined resonance history. <>= call test (restricted_subprocesses_1, "restricted_subprocesses_1", & "single subprocess", & u, results) <>= public :: restricted_subprocesses_1 <>= subroutine restricted_subprocesses_1 (u) integer, intent(in) :: u type(rt_data_t) :: global type(resonance_info_t) :: res_info type(resonance_history_t) :: res_history type(string_t) :: prc_name type(string_t), dimension(2) :: prt_in type(string_t), dimension(3) :: prt_out type(restricted_process_configuration_t) :: prc_config write (u, "(A)") "* Test output: restricted_subprocesses_1" write (u, "(A)") "* Purpose: create subprocess list from resonances" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%select_model (var_str ("SM")) write (u, "(A)") "* Create resonance history" write (u, "(A)") call res_info%init (3, -24, global%model, 5) call res_history%add_resonance (res_info) call res_history%write (u) write (u, "(A)") write (u, "(A)") "* Create process configuration" write (u, "(A)") prc_name = "restricted_subprocesses_1_p" prt_in(1) = "e-" prt_in(2) = "e+" prt_out(1) = "d" prt_out(2) = "u" prt_out(3) = "W+" call prc_config%init_resonant_process (prc_name, & new_prt_spec (prt_in), new_prt_spec (prt_out), & res_history, global%model, global%var_list) call prc_config%write (u) write (u, *) write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: restricted_subprocesses_1" end subroutine restricted_subprocesses_1 @ %def restricted_subprocesses_1 @ \subsubsection{Subprocess library configuration} Create a process library that represents restricted subprocesses for a given set of resonance histories <>= call test (restricted_subprocesses_2, "restricted_subprocesses_2", & "subprocess library", & u, results) <>= public :: restricted_subprocesses_2 <>= subroutine restricted_subprocesses_2 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(resonance_info_t) :: res_info type(resonance_history_t), dimension(2) :: res_history type(resonance_history_set_t) :: res_history_set type(string_t) :: libname type(string_t), dimension(2) :: prt_in type(string_t), dimension(3) :: prt_out type(resonant_subprocess_set_t) :: prc_set type(process_library_t), pointer :: lib logical :: exist write (u, "(A)") "* Test output: restricted_subprocesses_2" write (u, "(A)") "* Purpose: create subprocess library from resonances" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%select_model (var_str ("SM")) write (u, "(A)") "* Create resonance histories" write (u, "(A)") call res_info%init (3, -24, global%model, 5) call res_history(1)%add_resonance (res_info) call res_history(1)%write (u) call res_info%init (7, 23, global%model, 5) call res_history(2)%add_resonance (res_info) call res_history(2)%write (u) call res_history_set%init () call res_history_set%enter (res_history(1)) call res_history_set%enter (res_history(2)) call res_history_set%freeze () write (u, "(A)") write (u, "(A)") "* Empty restricted subprocess set" write (u, "(A)") write (u, "(A,1x,L1)") "active =", prc_set%is_active () write (u, "(A)") call prc_set%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Fill restricted subprocess set" write (u, "(A)") libname = "restricted_subprocesses_2_p_R" prt_in(1) = "e-" prt_in(2) = "e+" prt_out(1) = "d" prt_out(2) = "u" prt_out(3) = "W+" call prc_set%init (1) call prc_set%fill_resonances (res_history_set, 1) call prc_set%create_library (libname, global, exist) if (.not. exist) then call prc_set%add_to_library (1, & new_prt_spec (prt_in), new_prt_spec (prt_out), & global) end if call prc_set%freeze_library (global) write (u, "(A,1x,L1)") "active =", prc_set%is_active () write (u, "(A)") call prc_set%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Queries" write (u, "(A)") write (u, "(A,1x,I0)") "n_process =", prc_set%get_n_process () write (u, "(A)") write (u, "(A,A,A)") "libname = '", char (prc_set%get_libname ()), "'" write (u, "(A)") write (u, "(A,A,A)") "proc_id(1) = '", char (prc_set%get_proc_id (1)), "'" write (u, "(A,A,A)") "proc_id(2) = '", char (prc_set%get_proc_id (2)), "'" write (u, "(A)") write (u, "(A)") "* Process library" write (u, "(A)") call prc_set%compile_library (global) lib => global%prclib_stack%get_library_ptr (libname) if (associated (lib)) call lib%write (u, libpath=.false.) write (u, *) write (u, "(A)") "* Cleanup" call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: restricted_subprocesses_2" end subroutine restricted_subprocesses_2 @ %def restricted_subprocesses_2 @ \subsubsection{Auxiliary: Test processes} Auxiliary subroutine that constructs the process library for the above test. This parallels a similar subroutine in [[processes_uti]], but this time we want an \oMega\ process. <>= public :: prepare_resonance_test_library <>= subroutine prepare_resonance_test_library & (lib, libname, procname, model, global, u) type(process_library_t), target, intent(out) :: lib type(string_t), intent(in) :: libname type(string_t), intent(in) :: procname class(model_data_t), intent(in), pointer :: model type(rt_data_t), intent(in), target :: global integer, intent(in) :: u type(string_t), dimension(:), allocatable :: prt_in, prt_out class(prc_core_def_t), allocatable :: def type(process_def_entry_t), pointer :: entry call lib%init (libname) allocate (prt_in (2), prt_out (3)) prt_in = [var_str ("e+"), var_str ("e-")] prt_out = [var_str ("d"), var_str ("ubar"), var_str ("W+")] allocate (omega_def_t :: def) select type (def) type is (omega_def_t) call def%init (model%get_name (), prt_in, prt_out, & ovm=.false., ufo=.false.) end select allocate (entry) call entry%init (procname, & model_name = model%get_name (), & n_in = 2, n_components = 1, & requires_resonances = .true.) call entry%import_component (1, n_out = size (prt_out), & prt_in = new_prt_spec (prt_in), & prt_out = new_prt_spec (prt_out), & method = var_str ("omega"), & variant = def) call entry%write (u) call lib%append (entry) call lib%configure (global%os_data) call lib%write_makefile (global%os_data, force = .true., verbose = .false.) call lib%clean (global%os_data, distclean = .false.) call lib%write_driver (force = .true.) call lib%load (global%os_data) end subroutine prepare_resonance_test_library @ %def prepare_resonance_test_library @ \subsubsection{Kinematics and resonance selection} Prepare an actual process with resonant subprocesses. Insert kinematics and apply the resonance selector in an associated event transform. <>= call test (restricted_subprocesses_3, "restricted_subprocesses_3", & "resonance kinematics and probability", & u, results) <>= public :: restricted_subprocesses_3 <>= subroutine restricted_subprocesses_3 (u) integer, intent(in) :: u type(rt_data_t), target :: global class(model_t), pointer :: model class(model_data_t), pointer :: model_data type(string_t) :: libname, libname_res type(string_t) :: procname type(process_component_def_t), pointer :: process_component_def type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib logical :: exist type(process_t), pointer :: process type(process_instance_t), target :: process_instance type(resonance_history_set_t), dimension(1) :: res_history_set type(resonant_subprocess_set_t) :: prc_set type(particle_set_t) :: pset real(default) :: sqrts, mw, pp real(default), dimension(3) :: p3 type(vector4_t), dimension(:), allocatable :: p real(default), dimension(:), allocatable :: m integer, dimension(:), allocatable :: pdg real(default), dimension(:), allocatable :: sqme logical, dimension(:), allocatable :: mask real(default) :: on_shell_limit integer, dimension(:), allocatable :: i_array real(default), dimension(:), allocatable :: prob_array type(evt_resonance_t), target :: evt_resonance integer :: i, u_dump write (u, "(A)") "* Test output: restricted_subprocesses_3" write (u, "(A)") "* Purpose: handle process and resonance kinematics" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%set_log (var_str ("?resonance_history"), & .true., is_known = .true.) call global%select_model (var_str ("SM")) allocate (model) call model%init_instance (global%model) model_data => model libname = "restricted_subprocesses_3_lib" libname_res = "restricted_subprocesses_3_lib_res" procname = "restricted_subprocesses_3_p" write (u, "(A)") "* Initialize process library and process" write (u, "(A)") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) call prepare_resonance_test_library & (lib, libname, procname, model_data, global, u) call integrate_process (procname, global, & local_stack = .true., init_only = .true.) process => global%process_stack%get_process_ptr (procname) call process_instance%init (process) call process_instance%setup_event_data () write (u, "(A)") write (u, "(A)") "* Extract resonance history set" write (u, "(A)") call process%extract_resonance_history_set & (res_history_set(1), include_trivial=.true., i_component=1) call res_history_set(1)%write (u) write (u, "(A)") write (u, "(A)") "* Build resonant-subprocess library" write (u, "(A)") call prc_set%init (1) call prc_set%fill_resonances (res_history_set(1), 1) process_component_def => process%get_component_def_ptr (1) call prc_set%create_library (libname_res, global, exist) if (.not. exist) then call prc_set%add_to_library (1, & process_component_def%get_prt_spec_in (), & process_component_def%get_prt_spec_out (), & global) end if call prc_set%freeze_library (global) call prc_set%compile_library (global) call prc_set%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Build particle set" write (u, "(A)") sqrts = global%get_rval (var_str ("sqrts")) mw = 80._default ! deliberately slightly different from true mw pp = sqrt (sqrts**2 - 4 * mw**2) / 2 allocate (pdg (5), p (5), m (5)) pdg(1) = -11 p(1) = vector4_moving (sqrts/2, sqrts/2, 3) m(1) = 0 pdg(2) = 11 p(2) = vector4_moving (sqrts/2,-sqrts/2, 3) m(2) = 0 pdg(3) = 1 p3(1) = pp/2 p3(2) = mw/2 p3(3) = 0 p(3) = vector4_moving (sqrts/4, vector3_moving (p3)) m(3) = 0 p3(2) = -mw/2 pdg(4) = -2 p(4) = vector4_moving (sqrts/4, vector3_moving (p3)) m(4) = 0 pdg(5) = 24 p(5) = vector4_moving (sqrts/2,-pp, 1) m(5) = mw call pset%init_direct (0, 2, 0, 0, 3, pdg, model) call pset%set_momentum (p, m**2) call pset%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Fill process instance" ! workflow from event_recalculate call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1) call process_instance%recover & (1, 1, update_sqme=.true., recover_phs=.false.) call process_instance%evaluate_event_data (weight = 1._default) write (u, "(A)") write (u, "(A)") "* Prepare resonant subprocesses" call prc_set%prepare_process_objects (global) call prc_set%prepare_process_instances (global) call evt_resonance%set_resonance_data (res_history_set) call evt_resonance%select_component (1) call prc_set%connect_transform (evt_resonance) call evt_resonance%connect (process_instance, model) call prc_set%fill_momenta () write (u, "(A)") write (u, "(A)") "* Show squared matrix element of master process," write (u, "(A)") " should coincide with 2nd subprocess sqme" write (u, "(A)") write (u, "(1x,I0,1x," // FMT_12 // ")") 0, prc_set%get_master_sqme () write (u, "(A)") write (u, "(A)") "* Compute squared matrix elements & &of selected resonant subprocesses [1,2]" write (u, "(A)") call prc_set%evaluate_subprocess ([1,2]) allocate (sqme (3), source = 0._default) call prc_set%get_subprocess_sqme (sqme) do i = 1, size (sqme) write (u, "(1x,I0,1x," // FMT_12 // ")") i, sqme(i) end do deallocate (sqme) write (u, "(A)") write (u, "(A)") "* Compute squared matrix elements & &of all resonant subprocesses" write (u, "(A)") call prc_set%evaluate_subprocess ([1,2,3]) allocate (sqme (3), source = 0._default) call prc_set%get_subprocess_sqme (sqme) do i = 1, size (sqme) write (u, "(1x,I0,1x," // FMT_12 // ")") i, sqme(i) end do deallocate (sqme) write (u, "(A)") write (u, "(A)") "* Write process instances to file & &restricted_subprocesses_3_lib_res.dat" u_dump = free_unit () open (unit = u_dump, file = "restricted_subprocesses_3_lib_res.dat", & action = "write", status = "replace") call prc_set%dump_instances (u_dump) close (u_dump) write (u, "(A)") write (u, "(A)") "* Determine on-shell resonant subprocesses" write (u, "(A)") on_shell_limit = 0 write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) write (u, "(1x,A,9(1x,I0))") "resonant =", i_array on_shell_limit = 0.1_default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) write (u, "(1x,A,9(1x,I0))") "resonant =", i_array on_shell_limit = 10._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) write (u, "(1x,A,9(1x,I0))") "resonant =", i_array on_shell_limit = 10000._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) write (u, "(1x,A,9(1x,I0))") "resonant =", i_array write (u, "(A)") write (u, "(A)") "* Compute probabilities for applicable resonances" write (u, "(A)") " and initialize the process selector" write (u, "(A)") " (The first number is the probability for background)" write (u, "(A)") on_shell_limit = 0 write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) call prc_set%compute_probabilities (prob_array) write (u, "(1x,A,9(1x,"// FMT_12 // "))") "resonant =", prob_array call prc_set%write (u, testflag=.true.) write (u, *) on_shell_limit = 10._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) call prc_set%compute_probabilities (prob_array) write (u, "(1x,A,9(1x,"// FMT_12 // "))") "resonant =", prob_array call prc_set%write (u, testflag=.true.) write (u, *) on_shell_limit = 10000._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call prc_set%set_on_shell_limit (on_shell_limit) call prc_set%determine_on_shell_histories (1, i_array) call prc_set%compute_probabilities (prob_array) write (u, "(1x,A,9(1x,"// FMT_12 // "))") "resonant =", prob_array write (u, *) call prc_set%write (u, testflag=.true.) write (u, *) write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: restricted_subprocesses_3" end subroutine restricted_subprocesses_3 @ %def restricted_subprocesses_3 @ \subsubsection{Event transform} Prepare an actual process with resonant subprocesses. Prepare the resonance selector for a fixed event and apply the resonance-insertion event transform. <>= call test (restricted_subprocesses_4, "restricted_subprocesses_4", & "event transform", & u, results) <>= public :: restricted_subprocesses_4 <>= subroutine restricted_subprocesses_4 (u) integer, intent(in) :: u type(rt_data_t), target :: global class(model_t), pointer :: model class(model_data_t), pointer :: model_data type(string_t) :: libname, libname_res type(string_t) :: procname type(process_component_def_t), pointer :: process_component_def type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib logical :: exist type(process_t), pointer :: process type(process_instance_t), target :: process_instance type(resonance_history_set_t), dimension(1) :: res_history_set type(resonant_subprocess_set_t) :: prc_set type(particle_set_t) :: pset real(default) :: sqrts, mw, pp real(default), dimension(3) :: p3 type(vector4_t), dimension(:), allocatable :: p real(default), dimension(:), allocatable :: m integer, dimension(:), allocatable :: pdg real(default) :: on_shell_limit type(evt_trivial_t), target :: evt_trivial type(evt_resonance_t), target :: evt_resonance real(default) :: probability integer :: i write (u, "(A)") "* Test output: restricted_subprocesses_4" write (u, "(A)") "* Purpose: employ event transform" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%set_log (var_str ("?resonance_history"), & .true., is_known = .true.) call global%select_model (var_str ("SM")) allocate (model) call model%init_instance (global%model) model_data => model libname = "restricted_subprocesses_4_lib" libname_res = "restricted_subprocesses_4_lib_res" procname = "restricted_subprocesses_4_p" write (u, "(A)") "* Initialize process library and process" write (u, "(A)") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) call prepare_resonance_test_library & (lib, libname, procname, model_data, global, u) call integrate_process (procname, global, & local_stack = .true., init_only = .true.) process => global%process_stack%get_process_ptr (procname) call process_instance%init (process) call process_instance%setup_event_data () write (u, "(A)") write (u, "(A)") "* Extract resonance history set" call process%extract_resonance_history_set & (res_history_set(1), include_trivial=.false., i_component=1) write (u, "(A)") write (u, "(A)") "* Build resonant-subprocess library" call prc_set%init (1) call prc_set%fill_resonances (res_history_set(1), 1) process_component_def => process%get_component_def_ptr (1) call prc_set%create_library (libname_res, global, exist) if (.not. exist) then call prc_set%add_to_library (1, & process_component_def%get_prt_spec_in (), & process_component_def%get_prt_spec_out (), & global) end if call prc_set%freeze_library (global) call prc_set%compile_library (global) write (u, "(A)") write (u, "(A)") "* Build particle set" write (u, "(A)") sqrts = global%get_rval (var_str ("sqrts")) mw = 80._default ! deliberately slightly different from true mw pp = sqrt (sqrts**2 - 4 * mw**2) / 2 allocate (pdg (5), p (5), m (5)) pdg(1) = -11 p(1) = vector4_moving (sqrts/2, sqrts/2, 3) m(1) = 0 pdg(2) = 11 p(2) = vector4_moving (sqrts/2,-sqrts/2, 3) m(2) = 0 pdg(3) = 1 p3(1) = pp/2 p3(2) = mw/2 p3(3) = 0 p(3) = vector4_moving (sqrts/4, vector3_moving (p3)) m(3) = 0 p3(2) = -mw/2 pdg(4) = -2 p(4) = vector4_moving (sqrts/4, vector3_moving (p3)) m(4) = 0 pdg(5) = 24 p(5) = vector4_moving (sqrts/2,-pp, 1) m(5) = mw call pset%init_direct (0, 2, 0, 0, 3, pdg, model) call pset%set_momentum (p, m**2) write (u, "(A)") "* Fill process instance" write (u, "(A)") ! workflow from event_recalculate call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1) call process_instance%recover & (1, 1, update_sqme=.true., recover_phs=.false.) call process_instance%evaluate_event_data (weight = 1._default) write (u, "(A)") "* Prepare resonant subprocesses" write (u, "(A)") call prc_set%prepare_process_objects (global) call prc_set%prepare_process_instances (global) write (u, "(A)") "* Fill trivial event transform (deliberately w/o color)" write (u, "(A)") call evt_trivial%connect (process_instance, model) call evt_trivial%set_particle_set (pset, 1, 1) call evt_trivial%write (u) write (u, "(A)") write (u, "(A)") "* Initialize resonance-insertion event transform" write (u, "(A)") evt_trivial%next => evt_resonance evt_resonance%previous => evt_trivial call evt_resonance%set_resonance_data (res_history_set) call evt_resonance%select_component (1) call evt_resonance%connect (process_instance, model) call prc_set%connect_transform (evt_resonance) call evt_resonance%write (u) write (u, "(A)") write (u, "(A)") "* Compute probabilities for applicable resonances" write (u, "(A)") " and initialize the process selector" write (u, "(A)") on_shell_limit = 10._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", on_shell_limit call evt_resonance%set_on_shell_limit (on_shell_limit) write (u, "(A)") write (u, "(A)") "* Evaluate resonance-insertion event transform" write (u, "(A)") call evt_resonance%prepare_new_event (1, 1) call evt_resonance%generate_weighted (probability) call evt_resonance%make_particle_set (1, .false.) call evt_resonance%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: restricted_subprocesses_4" end subroutine restricted_subprocesses_4 @ %def restricted_subprocesses_4 @ \subsubsection{Gaussian turnoff} Identical to the previous process, except that we apply a Gaussian turnoff to the resonance kinematics, which affects the subprocess selector. <>= call test (restricted_subprocesses_5, "restricted_subprocesses_5", & "event transform with gaussian turnoff", & u, results) <>= public :: restricted_subprocesses_5 <>= subroutine restricted_subprocesses_5 (u) integer, intent(in) :: u type(rt_data_t), target :: global class(model_t), pointer :: model class(model_data_t), pointer :: model_data type(string_t) :: libname, libname_res type(string_t) :: procname type(process_component_def_t), pointer :: process_component_def type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib logical :: exist type(process_t), pointer :: process type(process_instance_t), target :: process_instance type(resonance_history_set_t), dimension(1) :: res_history_set type(resonant_subprocess_set_t) :: prc_set type(particle_set_t) :: pset real(default) :: sqrts, mw, pp real(default), dimension(3) :: p3 type(vector4_t), dimension(:), allocatable :: p real(default), dimension(:), allocatable :: m integer, dimension(:), allocatable :: pdg real(default) :: on_shell_limit real(default) :: on_shell_turnoff type(evt_trivial_t), target :: evt_trivial type(evt_resonance_t), target :: evt_resonance real(default) :: probability integer :: i write (u, "(A)") "* Test output: restricted_subprocesses_5" write (u, "(A)") "* Purpose: employ event transform & &with gaussian turnoff" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%set_log (var_str ("?resonance_history"), & .true., is_known = .true.) call global%select_model (var_str ("SM")) allocate (model) call model%init_instance (global%model) model_data => model libname = "restricted_subprocesses_5_lib" libname_res = "restricted_subprocesses_5_lib_res" procname = "restricted_subprocesses_5_p" write (u, "(A)") "* Initialize process library and process" write (u, "(A)") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) call prepare_resonance_test_library & (lib, libname, procname, model_data, global, u) call integrate_process (procname, global, & local_stack = .true., init_only = .true.) process => global%process_stack%get_process_ptr (procname) call process_instance%init (process) call process_instance%setup_event_data () write (u, "(A)") write (u, "(A)") "* Extract resonance history set" call process%extract_resonance_history_set & (res_history_set(1), include_trivial=.false., i_component=1) write (u, "(A)") write (u, "(A)") "* Build resonant-subprocess library" call prc_set%init (1) call prc_set%fill_resonances (res_history_set(1), 1) process_component_def => process%get_component_def_ptr (1) call prc_set%create_library (libname_res, global, exist) if (.not. exist) then call prc_set%add_to_library (1, & process_component_def%get_prt_spec_in (), & process_component_def%get_prt_spec_out (), & global) end if call prc_set%freeze_library (global) call prc_set%compile_library (global) write (u, "(A)") write (u, "(A)") "* Build particle set" write (u, "(A)") sqrts = global%get_rval (var_str ("sqrts")) mw = 80._default ! deliberately slightly different from true mw pp = sqrt (sqrts**2 - 4 * mw**2) / 2 allocate (pdg (5), p (5), m (5)) pdg(1) = -11 p(1) = vector4_moving (sqrts/2, sqrts/2, 3) m(1) = 0 pdg(2) = 11 p(2) = vector4_moving (sqrts/2,-sqrts/2, 3) m(2) = 0 pdg(3) = 1 p3(1) = pp/2 p3(2) = mw/2 p3(3) = 0 p(3) = vector4_moving (sqrts/4, vector3_moving (p3)) m(3) = 0 p3(2) = -mw/2 pdg(4) = -2 p(4) = vector4_moving (sqrts/4, vector3_moving (p3)) m(4) = 0 pdg(5) = 24 p(5) = vector4_moving (sqrts/2,-pp, 1) m(5) = mw call pset%init_direct (0, 2, 0, 0, 3, pdg, model) call pset%set_momentum (p, m**2) write (u, "(A)") "* Fill process instance" write (u, "(A)") ! workflow from event_recalculate call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1) call process_instance%recover & (1, 1, update_sqme=.true., recover_phs=.false.) call process_instance%evaluate_event_data (weight = 1._default) write (u, "(A)") "* Prepare resonant subprocesses" write (u, "(A)") call prc_set%prepare_process_objects (global) call prc_set%prepare_process_instances (global) write (u, "(A)") "* Fill trivial event transform (deliberately w/o color)" write (u, "(A)") call evt_trivial%connect (process_instance, model) call evt_trivial%set_particle_set (pset, 1, 1) call evt_trivial%write (u) write (u, "(A)") write (u, "(A)") "* Initialize resonance-insertion event transform" write (u, "(A)") evt_trivial%next => evt_resonance evt_resonance%previous => evt_trivial call evt_resonance%set_resonance_data (res_history_set) call evt_resonance%select_component (1) call evt_resonance%connect (process_instance, model) call prc_set%connect_transform (evt_resonance) call evt_resonance%write (u) write (u, "(A)") write (u, "(A)") "* Compute probabilities for applicable resonances" write (u, "(A)") " and initialize the process selector" write (u, "(A)") on_shell_limit = 10._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_limit =", & on_shell_limit call evt_resonance%set_on_shell_limit (on_shell_limit) on_shell_turnoff = 1._default write (u, "(1x,A,1x," // FMT_10 // ")") "on_shell_turnoff =", & on_shell_turnoff call evt_resonance%set_on_shell_turnoff (on_shell_turnoff) write (u, "(A)") write (u, "(A)") "* Evaluate resonance-insertion event transform" write (u, "(A)") call evt_resonance%prepare_new_event (1, 1) call evt_resonance%generate_weighted (probability) call evt_resonance%make_particle_set (1, .false.) call evt_resonance%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: restricted_subprocesses_5" end subroutine restricted_subprocesses_5 @ %def restricted_subprocesses_5 @ \subsubsection{Event transform} The same process and event again. This time, switch off the background contribution, so the selector becomes trivial. <>= call test (restricted_subprocesses_6, "restricted_subprocesses_6", & "event transform with background switched off", & u, results) <>= public :: restricted_subprocesses_6 <>= subroutine restricted_subprocesses_6 (u) integer, intent(in) :: u type(rt_data_t), target :: global class(model_t), pointer :: model class(model_data_t), pointer :: model_data type(string_t) :: libname, libname_res type(string_t) :: procname type(process_component_def_t), pointer :: process_component_def type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib logical :: exist type(process_t), pointer :: process type(process_instance_t), target :: process_instance type(resonance_history_set_t), dimension(1) :: res_history_set type(resonant_subprocess_set_t) :: prc_set type(particle_set_t) :: pset real(default) :: sqrts, mw, pp real(default), dimension(3) :: p3 type(vector4_t), dimension(:), allocatable :: p real(default), dimension(:), allocatable :: m integer, dimension(:), allocatable :: pdg real(default) :: on_shell_limit real(default) :: background_factor type(evt_trivial_t), target :: evt_trivial type(evt_resonance_t), target :: evt_resonance real(default) :: probability integer :: i write (u, "(A)") "* Test output: restricted_subprocesses_6" write (u, "(A)") "* Purpose: employ event transform & &with background switched off" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%set_log (var_str ("?resonance_history"), & .true., is_known = .true.) call global%select_model (var_str ("SM")) allocate (model) call model%init_instance (global%model) model_data => model libname = "restricted_subprocesses_6_lib" libname_res = "restricted_subprocesses_6_lib_res" procname = "restricted_subprocesses_6_p" write (u, "(A)") "* Initialize process library and process" write (u, "(A)") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) call prepare_resonance_test_library & (lib, libname, procname, model_data, global, u) call integrate_process (procname, global, & local_stack = .true., init_only = .true.) process => global%process_stack%get_process_ptr (procname) call process_instance%init (process) call process_instance%setup_event_data () write (u, "(A)") write (u, "(A)") "* Extract resonance history set" call process%extract_resonance_history_set & (res_history_set(1), include_trivial=.false., i_component=1) write (u, "(A)") write (u, "(A)") "* Build resonant-subprocess library" call prc_set%init (1) call prc_set%fill_resonances (res_history_set(1), 1) process_component_def => process%get_component_def_ptr (1) call prc_set%create_library (libname_res, global, exist) if (.not. exist) then call prc_set%add_to_library (1, & process_component_def%get_prt_spec_in (), & process_component_def%get_prt_spec_out (), & global) end if call prc_set%freeze_library (global) call prc_set%compile_library (global) write (u, "(A)") write (u, "(A)") "* Build particle set" write (u, "(A)") sqrts = global%get_rval (var_str ("sqrts")) mw = 80._default ! deliberately slightly different from true mw pp = sqrt (sqrts**2 - 4 * mw**2) / 2 allocate (pdg (5), p (5), m (5)) pdg(1) = -11 p(1) = vector4_moving (sqrts/2, sqrts/2, 3) m(1) = 0 pdg(2) = 11 p(2) = vector4_moving (sqrts/2,-sqrts/2, 3) m(2) = 0 pdg(3) = 1 p3(1) = pp/2 p3(2) = mw/2 p3(3) = 0 p(3) = vector4_moving (sqrts/4, vector3_moving (p3)) m(3) = 0 p3(2) = -mw/2 pdg(4) = -2 p(4) = vector4_moving (sqrts/4, vector3_moving (p3)) m(4) = 0 pdg(5) = 24 p(5) = vector4_moving (sqrts/2,-pp, 1) m(5) = mw call pset%init_direct (0, 2, 0, 0, 3, pdg, model) call pset%set_momentum (p, m**2) write (u, "(A)") "* Fill process instance" write (u, "(A)") ! workflow from event_recalculate call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1) call process_instance%recover & (1, 1, update_sqme=.true., recover_phs=.false.) call process_instance%evaluate_event_data (weight = 1._default) write (u, "(A)") "* Prepare resonant subprocesses" write (u, "(A)") call prc_set%prepare_process_objects (global) call prc_set%prepare_process_instances (global) write (u, "(A)") "* Fill trivial event transform (deliberately w/o color)" write (u, "(A)") call evt_trivial%connect (process_instance, model) call evt_trivial%set_particle_set (pset, 1, 1) call evt_trivial%write (u) write (u, "(A)") write (u, "(A)") "* Initialize resonance-insertion event transform" write (u, "(A)") evt_trivial%next => evt_resonance evt_resonance%previous => evt_trivial call evt_resonance%set_resonance_data (res_history_set) call evt_resonance%select_component (1) call evt_resonance%connect (process_instance, model) call prc_set%connect_transform (evt_resonance) call evt_resonance%write (u) write (u, "(A)") write (u, "(A)") "* Compute probabilities for applicable resonances" write (u, "(A)") " and initialize the process selector" write (u, "(A)") on_shell_limit = 10._default write (u, "(1x,A,1x," // FMT_10 // ")") & "on_shell_limit =", on_shell_limit call evt_resonance%set_on_shell_limit (on_shell_limit) background_factor = 0 write (u, "(1x,A,1x," // FMT_10 // ")") & "background_factor =", background_factor call evt_resonance%set_background_factor (background_factor) write (u, "(A)") write (u, "(A)") "* Evaluate resonance-insertion event transform" write (u, "(A)") call evt_resonance%prepare_new_event (1, 1) call evt_resonance%generate_weighted (probability) call evt_resonance%make_particle_set (1, .false.) call evt_resonance%write (u, testflag=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () call syntax_phs_forest_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: restricted_subprocesses_6" end subroutine restricted_subprocesses_6 @ %def restricted_subprocesses_6 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Simulation} This module manages simulation: event generation and reading/writing of event files. The [[simulation]] object is intended to be used (via a pointer) outside of \whizard, if events are generated individually by an external driver. <<[[simulations.f90]]>>= <> module simulations <> <> <> use io_units use format_utils, only: write_separator use format_defs, only: FMT_15, FMT_19 use os_interface use numeric_utils use string_utils, only: str use diagnostics use lorentz, only: vector4_t use sm_qcd use md5 use variables, only: var_list_t use eval_trees use model_data use flavors use particles use state_matrices, only: FM_IGNORE_HELICITY use beam_structures, only: beam_structure_t use beams use rng_base use rng_stream, only: rng_stream_t use selectors use resonances, only: resonance_history_set_t use process_libraries, only: process_library_t use process_libraries, only: process_component_def_t use prc_core ! TODO: (bcn 2016-09-13) should be ideally only pcm_base use pcm, only: pcm_nlo_t, pcm_instance_nlo_t ! TODO: (bcn 2016-09-13) details of process config should not be necessary here use process_config, only: COMP_REAL_FIN use process use instances use event_base use event_handles, only: event_handle_t use events use event_transforms use shower use eio_data use eio_base use rt_data use dispatch_beams, only: dispatch_qcd use dispatch_rng, only: dispatch_rng_factory use dispatch_rng, only: update_rng_seed_in_var_list use dispatch_me_methods, only: dispatch_core_update, dispatch_core_restore use dispatch_transforms, only: dispatch_evt_isr_epa_handler use dispatch_transforms, only: dispatch_evt_resonance use dispatch_transforms, only: dispatch_evt_decay use dispatch_transforms, only: dispatch_evt_shower use dispatch_transforms, only: dispatch_evt_hadrons use dispatch_transforms, only: dispatch_evt_nlo use integrations use event_streams use restricted_subprocesses, only: resonant_subprocess_set_t use restricted_subprocesses, only: get_libname_res use evt_nlo <> <> <> <> <> contains <> end module simulations @ %def simulations @ \subsection{Event counting} In this object we collect statistical information about an event sample or sub-sample. <>= type :: counter_t integer :: total = 0 integer :: generated = 0 integer :: read = 0 integer :: positive = 0 integer :: negative = 0 integer :: zero = 0 integer :: excess = 0 integer :: dropped = 0 real(default) :: max_excess = 0 real(default) :: sum_excess = 0 logical :: reproduce_xsection = .false. real(default) :: mean = 0 real(default) :: varsq = 0 integer :: nlo_weight_counter = 0 contains <> end type counter_t @ %def simulation_counter_t @ Output. <>= procedure :: write => counter_write <>= subroutine counter_write (counter, unit) class(counter_t), intent(in) :: counter integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) 1 format (3x,A,I0) 2 format (5x,A,I0) 3 format (5x,A,ES19.12) write (u, 1) "Events total = ", counter%total write (u, 2) "generated = ", counter%generated write (u, 2) "read = ", counter%read write (u, 2) "positive weight = ", counter%positive write (u, 2) "negative weight = ", counter%negative write (u, 2) "zero weight = ", counter%zero write (u, 2) "excess weight = ", counter%excess if (counter%excess /= 0) then write (u, 3) "max excess = ", counter%max_excess write (u, 3) "avg excess = ", counter%sum_excess / counter%total end if write (u, 1) "Events dropped = ", counter%dropped end subroutine counter_write @ %def counter_write @ This is a screen message: if there was an excess, display statistics. <>= procedure :: show_excess => counter_show_excess <>= subroutine counter_show_excess (counter) class(counter_t), intent(in) :: counter if (counter%excess > 0) then write (msg_buffer, "(A,1x,I0,1x,A,1x,'(',F7.3,' %)')") & "Encountered events with excess weight:", counter%excess, & "events", 100 * counter%excess / real (counter%total) call msg_warning () write (msg_buffer, "(A,ES10.3)") & "Maximum excess weight =", counter%max_excess call msg_message () write (msg_buffer, "(A,ES10.3)") & "Average excess weight =", counter%sum_excess / counter%total call msg_message () end if end subroutine counter_show_excess @ %def counter_show_excess @ If events have been dropped during simulation of weighted events, issue a message here. If a fraction [[n_dropped / n_total]] of the events fail the cuts, we keep generating new ones until we have [[n_total]] events with [[weight > 0]]. Thus, the total sum of weights will be a fraction of [[n_dropped / n_total]] too large. However, we do not know how many events will pass or fail the cuts prior to generating them so we leave it to the user to correct for this factor. <>= procedure :: show_dropped => counter_show_dropped <>= subroutine counter_show_dropped (counter) class(counter_t), intent(in) :: counter if (counter%dropped > 0) then write (msg_buffer, "(A,1x,I0,1x,'(',A,1x,I0,')')") & "Dropped events (weight zero) =", & counter%dropped, "total", counter%dropped + counter%total call msg_message () write (msg_buffer, "(A,ES15.8)") & "All event weights must be rescaled by f =", & real (counter%total, default) & / real (counter%dropped + counter%total, default) call msg_warning () end if end subroutine counter_show_dropped @ %def counter_show_dropped @ <>= procedure :: show_mean_and_variance => counter_show_mean_and_variance <>= subroutine counter_show_mean_and_variance (counter) class(counter_t), intent(in) :: counter if (counter%reproduce_xsection .and. counter%nlo_weight_counter > 1) then print *, "Reconstructed cross-section from event weights: " print *, counter%mean, '+-', sqrt (counter%varsq / (counter%nlo_weight_counter - 1)) end if end subroutine counter_show_mean_and_variance @ %def counter_show_mean_and_variance @ Count an event. The weight and event source are optional; by default we assume that the event has been generated and has positive weight. The optional integer [[n_dropped]] counts weighted events with weight zero that were encountered while generating the current event, but dropped (because of their zero weight). Accumulating this number allows for renormalizing event weight sums in histograms, after the generation step has been completed. <>= procedure :: record => counter_record <>= subroutine counter_record (counter, weight, excess, n_dropped, from_file) class(counter_t), intent(inout) :: counter real(default), intent(in), optional :: weight, excess integer, intent(in), optional :: n_dropped logical, intent(in), optional :: from_file counter%total = counter%total + 1 if (present (from_file)) then if (from_file) then counter%read = counter%read + 1 else counter%generated = counter%generated + 1 end if else counter%generated = counter%generated + 1 end if if (present (weight)) then if (weight > 0) then counter%positive = counter%positive + 1 else if (weight < 0) then counter%negative = counter%negative + 1 else counter%zero = counter%zero + 1 end if else counter%positive = counter%positive + 1 end if if (present (excess)) then if (excess > 0) then counter%excess = counter%excess + 1 counter%max_excess = max (counter%max_excess, excess) counter%sum_excess = counter%sum_excess + excess end if end if if (present (n_dropped)) then counter%dropped = counter%dropped + n_dropped end if end subroutine counter_record @ %def counter_record <>= procedure :: allreduce_record => counter_allreduce_record <>= subroutine counter_allreduce_record (counter) class(counter_t), intent(inout) :: counter integer :: read, generated integer :: positive, negative, zero, excess, dropped real(default) :: max_excess, sum_excess read = counter%read generated = counter%generated positive = counter%positive negative = counter%negative zero = counter%zero excess = counter%excess max_excess = counter%max_excess sum_excess = counter%sum_excess dropped = counter%dropped call MPI_ALLREDUCE (read, counter%read, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (generated, counter%generated, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (positive, counter%positive, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (negative, counter%negative, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (zero, counter%zero, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (excess, counter%excess, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (max_excess, counter%max_excess, 1, MPI_DOUBLE_PRECISION, MPI_MAX, MPI_COMM_WORLD) call MPI_ALLREDUCE (sum_excess, counter%sum_excess, 1, MPI_DOUBLE_PRECISION, MPI_SUM, MPI_COMM_WORLD) call MPI_ALLREDUCE (dropped, counter%dropped, 1, MPI_INTEGER, MPI_SUM, MPI_COMM_WORLD) !! \todo{sbrass - Implement allreduce of mean and variance, relevant for weighted events.} end subroutine counter_allreduce_record @ <>= procedure :: record_mean_and_variance => & counter_record_mean_and_variance <>= subroutine counter_record_mean_and_variance (counter, weight, i_nlo) class(counter_t), intent(inout) :: counter real(default), intent(in) :: weight integer, intent(in) :: i_nlo real(default), save :: weight_buffer = 0._default integer, save :: nlo_count = 1 if (.not. counter%reproduce_xsection) return if (i_nlo == 1) then call flush_weight_buffer (weight_buffer, nlo_count) weight_buffer = weight nlo_count = 1 else weight_buffer = weight_buffer + weight nlo_count = nlo_count + 1 end if contains subroutine flush_weight_buffer (w, n_nlo) real(default), intent(in) :: w integer, intent(in) :: n_nlo integer :: n real(default) :: mean_new counter%nlo_weight_counter = counter%nlo_weight_counter + 1 !!! Minus 1 to take into account offset from initialization n = counter%nlo_weight_counter - 1 if (n > 0) then mean_new = counter%mean + (w / n_nlo - counter%mean) / n if (n > 1) & counter%varsq = counter%varsq - counter%varsq / (n - 1) + & n * (mean_new - counter%mean)**2 counter%mean = mean_new end if end subroutine flush_weight_buffer end subroutine counter_record_mean_and_variance @ %def counter_record_mean_and_variance @ \subsection{Simulation: component sets} For each set of process components that share a MCI entry in the process configuration, we keep a separate event record. <>= type :: mci_set_t private integer :: n_components = 0 integer, dimension(:), allocatable :: i_component type(string_t), dimension(:), allocatable :: component_id logical :: has_integral = .false. real(default) :: integral = 0 real(default) :: error = 0 real(default) :: weight_mci = 0 type(counter_t) :: counter contains <> end type mci_set_t @ %def mci_set_t @ Output. <>= procedure :: write => mci_set_write <>= subroutine mci_set_write (object, unit, pacified) class(mci_set_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: pacified logical :: pacify integer :: u, i u = given_output_unit (unit) pacify = .false.; if (present (pacified)) pacify = pacified write (u, "(3x,A)") "Components:" do i = 1, object%n_components write (u, "(5x,I0,A,A,A)") object%i_component(i), & ": '", char (object%component_id(i)), "'" end do if (object%has_integral) then if (pacify) then write (u, "(3x,A," // FMT_15 // ")") "Integral = ", object%integral write (u, "(3x,A," // FMT_15 // ")") "Error = ", object%error write (u, "(3x,A,F9.6)") "Weight =", object%weight_mci else write (u, "(3x,A," // FMT_19 // ")") "Integral = ", object%integral write (u, "(3x,A," // FMT_19 // ")") "Error = ", object%error write (u, "(3x,A,F13.10)") "Weight =", object%weight_mci end if else write (u, "(3x,A)") "Integral = [undefined]" end if call object%counter%write (u) end subroutine mci_set_write @ %def mci_set_write @ Initialize: Get the indices and names for the process components that will contribute to this set. <>= procedure :: init => mci_set_init <>= subroutine mci_set_init (object, i_mci, process) class(mci_set_t), intent(out) :: object integer, intent(in) :: i_mci type(process_t), intent(in), target :: process integer :: i call process%get_i_component (i_mci, object%i_component) object%n_components = size (object%i_component) allocate (object%component_id (object%n_components)) do i = 1, size (object%component_id) object%component_id(i) = & process%get_component_id (object%i_component(i)) end do if (process%has_integral (i_mci)) then object%integral = process%get_integral (i_mci) object%error = process%get_error (i_mci) object%has_integral = .true. end if end subroutine mci_set_init @ %def mci_set_init @ \subsection{Process-core Safe} This is an object that temporarily holds a process core object. We need this while rescanning a process with modified parameters. After the rescan, we want to restore the original state. <>= type :: core_safe_t class(prc_core_t), allocatable :: core end type core_safe_t @ %def core_safe_t @ \subsection{Process Object} The simulation works on process objects. This subroutine makes a process object available for simulation. The process is in the process stack. [[use_process]] implies that the process should already exist as an object in the process stack. If integration is not yet done, do it. Any generated process object should be put on the global stack, if it is separate from the local one. <>= subroutine prepare_process & (process, process_id, use_process, integrate, local, global) type(process_t), pointer, intent(out) :: process type(string_t), intent(in) :: process_id logical, intent(in) :: use_process, integrate type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global type(rt_data_t), pointer :: current if (debug_on) call msg_debug (D_CORE, "prepare_process") if (debug_on) call msg_debug (D_CORE, "global present", present (global)) if (present (global)) then current => global else current => local end if process => current%process_stack%get_process_ptr (process_id) if (debug_on) call msg_debug (D_CORE, "use_process", use_process) if (debug_on) call msg_debug (D_CORE, "associated process", associated (process)) if (use_process .and. .not. associated (process)) then if (integrate) then call msg_message ("Simulate: process '" & // char (process_id) // "' needs integration") else call msg_message ("Simulate: process '" & // char (process_id) // "' needs initialization") end if if (present (global)) then call integrate_process (process_id, local, global, & init_only = .not. integrate) else call integrate_process (process_id, local, & local_stack = .true., init_only = .not. integrate) end if if (signal_is_pending ()) return process => current%process_stack%get_process_ptr (process_id) if (associated (process)) then if (integrate) then call msg_message ("Simulate: integration done") call current%process_stack%fill_result_vars (process_id) else call msg_message ("Simulate: process initialization done") end if else call msg_fatal ("Simulate: process '" & // char (process_id) // "' could not be initialized: aborting") end if else if (.not. associated (process)) then if (present (global)) then call integrate_process (process_id, local, global, & init_only = .true.) else call integrate_process (process_id, local, & local_stack = .true., init_only = .true.) end if process => current%process_stack%get_process_ptr (process_id) call msg_message & ("Simulate: process '" & // char (process_id) // "': enabled for rescan only") end if end subroutine prepare_process @ %def prepare_process @ \subsection{Simulation-entry object} For each process that we consider for event generation, we need a separate entry. The entry separately records the process ID and run ID. The [[weight_mci]] array is used for selecting a component set (which shares an MCI record inside the process container) when generating an event for the current process. The simulation entry is an extension of the [[event_t]] event record. This core object contains configuration data, pointers to the process and process instance, the expressions, flags and values that are evaluated at runtime, and the resulting particle set. The entry explicitly allocates the [[process_instance]], which becomes the process-specific workspace for the event record. If entries with differing environments are present simultaneously, we may need to switch QCD parameters and/or the model event by event. In this case, the [[qcd]] and/or [[model]] components are present. For the purpose of NLO events, [[entry_t]] contains a pointer list to other simulation-entries. This is due to the fact that we have to associate an event for each component of the fixed order simulation, i.e. one $N$-particle event and $N_\text{phs}$ $N+1$-particle events. However, all entries share the same event transforms. <>= type, extends (event_t) :: entry_t private type(string_t) :: process_id type(string_t) :: library type(string_t) :: run_id logical :: has_integral = .false. real(default) :: integral = 0 real(default) :: error = 0 real(default) :: process_weight = 0 logical :: valid = .false. type(counter_t) :: counter integer :: n_in = 0 integer :: n_mci = 0 type(mci_set_t), dimension(:), allocatable :: mci_sets type(selector_t) :: mci_selector logical :: has_resonant_subprocess_set = .false. type(resonant_subprocess_set_t) :: resonant_subprocess_set type(core_safe_t), dimension(:), allocatable :: core_safe class(model_data_t), pointer :: model => null () type(qcd_t) :: qcd type(entry_t), pointer :: first => null () type(entry_t), pointer :: next => null () class(evt_t), pointer :: evt_powheg => null () contains <> end type entry_t @ %def entry_t @ Output. Write just the configuration, the event is written by a separate routine. The [[verbose]] option is unused, it is required by the interface of the base-object method. <>= procedure :: write_config => entry_write_config <>= subroutine entry_write_config (object, unit, pacified) class(entry_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: pacified logical :: pacify integer :: u, i u = given_output_unit (unit) pacify = .false.; if (present (pacified)) pacify = pacified write (u, "(3x,A,A,A)") "Process = '", char (object%process_id), "'" write (u, "(3x,A,A,A)") "Library = '", char (object%library), "'" write (u, "(3x,A,A,A)") "Run = '", char (object%run_id), "'" write (u, "(3x,A,L1)") "is valid = ", object%valid if (object%has_integral) then if (pacify) then write (u, "(3x,A," // FMT_15 // ")") "Integral = ", object%integral write (u, "(3x,A," // FMT_15 // ")") "Error = ", object%error write (u, "(3x,A,F9.6)") "Weight =", object%process_weight else write (u, "(3x,A," // FMT_19 // ")") "Integral = ", object%integral write (u, "(3x,A," // FMT_19 // ")") "Error = ", object%error write (u, "(3x,A,F13.10)") "Weight =", object%process_weight end if else write (u, "(3x,A)") "Integral = [undefined]" end if write (u, "(3x,A,I0)") "MCI sets = ", object%n_mci call object%counter%write (u) do i = 1, size (object%mci_sets) write (u, "(A)") write (u, "(1x,A,I0,A)") "MCI set #", i, ":" call object%mci_sets(i)%write (u, pacified) end do if (object%resonant_subprocess_set%is_active ()) then write (u, "(A)") call object%write_resonant_subprocess_data (u) end if if (allocated (object%core_safe)) then do i = 1, size (object%core_safe) write (u, "(1x,A,I0,A)") "Saved process-component core #", i, ":" call object%core_safe(i)%core%write (u) end do end if end subroutine entry_write_config @ %def entry_write_config @ Finalizer. The [[instance]] pointer component of the [[event_t]] base type points to a target which we did explicitly allocate in the [[entry_init]] procedure. Therefore, we finalize and explicitly deallocate it here. Then we call the finalizer of the base type. <>= procedure :: final => entry_final <>= subroutine entry_final (object) class(entry_t), intent(inout) :: object integer :: i if (associated (object%instance)) then do i = 1, object%n_mci call object%instance%final_simulation (i) end do call object%instance%final () deallocate (object%instance) end if call object%event_t%final () end subroutine entry_final @ %def entry_final @ Copy the content of an entry into another one, except for the next-pointer <>= procedure :: copy_entry => entry_copy_entry <>= subroutine entry_copy_entry (entry1, entry2) class(entry_t), intent(in), target :: entry1 type(entry_t), intent(inout), target :: entry2 call entry1%event_t%clone (entry2%event_t) entry2%process_id = entry1%process_id entry2%library = entry1%library entry2%run_id = entry1%run_id entry2%has_integral = entry1%has_integral entry2%integral = entry1%integral entry2%error = entry1%error entry2%process_weight = entry1%process_weight entry2%valid = entry1%valid entry2%counter = entry1%counter entry2%n_in = entry1%n_in entry2%n_mci = entry1%n_mci if (allocated (entry1%mci_sets)) then allocate (entry2%mci_sets (size (entry1%mci_sets))) entry2%mci_sets = entry1%mci_sets end if entry2%mci_selector = entry1%mci_selector if (allocated (entry1%core_safe)) then allocate (entry2%core_safe (size (entry1%core_safe))) entry2%core_safe = entry1%core_safe end if entry2%model => entry1%model entry2%qcd = entry1%qcd end subroutine entry_copy_entry @ %def entry_copy_entry @ \subsubsection{Simulation-entry initialization} Search for a process entry and allocate a process instance as an anonymous object, temporarily accessible via the [[process_instance]] pointer. Assign data by looking at the process object and at the environment. If [[n_alt]] is set, we prepare for additional alternate sqme and weight entries. The [[compile]] flag is only false if we do not need the Whizard process at all, just its definition. In that case, we skip process initialization. Otherwise, and if the process object is not found initially: if [[integrate]] is set, attempt an integration pass and try again. Otherwise, just initialize the object. If [[generate]] is set, prepare the MCI objects for generating new events. For pure rescanning, this is not necessary. If [[resonance_history]] is set, we create a separate process library which contains all possible restricted subprocesses with distinct resonance histories. These processes will not be integrated, but their matrix element codes are used for determining probabilities of resonance histories. Note that this can work only if the process method is OMega, and the phase-space method is 'wood'. When done, we assign the [[instance]] and [[process]] pointers of the base type by the [[connect]] method, so we can reference them later. TODO: In case of NLO event generation, copying the configuration from the master process is rather intransparent. For instance, we override the process var list by the global var list. <>= procedure :: init => entry_init <>= subroutine entry_init & (entry, process_id, & use_process, integrate, generate, update_sqme, & support_resonance_history, & local, global, n_alt) class(entry_t), intent(inout), target :: entry type(string_t), intent(in) :: process_id logical, intent(in) :: use_process, integrate, generate, update_sqme logical, intent(in) :: support_resonance_history type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global integer, intent(in), optional :: n_alt type(process_t), pointer :: process, master_process type(process_instance_t), pointer :: process_instance type(process_library_t), pointer :: prclib_saved integer :: i logical :: res_include_trivial logical :: combined_integration integer :: selected_mci selected_mci = 0 if (debug_on) call msg_debug (D_CORE, "entry_init") if (debug_on) call msg_debug (D_CORE, "process_id", process_id) call prepare_process & (master_process, process_id, use_process, integrate, local, global) if (signal_is_pending ()) return if (associated (master_process)) then if (.not. master_process%has_matrix_element ()) then entry%has_integral = .true. entry%process_id = process_id entry%valid = .false. return end if else call entry%basic_init (local%var_list) entry%has_integral = .false. entry%process_id = process_id call entry%import_process_def_characteristics (local%prclib, process_id) entry%valid = .true. return end if call entry%basic_init (local%var_list, n_alt) entry%process_id = process_id if (generate .or. integrate) then entry%run_id = master_process%get_run_id () process => master_process else call local%set_log (var_str ("?rebuild_phase_space"), & .false., is_known = .true.) call local%set_log (var_str ("?check_phs_file"), & .false., is_known = .true.) call local%set_log (var_str ("?rebuild_grids"), & .false., is_known = .true.) entry%run_id = & local%var_list%get_sval (var_str ("$run_id")) if (update_sqme) then call prepare_local_process (process, process_id, local) else process => master_process end if end if call entry%import_process_characteristics (process) allocate (entry%mci_sets (entry%n_mci)) do i = 1, size (entry%mci_sets) call entry%mci_sets(i)%init (i, master_process) end do call entry%import_process_results (master_process) call entry%prepare_expressions (local) if (process%is_nlo_calculation ()) then call process%init_nlo_settings (global%var_list) end if combined_integration = local%get_lval (var_str ("?combined_nlo_integration")) if (.not. combined_integration) & selected_mci = process%extract_active_component_mci () call prepare_process_instance (process_instance, process, local%model, & local = local) if (generate) then if (selected_mci > 0) then call process%prepare_simulation (selected_mci) call process_instance%init_simulation (selected_mci, entry%config%safety_factor, & local%get_lval (var_str ("?keep_failed_events"))) else do i = 1, entry%n_mci call process%prepare_simulation (i) call process_instance%init_simulation (i, entry%config%safety_factor, & local%get_lval (var_str ("?keep_failed_events"))) end do end if end if if (support_resonance_history) then prclib_saved => local%prclib call entry%setup_resonant_subprocesses (local, process) if (entry%has_resonant_subprocess_set) then if (signal_is_pending ()) return call entry%compile_resonant_subprocesses (local) if (signal_is_pending ()) return call entry%prepare_resonant_subprocesses (local, global) if (signal_is_pending ()) return call entry%prepare_resonant_subprocess_instances (local) end if if (signal_is_pending ()) return if (associated (prclib_saved)) call local%update_prclib (prclib_saved) end if call entry%setup_event_transforms (process, local) call dispatch_qcd (entry%qcd, local%get_var_list_ptr (), local%os_data) call entry%connect_qcd () - select type (pcm => process_instance%pcm) - class is (pcm_instance_nlo_t) - select type (config => pcm%config) - type is (pcm_nlo_t) - if (config%settings%fixed_order_nlo) & - call pcm%set_fixed_order_event_mode () - end select - end select - if (present (global)) then call entry%connect (process_instance, local%model, global%process_stack) else call entry%connect (process_instance, local%model, local%process_stack) end if call entry%setup_expressions () entry%model => process%get_model_ptr () entry%valid = .true. end subroutine entry_init @ %def entry_init @ <>= procedure :: set_active_real_components => entry_set_active_real_components <>= subroutine entry_set_active_real_components (entry) class(entry_t), intent(inout) :: entry integer :: i_active_real select type (pcm => entry%instance%pcm) class is (pcm_instance_nlo_t) i_active_real = entry%instance%get_real_of_mci () if (debug_on) call msg_debug2 (D_CORE, "i_active_real", i_active_real) if (associated (entry%evt_powheg)) then select type (evt => entry%evt_powheg) type is (evt_shower_t) if (entry%process%get_component_type(i_active_real) == COMP_REAL_FIN) then if (debug_on) call msg_debug (D_CORE, "Disabling Powheg matching for ", i_active_real) call evt%disable_powheg_matching () else if (debug_on) call msg_debug (D_CORE, "Enabling Powheg matching for ", i_active_real) call evt%enable_powheg_matching () end if class default call msg_fatal ("powheg-evt should be evt_shower_t!") end select end if end select end subroutine entry_set_active_real_components @ %def entry_set_active_real_components @ Part of simulation-entry initialization: set up a process object for local use. <>= subroutine prepare_local_process (process, process_id, local) type(process_t), pointer, intent(inout) :: process type(string_t), intent(in) :: process_id type(rt_data_t), intent(inout), target :: local type(integration_t) :: intg call intg%create_process (process_id) call intg%init_process (local) call intg%setup_process (local, verbose=.false.) process => intg%get_process_ptr () end subroutine prepare_local_process @ %def prepare_local_process @ Part of simulation-entry initialization: set up a process instance matching the selected process object. The model that we can provide as an extra argument can modify particle settings (polarization) in the density matrices that will be constructed. It does not affect parameters. <>= subroutine prepare_process_instance & (process_instance, process, model, local) type(process_instance_t), pointer, intent(inout) :: process_instance type(process_t), intent(inout), target :: process class(model_data_t), intent(in), optional :: model type(rt_data_t), intent(in), optional, target :: local allocate (process_instance) call process_instance%init (process) if (process%is_nlo_calculation ()) then select type (pcm => process_instance%pcm) type is (pcm_instance_nlo_t) select type (config => pcm%config) type is (pcm_nlo_t) if (.not. config%settings%combined_integration) & call pcm%set_radiation_event () + if (config%settings%fixed_order_nlo) & + call pcm%set_fixed_order_event_mode () end select end select call process%prepare_any_external_code () end if call process_instance%setup_event_data (model) end subroutine prepare_process_instance @ %def prepare_process_instance @ Part of simulation-entry initialization: query the process for basic information. <>= procedure, private :: import_process_characteristics & => entry_import_process_characteristics <>= subroutine entry_import_process_characteristics (entry, process) class(entry_t), intent(inout) :: entry type(process_t), intent(in), target :: process entry%library = process%get_library_name () entry%n_in = process%get_n_in () entry%n_mci = process%get_n_mci () end subroutine entry_import_process_characteristics @ %def entry_import_process_characteristics @ This is the alternative form which applies if there is no process entry, but just a process definition which we take from the provided [[prclib]] definition library. <>= procedure, private :: import_process_def_characteristics & => entry_import_process_def_characteristics <>= subroutine entry_import_process_def_characteristics (entry, prclib, id) class(entry_t), intent(inout) :: entry type(process_library_t), intent(in), target :: prclib type(string_t), intent(in) :: id entry%library = prclib%get_name () entry%n_in = prclib%get_n_in (id) end subroutine entry_import_process_def_characteristics @ %def entry_import_process_def_characteristics @ Part of simulation-entry initialization: query the process for integration results. <>= procedure, private :: import_process_results & => entry_import_process_results <>= subroutine entry_import_process_results (entry, process) class(entry_t), intent(inout) :: entry type(process_t), intent(in), target :: process if (process%has_integral ()) then entry%integral = process%get_integral () entry%error = process%get_error () call entry%set_sigma (entry%integral) entry%has_integral = .true. end if end subroutine entry_import_process_results @ %def entry_import_process_characteristics @ Part of simulation-entry initialization: create expression factory objects and store them. <>= procedure, private :: prepare_expressions & => entry_prepare_expressions <>= subroutine entry_prepare_expressions (entry, local) class(entry_t), intent(inout) :: entry type(rt_data_t), intent(in), target :: local type(eval_tree_factory_t) :: expr_factory call expr_factory%init (local%pn%selection_lexpr) call entry%set_selection (expr_factory) call expr_factory%init (local%pn%reweight_expr) call entry%set_reweight (expr_factory) call expr_factory%init (local%pn%analysis_lexpr) call entry%set_analysis (expr_factory) end subroutine entry_prepare_expressions @ %def entry_prepare_expressions @ \subsubsection{Extra (NLO) entries} Initializes the list of additional NLO entries. The routine gets the information about how many entries to associate from [[region_data]]. <>= procedure :: setup_additional_entries => entry_setup_additional_entries <>= subroutine entry_setup_additional_entries (entry) class(entry_t), intent(inout), target :: entry type(entry_t), pointer :: current_entry integer :: i, n_phs type(evt_nlo_t), pointer :: evt integer :: mode evt => null () select type (pcm => entry%instance%pcm) class is (pcm_instance_nlo_t) select type (config => pcm%config) type is (pcm_nlo_t) n_phs = config%region_data%n_phs end select end select select type (entry) type is (entry_t) current_entry => entry current_entry%first => entry call get_nlo_evt_ptr (current_entry, evt, mode) if (mode > EVT_NLO_SEPARATE_BORNLIKE) then allocate (evt%particle_set_nlo (n_phs + 1)) evt%event_deps%n_phs = n_phs evt%qcd = entry%qcd do i = 1, n_phs allocate (current_entry%next) current_entry%next%first => current_entry%first current_entry => current_entry%next call entry%copy_entry (current_entry) current_entry%i_event = i end do else allocate (evt%particle_set_nlo (1)) end if end select contains subroutine get_nlo_evt_ptr (entry, evt, mode) type(entry_t), intent(in), target :: entry type(evt_nlo_t), intent(out), pointer :: evt integer, intent(out) :: mode class(evt_t), pointer :: current_evt evt => null () current_evt => entry%transform_first do select type (current_evt) type is (evt_nlo_t) evt => current_evt mode = evt%mode exit end select if (associated (current_evt%next)) then current_evt => current_evt%next else call msg_fatal ("evt_nlo not in list of event transforms") end if end do end subroutine get_nlo_evt_ptr end subroutine entry_setup_additional_entries @ %def entry_setup_additional_entries @ <>= procedure :: get_first => entry_get_first <>= function entry_get_first (entry) result (entry_out) class(entry_t), intent(in), target :: entry type(entry_t), pointer :: entry_out entry_out => null () select type (entry) type is (entry_t) if (entry%is_nlo ()) then entry_out => entry%first else entry_out => entry end if end select end function entry_get_first @ %def entry_get_first @ <>= procedure :: get_next => entry_get_next <>= function entry_get_next (entry) result (next_entry) class(entry_t), intent(in) :: entry type(entry_t), pointer :: next_entry next_entry => null () if (associated (entry%next)) then next_entry => entry%next else call msg_fatal ("Get next entry: No next entry") end if end function entry_get_next @ %def entry_get_next @ <>= procedure :: count_nlo_entries => entry_count_nlo_entries <>= function entry_count_nlo_entries (entry) result (n) class(entry_t), intent(in), target :: entry integer :: n type(entry_t), pointer :: current_entry n = 1 if (.not. associated (entry%next)) then return else current_entry => entry%next do n = n + 1 if (.not. associated (current_entry%next)) exit current_entry => current_entry%next end do end if end function entry_count_nlo_entries @ %def entry_count_nlo_entries @ <>= procedure :: reset_nlo_counter => entry_reset_nlo_counter <>= subroutine entry_reset_nlo_counter (entry) class(entry_t), intent(inout) :: entry class(evt_t), pointer :: evt evt => entry%transform_first do select type (evt) type is (evt_nlo_t) evt%i_evaluation = 0 exit end select if (associated (evt%next)) evt => evt%next end do end subroutine entry_reset_nlo_counter @ %def entry_reset_nlo_counter @ <>= procedure :: determine_if_powheg_matching => entry_determine_if_powheg_matching <>= subroutine entry_determine_if_powheg_matching (entry) class(entry_t), intent(inout) :: entry class(evt_t), pointer :: current_transform if (associated (entry%transform_first)) then current_transform => entry%transform_first do select type (current_transform) type is (evt_shower_t) if (current_transform%contains_powheg_matching ()) & entry%evt_powheg => current_transform exit end select if (associated (current_transform%next)) then current_transform => current_transform%next else exit end if end do end if end subroutine entry_determine_if_powheg_matching @ %def entry_determine_if_powheg_matching @ \subsubsection{Event-transform initialization} Part of simulation-entry initialization: dispatch event transforms (decay, shower) as requested. If a transform is not applicable or switched off via some variable, it will be skipped. Regarding resonances/decays: these two transforms are currently mutually exclusive. Resonance insertion will not be applied if there is an unstable particle in the game. The initial particle set is the output of the trivial transform; this has already been applied when the transforms listed here are encountered. Each transform takes a particle set and produces a new one, with one exception: the decay module takes its input from the process object, ignoring the trivial transform. (Reason: spin correlations.) Therefore, the decay module must be first in line. Settings that we don't or can't support (yet) are rejected by the embedded call to [[event_transforms_check]]. <>= procedure, private :: setup_event_transforms & => entry_setup_event_transforms <>= subroutine entry_setup_event_transforms (entry, process, local) class(entry_t), intent(inout) :: entry type(process_t), intent(inout), target :: process type(rt_data_t), intent(in), target :: local class(evt_t), pointer :: evt type(var_list_t), pointer :: var_list logical :: enable_isr_handler logical :: enable_epa_handler logical :: enable_fixed_order logical :: enable_shower character(len=7) :: sample_normalization call event_transforms_check (entry, process, local) var_list => local%get_var_list_ptr () if (process%contains_unstable (local%model)) then call dispatch_evt_decay (evt, local%var_list) if (associated (evt)) call entry%import_transform (evt) end if if (entry%resonant_subprocess_set%is_active ()) then call dispatch_evt_resonance (evt, local%var_list, & entry%resonant_subprocess_set%get_resonance_history_set (), & entry%resonant_subprocess_set%get_libname ()) if (associated (evt)) then call entry%resonant_subprocess_set%connect_transform (evt) call entry%resonant_subprocess_set%set_on_shell_limit & (local%get_rval (var_str ("resonance_on_shell_limit"))) call entry%resonant_subprocess_set%set_on_shell_turnoff & (local%get_rval (var_str ("resonance_on_shell_turnoff"))) call entry%resonant_subprocess_set%set_background_factor & (local%get_rval (var_str ("resonance_background_factor"))) call entry%import_transform (evt) end if end if enable_fixed_order = local%get_lval (var_str ("?fixed_order_nlo_events")) if (enable_fixed_order) then call dispatch_evt_nlo & (evt, local%get_lval (var_str ("?keep_failed_events"))) call entry%import_transform (evt) end if enable_isr_handler = local%get_lval (var_str ("?isr_handler")) enable_epa_handler = local%get_lval (var_str ("?epa_handler")) if (enable_isr_handler .or. enable_epa_handler) then call dispatch_evt_isr_epa_handler (evt, local%var_list) if (associated (evt)) call entry%import_transform (evt) end if enable_shower = local%get_lval (var_str ("?allow_shower")) .and. & (local%get_lval (var_str ("?ps_isr_active")) & .or. local%get_lval (var_str ("?ps_fsr_active")) & .or. local%get_lval (var_str ("?muli_active")) & .or. local%get_lval (var_str ("?mlm_matching")) & .or. local%get_lval (var_str ("?ckkw_matching")) & .or. local%get_lval (var_str ("?powheg_matching"))) if (enable_shower) then call dispatch_evt_shower (evt, var_list, local%model, & local%fallback_model, local%os_data, local%beam_structure, & process) call entry%import_transform (evt) end if if (local%get_lval (var_str ("?hadronization_active"))) then call dispatch_evt_hadrons (evt, var_list, local%fallback_model) call entry%import_transform (evt) end if end subroutine entry_setup_event_transforms @ %def entry_setup_event_transforms @ This routine rejects all event-transform settings which we don't support at present. <>= subroutine event_transforms_check (entry, process, local) class(entry_t), intent(in) :: entry type(process_t), intent(in), target :: process type(rt_data_t), intent(in), target :: local if (local%get_lval (var_str ("?fixed_order_nlo_events"))) then if (local%get_lval (var_str ("?unweighted"))) then call msg_fatal ("NLO fixed-order events have to be generated with & &?unweighted = false") end if select case (char (local%get_sval (var_str ("$sample_normalization")))) case ("sigma", "auto") case default call msg_fatal ("NLO fixed-order events: only & &$sample_normalization = 'sigma' is supported.") end select if (process%contains_unstable (local%model)) then call msg_fatal ("NLO fixed-order events: unstable final-state & &particles not supported yet") end if if (entry%resonant_subprocess_set%is_active ()) then call msg_fatal ("NLO fixed-order events: resonant subprocess & &insertion not supported") end if if (local%get_lval (var_str ("?isr_handler")) & .or. local%get_lval (var_str ("?epa_handler"))) then call msg_fatal ("NLO fixed-order events: ISR handler for & &photon-pT generation not supported yet") end if end if if (process%contains_unstable (local%model) & .and. entry%resonant_subprocess_set%is_active ()) then call msg_fatal ("Simulation: resonant subprocess insertion with & &unstable final-state particles not supported") end if end subroutine event_transforms_check @ %def event_transforms_check @ \subsubsection{Process/MCI selector} Compute weights. The integral in the argument is the sum of integrals for all processes in the sample. After computing the process weights, we repeat the normalization procedure for the process components. <>= procedure :: init_mci_selector => entry_init_mci_selector <>= subroutine entry_init_mci_selector (entry, negative_weights) class(entry_t), intent(inout), target :: entry logical, intent(in), optional :: negative_weights type(entry_t), pointer :: current_entry integer :: i, j, k if (debug_on) call msg_debug (D_CORE, "entry_init_mci_selector") if (entry%has_integral) then select type (entry) type is (entry_t) current_entry => entry do j = 1, current_entry%count_nlo_entries () if (j > 1) current_entry => current_entry%get_next () do k = 1, size(current_entry%mci_sets%integral) if (debug_on) call msg_debug (D_CORE, "current_entry%mci_sets(k)%integral", & current_entry%mci_sets(k)%integral) end do call current_entry%mci_selector%init & (current_entry%mci_sets%integral, negative_weights) do i = 1, current_entry%n_mci current_entry%mci_sets(i)%weight_mci = & current_entry%mci_selector%get_weight (i) end do end do end select end if end subroutine entry_init_mci_selector @ %def entry_init_mci_selector @ Select a MCI entry, using the embedded random-number generator. <>= procedure :: select_mci => entry_select_mci <>= function entry_select_mci (entry) result (i_mci) class(entry_t), intent(inout) :: entry integer :: i_mci if (debug_on) call msg_debug2 (D_CORE, "entry_select_mci") i_mci = entry%process%extract_active_component_mci () if (i_mci == 0) call entry%mci_selector%generate (entry%rng, i_mci) if (debug_on) call msg_debug2 (D_CORE, "i_mci", i_mci) end function entry_select_mci @ %def entry_select_mci @ \subsubsection{Entries: event-wise updates} Record an event for this entry, i.e., increment the appropriate counters. <>= procedure :: record => entry_record <>= subroutine entry_record (entry, i_mci, from_file) class(entry_t), intent(inout) :: entry integer, intent(in) :: i_mci logical, intent(in), optional :: from_file real(default) :: weight, excess integer :: n_dropped weight = entry%get_weight_prc () excess = entry%get_excess_prc () n_dropped = entry%get_n_dropped () call entry%counter%record (weight, excess, n_dropped, from_file) if (i_mci > 0) then call entry%mci_sets(i_mci)%counter%record (weight, excess) end if end subroutine entry_record @ %def entry_record @ Update and restore the process core that this entry accesses, when parameters change. If explicit arguments [[model]], [[qcd]], or [[helicity_selection]] are provided, use those. Otherwise use the parameters stored in the process object. These two procedures come with a caching mechanism which guarantees that the current core object is saved when calling [[update_process]], and restored by calling [[restore_process]]. If the flag [[saved]] is unset, saving is skipped, and the [[restore]] procedure should not be called. <>= procedure :: update_process => entry_update_process procedure :: restore_process => entry_restore_process <>= subroutine entry_update_process & (entry, model, qcd, helicity_selection, saved) class(entry_t), intent(inout) :: entry class(model_data_t), intent(in), optional, target :: model type(qcd_t), intent(in), optional :: qcd type(helicity_selection_t), intent(in), optional :: helicity_selection logical, intent(in), optional :: saved type(process_t), pointer :: process class(prc_core_t), allocatable :: core integer :: i, n_terms class(model_data_t), pointer :: model_local type(qcd_t) :: qcd_local logical :: use_saved if (present (model)) then model_local => model else model_local => entry%model end if if (present (qcd)) then qcd_local = qcd else qcd_local = entry%qcd end if use_saved = .true.; if (present (saved)) use_saved = saved process => entry%get_process_ptr () n_terms = process%get_n_terms () if (use_saved) allocate (entry%core_safe (n_terms)) do i = 1, n_terms if (process%has_matrix_element (i, is_term_index = .true.)) then call process%extract_core (i, core) if (use_saved) then call dispatch_core_update (core, & model_local, helicity_selection, qcd_local, & entry%core_safe(i)%core) else call dispatch_core_update (core, & model_local, helicity_selection, qcd_local) end if call process%restore_core (i, core) end if end do end subroutine entry_update_process subroutine entry_restore_process (entry) class(entry_t), intent(inout) :: entry type(process_t), pointer :: process class(prc_core_t), allocatable :: core integer :: i, n_terms process => entry%get_process_ptr () n_terms = process%get_n_terms () do i = 1, n_terms if (process%has_matrix_element (i, is_term_index = .true.)) then call process%extract_core (i, core) call dispatch_core_restore (core, entry%core_safe(i)%core) call process%restore_core (i, core) end if end do deallocate (entry%core_safe) end subroutine entry_restore_process @ %def entry_update_process @ %def entry_restore_process <>= procedure :: connect_qcd => entry_connect_qcd <>= subroutine entry_connect_qcd (entry) class(entry_t), intent(inout), target :: entry class(evt_t), pointer :: evt evt => entry%transform_first do while (associated (evt)) select type (evt) type is (evt_shower_t) evt%qcd = entry%qcd if (allocated (evt%matching)) then evt%matching%qcd = entry%qcd end if end select evt => evt%next end do end subroutine entry_connect_qcd @ %def entry_connect_qcd @ \subsection{Handling resonant subprocesses} Resonant subprocesses are required if we want to determine resonance histories when generating events. The feature is optional, to be switched on by the user. This procedure initializes a new, separate process library that contains copies of the current process, restricted to the relevant resonance histories. (If this library exists already, it is just kept.) The histories can be extracted from the process object. The code has to match the assignments in [[create_resonant_subprocess_library]]. The library may already exist -- in that case, here it will be recovered without recompilation. <>= procedure :: setup_resonant_subprocesses & => entry_setup_resonant_subprocesses <>= subroutine entry_setup_resonant_subprocesses (entry, global, process) class(entry_t), intent(inout) :: entry type(rt_data_t), intent(inout), target :: global type(process_t), intent(in), target :: process type(string_t) :: libname type(resonance_history_set_t) :: res_history_set type(process_library_t), pointer :: lib type(process_component_def_t), pointer :: process_component_def logical :: req_resonant, library_exist integer :: i_component libname = process%get_library_name () lib => global%prclib_stack%get_library_ptr (libname) entry%has_resonant_subprocess_set = lib%req_resonant (process%get_id ()) if (entry%has_resonant_subprocess_set) then libname = get_libname_res (process%get_id ()) call entry%resonant_subprocess_set%init (process%get_n_components ()) call entry%resonant_subprocess_set%create_library & (libname, global, library_exist) do i_component = 1, process%get_n_components () call process%extract_resonance_history_set & (res_history_set, i_component = i_component) call entry%resonant_subprocess_set%fill_resonances & (res_history_set, i_component) if (.not. library_exist) then process_component_def & => process%get_component_def_ptr (i_component) call entry%resonant_subprocess_set%add_to_library & (i_component, & process_component_def%get_prt_spec_in (), & process_component_def%get_prt_spec_out (), & global) end if end do call entry%resonant_subprocess_set%freeze_library (global) end if end subroutine entry_setup_resonant_subprocesses @ %def entry_setup_resonant_subprocesses @ Compile the resonant-subprocesses library. The library is assumed to be the current library in the [[global]] object. This is a simple wrapper. <>= procedure :: compile_resonant_subprocesses & => entry_compile_resonant_subprocesses <>= subroutine entry_compile_resonant_subprocesses (entry, global) class(entry_t), intent(inout) :: entry type(rt_data_t), intent(inout), target :: global call entry%resonant_subprocess_set%compile_library (global) end subroutine entry_compile_resonant_subprocesses @ %def entry_compile_resonant_subprocesses @ Prepare process objects for the resonant-subprocesses library. The process objects are appended to the global process stack. We initialize the processes, such that we can evaluate matrix elements, but we do not need to integrate them. <>= procedure :: prepare_resonant_subprocesses & => entry_prepare_resonant_subprocesses <>= subroutine entry_prepare_resonant_subprocesses (entry, local, global) class(entry_t), intent(inout) :: entry type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global call entry%resonant_subprocess_set%prepare_process_objects (local, global) end subroutine entry_prepare_resonant_subprocesses @ %def entry_prepare_resonant_subprocesses @ Prepare process instances. They are linked to their corresponding process objects. Both, process and instance objects, are allocated as anonymous targets inside the [[resonant_subprocess_set]] component. NOTE: those anonymous object are likely forgotten during finalization of the parent [[event_t]] (extended as [[entry_t]]) object. This should be checked! The memory leak is probably harmless as long as the event object is created once per run, not once per event. <>= procedure :: prepare_resonant_subprocess_instances & => entry_prepare_resonant_subprocess_instances <>= subroutine entry_prepare_resonant_subprocess_instances (entry, global) class(entry_t), intent(inout) :: entry type(rt_data_t), intent(in), target :: global call entry%resonant_subprocess_set%prepare_process_instances (global) end subroutine entry_prepare_resonant_subprocess_instances @ %def entry_prepare_resonant_subprocess_instances @ Display the resonant subprocesses. This includes, upon request, the resonance set that defines those subprocess, and a short or long account of the process objects themselves. <>= procedure :: write_resonant_subprocess_data & => entry_write_resonant_subprocess_data <>= subroutine entry_write_resonant_subprocess_data (entry, unit) class(entry_t), intent(in) :: entry integer, intent(in), optional :: unit integer :: u, i u = given_output_unit (unit) call entry%resonant_subprocess_set%write (unit) write (u, "(1x,A,I0)") "Resonant subprocesses refer to & &process component #", 1 end subroutine entry_write_resonant_subprocess_data @ %def entry_write_resonant_subprocess_data @ Display of the master process for the current event, for diagnostics. <>= procedure :: write_process_data => entry_write_process_data <>= subroutine entry_write_process_data & (entry, unit, show_process, show_instance, verbose) class(entry_t), intent(in) :: entry integer, intent(in), optional :: unit logical, intent(in), optional :: show_process logical, intent(in), optional :: show_instance logical, intent(in), optional :: verbose integer :: u, i logical :: s_proc, s_inst, verb type(process_t), pointer :: process type(process_instance_t), pointer :: instance u = given_output_unit (unit) s_proc = .false.; if (present (show_process)) s_proc = show_process s_inst = .false.; if (present (show_instance)) s_inst = show_instance verb = .false.; if (present (verbose)) verb = verbose if (s_proc .or. s_inst) then write (u, "(1x,A,':')") "Process data" if (s_proc) then process => entry%process if (associated (process)) then if (verb) then call write_separator (u, 2) call process%write (.false., u) else call process%show (u, verbose=.false.) end if else write (u, "(3x,A)") "[not associated]" end if end if if (s_inst) then instance => entry%instance if (associated (instance)) then if (verb) then call instance%write (u) else call instance%write_header (u) end if else write (u, "(3x,A)") "Process instance: [not associated]" end if end if end if end subroutine entry_write_process_data @ %def entry_write_process_data @ \subsection{Entries for alternative environment} Entries for alternate environments. [No additional components anymore, so somewhat redundant.] <>= type, extends (entry_t) :: alt_entry_t contains <> end type alt_entry_t @ %def alt_entry_t @ The alternative entries are there to re-evaluate the event, given momenta, in a different context. Therefore, we allocate a local process object and use this as the reference for the local process instance, when initializing the entry. We temporarily import the [[process]] object into an [[integration_t]] wrapper, to take advantage of the associated methods. The local process object is built in the context of the current environment, here called [[global]]. Then, we initialize the process instance. The [[master_process]] object contains the integration results to which we refer when recalculating an event. Therefore, we use this object instead of the locally built [[process]] when we extract the integration results. The locally built [[process]] object should be finalized when done. It remains accessible via the [[event_t]] base object of [[entry]], which contains pointers to the process and instance. <>= procedure :: init_alt => alt_entry_init <>= subroutine alt_entry_init (entry, process_id, master_process, local) class(alt_entry_t), intent(inout), target :: entry type(string_t), intent(in) :: process_id type(process_t), intent(in), target :: master_process type(rt_data_t), intent(inout), target :: local type(process_t), pointer :: process type(process_instance_t), pointer :: process_instance type(string_t) :: run_id integer :: i call msg_message ("Simulate: initializing alternate process setup ...") run_id = & local%var_list%get_sval (var_str ("$run_id")) call local%set_log (var_str ("?rebuild_phase_space"), & .false., is_known = .true.) call local%set_log (var_str ("?check_phs_file"), & .false., is_known = .true.) call local%set_log (var_str ("?rebuild_grids"), & .false., is_known = .true.) call entry%basic_init (local%var_list) call prepare_local_process (process, process_id, local) entry%process_id = process_id entry%run_id = run_id call entry%import_process_characteristics (process) allocate (entry%mci_sets (entry%n_mci)) do i = 1, size (entry%mci_sets) call entry%mci_sets(i)%init (i, master_process) end do call entry%import_process_results (master_process) call entry%prepare_expressions (local) call prepare_process_instance (process_instance, process, local%model) call entry%setup_event_transforms (process, local) call entry%connect (process_instance, local%model, local%process_stack) call entry%setup_expressions () entry%model => process%get_model_ptr () call msg_message ("... alternate process setup complete.") end subroutine alt_entry_init @ %def alt_entry_init @ Copy the particle set from the master entry to the alternate entry. This is the particle set of the hard process. <>= procedure :: fill_particle_set => entry_fill_particle_set <>= subroutine entry_fill_particle_set (alt_entry, entry) class(alt_entry_t), intent(inout) :: alt_entry class(entry_t), intent(in), target :: entry type(particle_set_t) :: pset call entry%get_hard_particle_set (pset) call alt_entry%set_hard_particle_set (pset) call pset%final () end subroutine entry_fill_particle_set @ %def particle_set_copy_prt @ \subsection{The simulation object} Each simulation object corresponds to an event sample, identified by the [[sample_id]]. The simulation may cover several processes simultaneously. All process-specific data, including the event records, are stored in the [[entry]] subobjects. The [[current]] index indicates which record was selected last. [[version]] is foreseen to contain a tag on the \whizard\ event file version. It can be <>= public :: simulation_t <>= type :: simulation_t private type(rt_data_t), pointer :: local => null () type(string_t) :: sample_id logical :: unweighted = .true. logical :: negative_weights = .false. logical :: support_resonance_history = .false. logical :: respect_selection = .true. integer :: norm_mode = NORM_UNDEFINED logical :: update_sqme = .false. logical :: update_weight = .false. logical :: update_event = .false. logical :: recover_beams = .false. logical :: pacify = .false. integer :: n_max_tries = 10000 integer :: n_prc = 0 integer :: n_alt = 0 logical :: has_integral = .false. logical :: valid = .false. real(default) :: integral = 0 real(default) :: error = 0 integer :: version = 1 character(32) :: md5sum_prc = "" character(32) :: md5sum_cfg = "" character(32), dimension(:), allocatable :: md5sum_alt type(entry_t), dimension(:), allocatable :: entry type(alt_entry_t), dimension(:,:), allocatable :: alt_entry type(selector_t) :: process_selector integer :: n_evt_requested = 0 integer :: event_index_offset = 0 logical :: event_index_set = .false. integer :: event_index = 0 integer :: split_n_evt = 0 integer :: split_n_kbytes = 0 integer :: split_index = 0 type(counter_t) :: counter class(rng_t), allocatable :: rng integer :: i_prc = 0 integer :: i_mci = 0 real(default) :: weight = 0 real(default) :: excess = 0 integer :: n_dropped = 0 contains <> end type simulation_t @ %def simulation_t @ \subsubsection{Output of the simulation data} [[write_config]] writes just the configuration. [[write]] as a method of the base type [[event_t]] writes the current event and process instance, depending on options. <>= procedure :: write => simulation_write <>= subroutine simulation_write (object, unit, testflag) class(simulation_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: testflag logical :: pacified integer :: u, i u = given_output_unit (unit) pacified = object%pacify; if (present (testflag)) pacified = testflag call write_separator (u, 2) write (u, "(1x,A,A,A)") "Event sample: '", char (object%sample_id), "'" write (u, "(3x,A,I0)") "Processes = ", object%n_prc if (object%n_alt > 0) then write (u, "(3x,A,I0)") "Alt.wgts = ", object%n_alt end if write (u, "(3x,A,L1)") "Unweighted = ", object%unweighted write (u, "(3x,A,A)") "Event norm = ", & char (event_normalization_string (object%norm_mode)) write (u, "(3x,A,L1)") "Neg. weights = ", object%negative_weights write (u, "(3x,A,L1)") "Res. history = ", object%support_resonance_history write (u, "(3x,A,L1)") "Respect sel. = ", object%respect_selection write (u, "(3x,A,L1)") "Update sqme = ", object%update_sqme write (u, "(3x,A,L1)") "Update wgt = ", object%update_weight write (u, "(3x,A,L1)") "Update event = ", object%update_event write (u, "(3x,A,L1)") "Recov. beams = ", object%recover_beams write (u, "(3x,A,L1)") "Pacify = ", object%pacify write (u, "(3x,A,I0)") "Max. tries = ", object%n_max_tries if (object%has_integral) then if (pacified) then write (u, "(3x,A," // FMT_15 // ")") & "Integral = ", object%integral write (u, "(3x,A," // FMT_15 // ")") & "Error = ", object%error else write (u, "(3x,A," // FMT_19 // ")") & "Integral = ", object%integral write (u, "(3x,A," // FMT_19 // ")") & "Error = ", object%error end if else write (u, "(3x,A)") "Integral = [undefined]" end if write (u, "(3x,A,L1)") "Sim. valid = ", object%valid write (u, "(3x,A,I0)") "Ev.file ver. = ", object%version if (object%md5sum_prc /= "") then write (u, "(3x,A,A,A)") "MD5 sum (proc) = '", object%md5sum_prc, "'" end if if (object%md5sum_cfg /= "") then write (u, "(3x,A,A,A)") "MD5 sum (config) = '", object%md5sum_cfg, "'" end if write (u, "(3x,A,I0)") "Events requested = ", object%n_evt_requested if (object%event_index_offset /= 0) then write (u, "(3x,A,I0)") "Event index offset= ", object%event_index_offset end if if (object%event_index_set) then write (u, "(3x,A,I0)") "Event index = ", object%event_index end if if (object%split_n_evt > 0 .or. object%split_n_kbytes > 0) then write (u, "(3x,A,I0)") "Events per file = ", object%split_n_evt write (u, "(3x,A,I0)") "KBytes per file = ", object%split_n_kbytes write (u, "(3x,A,I0)") "First file index = ", object%split_index end if call object%counter%write (u) call write_separator (u) if (object%i_prc /= 0) then write (u, "(1x,A)") "Current event:" write (u, "(3x,A,I0,A,A)") "Process #", & object%i_prc, ": ", & char (object%entry(object%i_prc)%process_id) write (u, "(3x,A,I0)") "MCI set #", object%i_mci write (u, "(3x,A," // FMT_19 // ")") "Weight = ", object%weight if (.not. vanishes (object%excess)) & write (u, "(3x,A," // FMT_19 // ")") "Excess = ", object%excess write (u, "(3x,A,I0)") "Zero-weight events dropped = ", object%n_dropped else write (u, "(1x,A,I0,A,A)") "Current event: [undefined]" end if call write_separator (u) if (allocated (object%rng)) then call object%rng%write (u) else write (u, "(3x,A)") "Random-number generator: [undefined]" end if if (allocated (object%entry)) then do i = 1, size (object%entry) if (i == 1) then call write_separator (u, 2) else call write_separator (u) end if write (u, "(1x,A,I0,A)") "Process #", i, ":" call object%entry(i)%write_config (u, pacified) end do end if call write_separator (u, 2) end subroutine simulation_write @ %def simulation_write @ Write the current event record. If an explicit index is given, write that event record. We implement writing to [[unit]] (event contents / debugging format) and writing to an [[eio]] event stream (storage). We include a [[testflag]] in order to suppress numerical noise in the testsuite. <>= generic :: write_event => write_event_unit procedure :: write_event_unit => simulation_write_event_unit <>= subroutine simulation_write_event_unit & (object, unit, i_prc, verbose, testflag) class(simulation_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: verbose integer, intent(in), optional :: i_prc logical, intent(in), optional :: testflag logical :: pacified integer :: current pacified = .false.; if (present(testflag)) pacified = testflag pacified = pacified .or. object%pacify if (present (i_prc)) then current = i_prc else current = object%i_prc end if if (current > 0) then call object%entry(current)%write (unit, verbose = verbose, & testflag = pacified) else call msg_fatal ("Simulation: write event: no process selected") end if end subroutine simulation_write_event_unit @ %def simulation_write_event @ This writes one of the alternate events, if allocated. <>= procedure :: write_alt_event => simulation_write_alt_event <>= subroutine simulation_write_alt_event (object, unit, j_alt, i_prc, & verbose, testflag) class(simulation_t), intent(in) :: object integer, intent(in), optional :: unit integer, intent(in), optional :: j_alt integer, intent(in), optional :: i_prc logical, intent(in), optional :: verbose logical, intent(in), optional :: testflag integer :: i, j if (present (j_alt)) then j = j_alt else j = 1 end if if (present (i_prc)) then i = i_prc else i = object%i_prc end if if (i > 0) then if (j> 0 .and. j <= object%n_alt) then call object%alt_entry(i,j)%write (unit, verbose = verbose, & testflag = testflag) else call msg_fatal ("Simulation: write alternate event: out of range") end if else call msg_fatal ("Simulation: write alternate event: no process selected") end if end subroutine simulation_write_alt_event @ %def simulation_write_alt_event @ This writes the contents of the resonant subprocess set in the current event record. <>= procedure :: write_resonant_subprocess_data & => simulation_write_resonant_subprocess_data <>= subroutine simulation_write_resonant_subprocess_data (object, unit, i_prc) class(simulation_t), intent(in) :: object integer, intent(in), optional :: unit integer, intent(in), optional :: i_prc integer :: i if (present (i_prc)) then i = i_prc else i = object%i_prc end if call object%entry(i)%write_resonant_subprocess_data (unit) end subroutine simulation_write_resonant_subprocess_data @ %def simulation_write_resonant_subprocess_data @ The same for the master process, as an additional debugging aid. <>= procedure :: write_process_data & => simulation_write_process_data <>= subroutine simulation_write_process_data & (object, unit, i_prc, & show_process, show_instance, verbose) class(simulation_t), intent(in) :: object integer, intent(in), optional :: unit integer, intent(in), optional :: i_prc logical, intent(in), optional :: show_process logical, intent(in), optional :: show_instance logical, intent(in), optional :: verbose integer :: i if (present (i_prc)) then i = i_prc else i = object%i_prc end if call object%entry(i)%write_process_data & (unit, show_process, show_instance, verbose) end subroutine simulation_write_process_data @ %def simulation_write_process_data @ Write the actual efficiency of the simulation run. We get the total number of events stored in the simulation counter and compare this with the total number of calls stored in the event entries. In order not to miscount samples that are partly read from file, use the [[generated]] counter, not the [[total]] counter. <>= procedure :: show_efficiency => simulation_show_efficiency <>= subroutine simulation_show_efficiency (simulation) class(simulation_t), intent(inout) :: simulation integer :: n_events, n_calls real(default) :: eff n_events = simulation%counter%generated n_calls = sum (simulation%entry%get_actual_calls_total ()) if (n_calls > 0) then eff = real (n_events, kind=default) / n_calls write (msg_buffer, "(A,1x,F6.2,1x,A)") & "Events: actual unweighting efficiency =", 100 * eff, "%" call msg_message () end if end subroutine simulation_show_efficiency @ %def simulation_show_efficiency @ Compute the checksum of the process set. We retrieve the MD5 sums of all processes. This depends only on the process definitions, while parameters are not considered. The configuration checksum is retrieved from the MCI records in the process objects and furthermore includes beams, parameters, integration results, etc., so matching the latter should guarantee identical physics. <>= procedure :: compute_md5sum => simulation_compute_md5sum <>= subroutine simulation_compute_md5sum (simulation) class(simulation_t), intent(inout) :: simulation type(process_t), pointer :: process type(string_t) :: buffer integer :: j, i, n_mci, i_mci, n_component, i_component if (simulation%md5sum_prc == "") then buffer = "" do i = 1, simulation%n_prc if (.not. simulation%entry(i)%valid) cycle process => simulation%entry(i)%get_process_ptr () if (associated (process)) then n_component = process%get_n_components () do i_component = 1, n_component if (process%has_matrix_element (i_component)) then buffer = buffer // process%get_md5sum_prc (i_component) end if end do end if end do simulation%md5sum_prc = md5sum (char (buffer)) end if if (simulation%md5sum_cfg == "") then buffer = "" do i = 1, simulation%n_prc if (.not. simulation%entry(i)%valid) cycle process => simulation%entry(i)%get_process_ptr () if (associated (process)) then n_mci = process%get_n_mci () do i_mci = 1, n_mci buffer = buffer // process%get_md5sum_mci (i_mci) end do end if end do simulation%md5sum_cfg = md5sum (char (buffer)) end if do j = 1, simulation%n_alt if (simulation%md5sum_alt(j) == "") then buffer = "" do i = 1, simulation%n_prc process => simulation%alt_entry(i,j)%get_process_ptr () if (associated (process)) then buffer = buffer // process%get_md5sum_cfg () end if end do simulation%md5sum_alt(j) = md5sum (char (buffer)) end if end do end subroutine simulation_compute_md5sum @ %def simulation_compute_md5sum @ \subsubsection{Simulation-object finalizer} <>= procedure :: final => simulation_final <>= subroutine simulation_final (object) class(simulation_t), intent(inout) :: object integer :: i, j if (allocated (object%entry)) then do i = 1, size (object%entry) call object%entry(i)%final () end do end if if (allocated (object%alt_entry)) then do j = 1, size (object%alt_entry, 2) do i = 1, size (object%alt_entry, 1) call object%alt_entry(i,j)%final () end do end do end if if (allocated (object%rng)) call object%rng%final () end subroutine simulation_final @ %def simulation_final @ \subsubsection{Simulation-object initialization} We can deduce all data from the given list of process IDs and the global data set. The process objects are taken from the stack. Once the individual integrals are known, we add them (and the errors), to get the sample integral. If there are alternative environments, we suspend initialization for setting up alternative process objects, then restore the master process and its parameters. The generator or rescanner can then switch rapidly between processes. If [[integrate]] is set, we make sure that all affected processes are integrated before simulation. This is necessary if we want to actually generate events. If [[integrate]] is unset, we do not need the integral because we just rescan existing events. In that case, we just need compiled matrix elements. If [[generate]] is set, we prepare for actually generating events. Otherwise, we may only read and rescan events. <>= procedure :: init => simulation_init <>= subroutine simulation_init (simulation, & process_id, integrate, generate, local, global, alt_env) class(simulation_t), intent(out), target :: simulation type(string_t), dimension(:), intent(in) :: process_id logical, intent(in) :: integrate, generate type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), optional, target :: global type(rt_data_t), dimension(:), intent(inout), optional, target :: alt_env class(rng_factory_t), allocatable :: rng_factory integer :: next_rng_seed type(string_t) :: norm_string, version_string logical :: use_process integer :: i, j type(string_t) :: sample_suffix <> sample_suffix = "" <> simulation%local => local simulation%sample_id = & local%get_sval (var_str ("$sample")) simulation%unweighted = & local%get_lval (var_str ("?unweighted")) simulation%negative_weights = & local%get_lval (var_str ("?negative_weights")) simulation%support_resonance_history = & local%get_lval (var_str ("?resonance_history")) simulation%respect_selection = & local%get_lval (var_str ("?sample_select")) version_string = & local%get_sval (var_str ("$event_file_version")) norm_string = & local%get_sval (var_str ("$sample_normalization")) simulation%norm_mode = & event_normalization_mode (norm_string, simulation%unweighted) simulation%pacify = & local%get_lval (var_str ("?sample_pacify")) simulation%event_index_offset = & local%get_ival (var_str ("event_index_offset")) simulation%n_max_tries = & local%get_ival (var_str ("sample_max_tries")) simulation%split_n_evt = & local%get_ival (var_str ("sample_split_n_evt")) simulation%split_n_kbytes = & local%get_ival (var_str ("sample_split_n_kbytes")) simulation%split_index = & local%get_ival (var_str ("sample_split_index")) simulation%update_sqme = & local%get_lval (var_str ("?update_sqme")) simulation%update_weight = & local%get_lval (var_str ("?update_weight")) simulation%update_event = & local%get_lval (var_str ("?update_event")) simulation%recover_beams = & local%get_lval (var_str ("?recover_beams")) simulation%counter%reproduce_xsection = & local%get_lval (var_str ("?check_event_weights_against_xsection")) use_process = & integrate .or. generate & .or. simulation%update_sqme & .or. simulation%update_weight & .or. simulation%update_event & .or. present (alt_env) select case (size (process_id)) case (0) call msg_error ("Simulation: no process selected") case (1) write (msg_buffer, "(A,A,A)") & "Starting simulation for process '", & char (process_id(1)), "'" call msg_message () case default write (msg_buffer, "(A,A,A)") & "Starting simulation for processes '", & char (process_id(1)), "' etc." call msg_message () end select select case (char (version_string)) case ("", "2.2.4") simulation%version = 2 case ("2.2") simulation%version = 1 case default simulation%version = 0 end select if (simulation%version == 0) then call msg_fatal ("Event file format '" & // char (version_string) & // "' is not compatible with this version.") end if simulation%n_prc = size (process_id) allocate (simulation%entry (simulation%n_prc)) if (present (alt_env)) then simulation%n_alt = size (alt_env) do i = 1, simulation%n_prc call simulation%entry(i)%init (process_id(i), & use_process, integrate, generate, & simulation%update_sqme, & simulation%support_resonance_history, & local, global, simulation%n_alt) if (signal_is_pending ()) return end do simulation%valid = any (simulation%entry%valid) if (.not. simulation%valid) then call msg_error ("Simulate: no process has a valid matrix element.") return end if call simulation%update_processes () allocate (simulation%alt_entry (simulation%n_prc, simulation%n_alt)) allocate (simulation%md5sum_alt (simulation%n_alt)) simulation%md5sum_alt = "" do j = 1, simulation%n_alt do i = 1, simulation%n_prc call simulation%alt_entry(i,j)%init_alt (process_id(i), & simulation%entry(i)%get_process_ptr (), alt_env(j)) if (signal_is_pending ()) return end do end do call simulation%restore_processes () else do i = 1, simulation%n_prc call simulation%entry(i)%init & (process_id(i), & use_process, integrate, generate, & simulation%update_sqme, & simulation%support_resonance_history, & local, global) call simulation%entry(i)%determine_if_powheg_matching () if (signal_is_pending ()) return if (simulation%entry(i)%is_nlo ()) & call simulation%entry(i)%setup_additional_entries () end do simulation%valid = any (simulation%entry%valid) if (.not. simulation%valid) then call msg_error ("Simulate: " & // "no process has a valid matrix element.") return end if end if !!! if this becomes conditional, some ref files will need update (seed change) ! if (generate) then call dispatch_rng_factory (rng_factory, local%var_list, next_rng_seed) call update_rng_seed_in_var_list (local%var_list, next_rng_seed) call rng_factory%make (simulation%rng) <> ! end if if (all (simulation%entry%has_integral)) then simulation%integral = sum (simulation%entry%integral) simulation%error = sqrt (sum (simulation%entry%error ** 2)) simulation%has_integral = .true. if (integrate .and. generate) then do i = 1, simulation%n_prc if (simulation%entry(i)%integral < 0 .and. .not. & simulation%negative_weights) then call msg_fatal ("Integral of process '" // & char (process_id (i)) // "'is negative.") end if end do end if else if (integrate .and. generate) & call msg_error ("Simulation contains undefined integrals.") end if if (simulation%integral > 0 .or. & (simulation%integral < 0 .and. simulation%negative_weights)) then simulation%valid = .true. else if (generate) then call msg_error ("Simulate: " & // "sum of process integrals must be positive; skipping.") simulation%valid = .false. else simulation%valid = .true. end if if (simulation%sample_id == "") then simulation%sample_id = simulation%get_default_sample_name () end if simulation%sample_id = simulation%sample_id // sample_suffix if (simulation%valid) call simulation%compute_md5sum () end subroutine simulation_init @ %def simulation_init @ The RNG initialization depends on serial/MPI mode. <>= <>= integer :: rank, n_size <>= <>= call mpi_get_comm_id (n_size, rank) if (n_size > 1) then sample_suffix = var_str ("_") // str (rank) end if <>= <>= do i = 2, rank + 1 select type (rng => simulation%rng) type is (rng_stream_t) call rng%next_substream () if (i == rank) & call msg_message ("Simulate: Advance RNG for parallel event generation") class default call rng%write () call msg_bug ("Parallel event generation: random-number generator & &must be 'rng_stream'.") end select end do @ The number of events that we want to simulate is determined by the settings of [[n_events]], [[luminosity]], and [[?unweighted]]. For weighted events, we take [[n_events]] at face value as the number of matrix element calls. For unweighted events, if the process is a decay, [[n_events]] is the number of unweighted events. In these cases, the luminosity setting is ignored. For unweighted events with a scattering process, we calculate the event number that corresponds to the luminosity, given the current value of the integral. We then compare this with [[n_events]] and choose the larger number. <>= procedure :: compute_n_events => simulation_compute_n_events <>= subroutine simulation_compute_n_events (simulation, n_events) class(simulation_t), intent(in) :: simulation integer, intent(out) :: n_events real(default) :: lumi, x_events_lumi integer :: n_events_lumi logical :: is_scattering n_events = & simulation%local%get_ival (var_str ("n_events")) lumi = & simulation%local%get_rval (var_str ("luminosity")) if (simulation%unweighted) then is_scattering = simulation%entry(1)%n_in == 2 if (is_scattering) then x_events_lumi = abs (simulation%integral * lumi) if (x_events_lumi < huge (n_events)) then n_events_lumi = nint (x_events_lumi) else call msg_message ("Simulation: luminosity too large, & &limiting number of events") n_events_lumi = huge (n_events) end if if (n_events_lumi > n_events) then call msg_message ("Simulation: using n_events as computed from & &luminosity value") n_events = n_events_lumi else write (msg_buffer, "(A,1x,I0)") & "Simulation: requested number of events =", n_events call msg_message () if (.not. vanishes (simulation%integral)) then write (msg_buffer, "(A,1x,ES11.4)") & " corr. to luminosity [fb-1] = ", & n_events / simulation%integral call msg_message () end if end if end if end if end subroutine simulation_compute_n_events @ %def simulation_compute_n_events @ Configuration of the OpenMP parameters, in case OpenMP is active. We use the settings accessible via the local environment. <>= procedure :: setup_openmp => simulation_setup_openmp <>= subroutine simulation_setup_openmp (simulation) class(simulation_t), intent(inout) :: simulation call openmp_set_num_threads_verbose & (simulation%local%get_ival (var_str ("openmp_num_threads")), & simulation%local%get_lval (var_str ("?openmp_logging"))) end subroutine simulation_setup_openmp @ %def simulation_setup_openmp @ Configuration of the event-stream array -- i.e., the setup of output file formats. <>= procedure :: prepare_event_streams => simulation_prepare_event_streams <>= subroutine simulation_prepare_event_streams (sim, es_array) class(simulation_t), intent(inout) :: sim type(event_stream_array_t), intent(out) :: es_array integer :: n_events logical :: rebuild_events, read_raw, write_raw integer :: checkpoint, callback integer :: n_fmt type(event_sample_data_t) :: data type(string_t), dimension(:), allocatable :: sample_fmt n_events = & sim%n_evt_requested rebuild_events = & sim%local%get_lval (var_str ("?rebuild_events")) read_raw = & sim%local%get_lval (var_str ("?read_raw")) .and. .not. rebuild_events write_raw = & sim%local%get_lval (var_str ("?write_raw")) checkpoint = & sim%local%get_ival (var_str ("checkpoint")) callback = & sim%local%get_ival (var_str ("event_callback_interval")) if (read_raw) then inquire (file = char (sim%sample_id) // ".evx", exist = read_raw) end if if (allocated (sim%local%sample_fmt)) then n_fmt = size (sim%local%sample_fmt) else n_fmt = 0 end if data = sim%get_data () data%n_evt = n_events data%nlo_multiplier = sim%get_n_nlo_entries (1) if (read_raw) then allocate (sample_fmt (n_fmt)) if (n_fmt > 0) sample_fmt = sim%local%sample_fmt call es_array%init (sim%sample_id, & sample_fmt, sim%local, & data = data, & input = var_str ("raw"), & allow_switch = write_raw, & checkpoint = checkpoint, & callback = callback) else if (write_raw) then allocate (sample_fmt (n_fmt + 1)) if (n_fmt > 0) sample_fmt(:n_fmt) = sim%local%sample_fmt sample_fmt(n_fmt+1) = var_str ("raw") call es_array%init (sim%sample_id, & sample_fmt, sim%local, & data = data, & checkpoint = checkpoint, & callback = callback) else if (allocated (sim%local%sample_fmt) & .or. checkpoint > 0 & .or. callback > 0) then allocate (sample_fmt (n_fmt)) if (n_fmt > 0) sample_fmt = sim%local%sample_fmt call es_array%init (sim%sample_id, & sample_fmt, sim%local, & data = data, & checkpoint = checkpoint, & callback = callback) end if end subroutine simulation_prepare_event_streams @ %def simulation_prepare_event_streams @ <>= procedure :: get_n_nlo_entries => simulation_get_n_nlo_entries <>= function simulation_get_n_nlo_entries (simulation, i_prc) result (n_extra) class(simulation_t), intent(in) :: simulation integer, intent(in) :: i_prc integer :: n_extra n_extra = simulation%entry(i_prc)%count_nlo_entries () end function simulation_get_n_nlo_entries @ %def simulation_get_n_nlo_entries @ Initialize the process selector, using the entry integrals as process weights. <>= procedure :: init_process_selector => simulation_init_process_selector <>= subroutine simulation_init_process_selector (simulation) class(simulation_t), intent(inout) :: simulation integer :: i if (simulation%has_integral) then call simulation%process_selector%init (simulation%entry%integral, & negative_weights = simulation%negative_weights) do i = 1, simulation%n_prc associate (entry => simulation%entry(i)) if (.not. entry%valid) then call msg_warning ("Process '" // char (entry%process_id) // & "': matrix element vanishes, no events can be generated.") cycle end if call entry%init_mci_selector (simulation%negative_weights) entry%process_weight = simulation%process_selector%get_weight (i) end associate end do end if end subroutine simulation_init_process_selector @ %def simulation_init_process_selector @ Select a process, using the random-number generator. <>= procedure :: select_prc => simulation_select_prc <>= function simulation_select_prc (simulation) result (i_prc) class(simulation_t), intent(inout) :: simulation integer :: i_prc call simulation%process_selector%generate (simulation%rng, i_prc) end function simulation_select_prc @ %def simulation_select_prc @ Select a MCI set for the selected process. <>= procedure :: select_mci => simulation_select_mci <>= function simulation_select_mci (simulation) result (i_mci) class(simulation_t), intent(inout) :: simulation integer :: i_mci i_mci = 0 if (simulation%i_prc /= 0) then i_mci = simulation%entry(simulation%i_prc)%select_mci () end if end function simulation_select_mci @ %def simulation_select_mci @ \subsubsection{Generate-event loop} The requested number of events should be set by this, in time for the event-array initializers that may use this number. <>= procedure :: set_n_events_requested => simulation_set_n_events_requested procedure :: get_n_events_requested => simulation_get_n_events_requested <>= subroutine simulation_set_n_events_requested (simulation, n) class(simulation_t), intent(inout) :: simulation integer, intent(in) :: n simulation%n_evt_requested = n end subroutine simulation_set_n_events_requested function simulation_get_n_events_requested (simulation) result (n) class(simulation_t), intent(in) :: simulation integer :: n n = simulation%n_evt_requested end function simulation_get_n_events_requested @ %def simulation_set_n_events_requested @ %def simulation_get_n_events_requested @ Generate the number of events that has been set by [[simulation_set_n_events_requested]]. First select a process and a component set, then generate an event for that process and factorize the quantum state. The pair of random numbers can be used for factorization. When generating events, we drop all configurations where the event is marked as incomplete. This happens if the event fails cuts. In fact, such events are dropped already by the sampler if unweighting is in effect, so this can happen only for weighted events. By setting a limit given by [[sample_max_tries]] (user parameter), we can avoid an endless loop. The [[begin_it]] and [[end_it]] limits are equal to 1 and the number of events, repspectively, in serial mode, but differ for MPI mode. TODO: When reading from file, event transforms cannot be applied because the process instance will not be complete. (?) <>= procedure :: generate => simulation_generate <>= subroutine simulation_generate (simulation, es_array) class(simulation_t), intent(inout), target :: simulation type(event_stream_array_t), intent(inout), optional :: es_array integer :: begin_it, end_it integer :: i, j, k call simulation%before_first_event (begin_it, end_it, es_array) do i = begin_it, end_it call simulation%next_event (es_array) end do call simulation%after_last_event (begin_it, end_it) end subroutine simulation_generate @ %def simulation_generate @ The header of the event loop: with all necessary information present in the [[simulation]] and [[es_array]] objects, and given a number of events [[n]] to generate, we prepare for actually generating/reading/writing events. The procedure returns the real iteration bounds [[begin_it]] and [[end_it]] for the event loop. This is nontrivial only for MPI; in serial mode those are equal to 1 and to [[n_events]], respectively. <>= procedure :: before_first_event => simulation_before_first_event <>= subroutine simulation_before_first_event (simulation, begin_it, end_it, & es_array) class(simulation_t), intent(inout), target :: simulation integer, intent(out) :: begin_it integer, intent(out) :: end_it type(event_stream_array_t), intent(inout), optional :: es_array integer :: n_evt_requested logical :: has_input integer :: n_events_print logical :: is_leading_order logical :: is_weighted logical :: is_polarized n_evt_requested = simulation%n_evt_requested n_events_print = n_evt_requested * simulation%get_n_nlo_entries (1) is_leading_order = (n_events_print == n_evt_requested) has_input = .false. if (present (es_array)) has_input = es_array%has_input () is_weighted = .not. simulation%entry(1)%config%unweighted is_polarized = simulation%entry(1)%config%factorization_mode & /= FM_IGNORE_HELICITY call simulation%startup_message_generate ( & has_input = has_input, & is_weighted = is_weighted, & is_polarized = is_polarized, & is_leading_order = is_leading_order, & n_events = n_events_print) call simulation%entry%set_n (n_evt_requested) if (simulation%n_alt > 0) call simulation%alt_entry%set_n (n_evt_requested) call simulation%init_event_index () begin_it = 1 end_it = n_evt_requested <> end subroutine simulation_before_first_event @ %def simulation_before_first_event @ Keep the user informed: <>= procedure, private :: startup_message_generate & => simulation_startup_message_generate <>= subroutine simulation_startup_message_generate (simulation, & has_input, is_weighted, is_polarized, is_leading_order, n_events) class(simulation_t), intent(in) :: simulation logical, intent(in) :: has_input logical, intent(in) :: is_weighted logical, intent(in) :: is_polarized logical, intent(in) :: is_leading_order integer, intent(in) :: n_events type(string_t) :: str1, str2, str3, str4 if (has_input) then str1 = "Events: reading" else str1 = "Events: generating" end if if (is_weighted) then str2 = "weighted" else str2 = "unweighted" end if if (is_polarized) then str3 = ", polarized" else str3 = ", unpolarized" end if str4 = "" if (.not. is_leading_order) str4 = " NLO" write (msg_buffer, "(A,1X,I0,1X,A,1X,A)") char (str1), n_events, & char (str2) // char(str3) // char(str4), "events ..." call msg_message () write (msg_buffer, "(A,1x,A)") "Events: event normalization mode", & char (event_normalization_string (simulation%norm_mode)) call msg_message () end subroutine simulation_startup_message_generate @ %def simulation_startup_message_generate @ The body of the event loop: generate and process a single event. Optionally transfer the current event to one of the provided event handles, for in and/or output streams. This works for any stream for which the I/O stream type matches the event-handle type. <>= procedure :: next_event => simulation_next_event <>= subroutine simulation_next_event & (simulation, es_array, event_handle_out, event_handle_in) class(simulation_t), intent(inout) :: simulation type(event_stream_array_t), intent(inout), optional :: es_array class(event_handle_t), intent(inout), optional :: event_handle_out class(event_handle_t), intent(inout), optional :: event_handle_in type(entry_t), pointer :: current_entry logical :: generate_new logical :: passed integer :: j, k call simulation%increment_event_index () if (present (es_array)) then call simulation%read_event & (es_array, .true., generate_new, event_handle_in) else generate_new = .true. end if if (generate_new) then simulation%i_prc = simulation%select_prc () simulation%i_mci = simulation%select_mci () associate (entry => simulation%entry(simulation%i_prc)) entry%instance%i_mci = simulation%i_mci call entry%set_active_real_components () current_entry => entry%get_first () do k = 1, current_entry%count_nlo_entries () if (k > 1) then current_entry => current_entry%get_next () current_entry%particle_set => current_entry%first%particle_set current_entry%particle_set_is_valid & = current_entry%first%particle_set_is_valid end if do j = 1, simulation%n_max_tries if (.not. current_entry%valid) call msg_warning & ("Process '" // char (current_entry%process_id) // "': " // & "matrix element vanishes, no events can be generated.") call current_entry%generate (simulation%i_mci, i_nlo = k) if (signal_is_pending ()) return call simulation%counter%record_mean_and_variance & (current_entry%weight_prc, k) if (current_entry%has_valid_particle_set ()) exit end do end do if (entry%is_nlo ()) call entry%reset_nlo_counter () if (.not. entry%has_valid_particle_set ()) then write (msg_buffer, "(A,I0,A)") "Simulation: failed to & &generate valid event after ", & simulation%n_max_tries, " tries (sample_max_tries)" call msg_fatal () end if current_entry => entry%get_first () do k = 1, current_entry%count_nlo_entries () if (k > 1) current_entry => current_entry%get_next () call current_entry%set_index (simulation%get_event_index ()) call current_entry%evaluate_expressions () end do if (signal_is_pending ()) return simulation%n_dropped = entry%get_n_dropped () if (entry%passed_selection ()) then simulation%weight = entry%get_weight_ref () simulation%excess = entry%get_excess_prc () end if call simulation%counter%record & (simulation%weight, simulation%excess, simulation%n_dropped) call entry%record (simulation%i_mci) end associate else associate (entry => simulation%entry(simulation%i_prc)) call simulation%set_event_index (entry%get_index ()) call entry%accept_sqme_ref () call entry%accept_weight_ref () call entry%check () call entry%evaluate_expressions () if (signal_is_pending ()) return simulation%n_dropped = entry%get_n_dropped () if (entry%passed_selection ()) then simulation%weight = entry%get_weight_ref () simulation%excess = entry%get_excess_prc () end if call simulation%counter%record & (simulation%weight, simulation%excess, simulation%n_dropped, & from_file=.true.) call entry%record (simulation%i_mci, from_file=.true.) end associate end if call simulation%calculate_alt_entries () if (simulation%pacify) call pacify (simulation) if (signal_is_pending ()) return if (simulation%respect_selection) then passed = simulation%entry(simulation%i_prc)%passed_selection () else passed = .true. end if if (present (es_array)) then call simulation%write_event (es_array, passed, event_handle_out) end if end subroutine simulation_next_event @ %def simulation_next_event @ Cleanup after last event: compute and show summary information. <>= procedure :: after_last_event => simulation_after_last_event <>= subroutine simulation_after_last_event (simulation, begin_it, end_it) class(simulation_t), intent(inout) :: simulation integer, intent(in) :: begin_it, end_it call msg_message (" ... event sample complete.") <> if (simulation%unweighted) call simulation%show_efficiency () call simulation%counter%show_excess () call simulation%counter%show_dropped () call simulation%counter%show_mean_and_variance () end subroutine simulation_after_last_event @ %def simulation_after_last_event @ \subsubsection{MPI additions} Below, we define code chunks that differ between the serial and MPI versions. Extra logging for MPI only. <>= procedure :: activate_extra_logging => simulation_activate_extra_logging <>= subroutine simulation_activate_extra_logging (simulation) class(simulation_t), intent(in) :: simulation <> end subroutine simulation_activate_extra_logging <>= <>= logical :: mpi_logging integer :: rank, n_size call mpi_get_comm_id (n_size, rank) mpi_logging = & (simulation%local%get_sval (var_str ("$integration_method")) == "vamp2" & .and. n_size > 1) & .or. simulation%local%get_lval (var_str ("?mpi_logging")) call mpi_set_logging (mpi_logging) @ %def simulation_activate_extra_logging @ Extra subroutine to be called before the first event: <>= <>= call simulation%init_event_loop_mpi (n_evt_requested, begin_it, end_it) @ Extra subroutine to be called after the last event: <>= <>= call simulation%final_event_loop_mpi (begin_it, end_it) @ For MPI event generation, the event-loop interval (1\dots n) is split up into intervals of [[n_workers]]. <>= procedure, private :: init_event_loop_mpi => simulation_init_event_loop_mpi <>= subroutine simulation_init_event_loop_mpi & (simulation, n_events, begin_it, end_it) class(simulation_t), intent(inout) :: simulation integer, intent(in) :: n_events integer, intent(out) :: begin_it, end_it integer :: rank, n_workers call MPI_COMM_SIZE (MPI_COMM_WORLD, n_workers) if (n_workers < 2) then begin_it = 1; end_it = n_events return end if call MPI_COMM_RANK (MPI_COMM_WORLD, rank) if (rank == 0) then call compute_and_scatter_intervals (n_events, begin_it, end_it) else call retrieve_intervals (begin_it, end_it) end if !! Event index starts by 0 (before incrementing when the first event gets generated/read in). !! Proof: event_index_offset in [0, N], start_it in [1, N]. simulation%event_index_offset = simulation%event_index_offset + (begin_it - 1) call simulation%init_event_index () write (msg_buffer, "(A,I0,A,I0,A)") & & "MPI: generate events [", begin_it, ":", end_it, "]" call msg_message () contains subroutine compute_and_scatter_intervals (n_events, begin_it, end_it) integer, intent(in) :: n_events integer, intent(out) :: begin_it, end_it integer, dimension(:), allocatable :: all_begin_it, all_end_it integer :: rank, n_workers, n_events_per_worker call MPI_COMM_RANK (MPI_COMM_WORLD, rank) call MPI_COMM_SIZE (MPI_COMM_WORLD, n_workers) allocate (all_begin_it (n_workers), source = 1) allocate (all_end_it (n_workers), source = n_events) n_events_per_worker = floor (real (n_events, default) / n_workers) all_begin_it = [(1 + rank * n_events_per_worker, rank = 0, n_workers - 1)] all_end_it = [(rank * n_events_per_worker, rank = 1, n_workers)] all_end_it(n_workers) = n_events call MPI_SCATTER (all_begin_it, 1, MPI_INTEGER, begin_it, 1, MPI_INTEGER, 0, MPI_COMM_WORLD) call MPI_SCATTER (all_end_it, 1, MPI_INTEGER, end_it, 1, MPI_INTEGER, 0, MPI_COMM_WORLD) end subroutine compute_and_scatter_intervals subroutine retrieve_intervals (begin_it, end_it) integer, intent(out) :: begin_it, end_it integer :: local_begin_it, local_end_it call MPI_SCATTER (local_begin_it, 1, MPI_INTEGER, begin_it, 1, MPI_INTEGER, 0, MPI_COMM_WORLD) call MPI_SCATTER (local_end_it, 1, MPI_INTEGER, end_it, 1, MPI_INTEGER, 0, MPI_COMM_WORLD) end subroutine retrieve_intervals end subroutine simulation_init_event_loop_mpi @ %def simulation_init_event_loop_mpi @ Synchronize, reduce and collect stuff after the event loop has completed. <>= procedure, private :: final_event_loop_mpi => simulation_final_event_loop_mpi <>= subroutine simulation_final_event_loop_mpi (simulation, begin_it, end_it) class(simulation_t), intent(inout) :: simulation integer, intent(in) :: begin_it, end_it integer :: n_workers, n_events_local, n_events_global call MPI_Barrier (MPI_COMM_WORLD) call MPI_COMM_SIZE (MPI_COMM_WORLD, n_workers) if (n_workers < 2) return n_events_local = end_it - begin_it + 1 call MPI_ALLREDUCE (n_events_local, n_events_global, 1, MPI_INTEGER, MPI_SUM,& & MPI_COMM_WORLD) write (msg_buffer, "(2(A,1X,I0))") & "MPI: Number of generated events locally", n_events_local, " and in world", n_events_global call msg_message () call simulation%counter%allreduce_record () end subroutine simulation_final_event_loop_mpi @ %def simulation_final_event_loop_mpi @ \subsubsection{Alternate environments} Compute the event matrix element and weight for all alternative environments, given the current event and selected process. We first copy the particle set, then temporarily update the process core with local parameters, recalculate everything, and restore the process core. The event weight is obtained by rescaling the original event weight with the ratio of the new and old [[sqme]] values. (In particular, if the old value was zero, the weight will stay zero.) Note: this may turn out to be inefficient because we always replace all parameters and recalculate everything, once for each event and environment. However, a more fine-grained control requires more code. In any case, while we may keep multiple process cores (which stay constant for a simulation run), we still have to update the external matrix element parameters event by event. The matrix element ``object'' is present only once. <>= procedure :: calculate_alt_entries => simulation_calculate_alt_entries <>= subroutine simulation_calculate_alt_entries (simulation) class(simulation_t), intent(inout) :: simulation real(default) :: sqme_prc, weight_prc, factor real(default), dimension(:), allocatable :: sqme_alt, weight_alt integer :: n_alt, i, j i = simulation%i_prc n_alt = simulation%n_alt if (n_alt == 0) return allocate (sqme_alt (n_alt), weight_alt (n_alt)) associate (entry => simulation%entry(i)) do j = 1, n_alt if (signal_is_pending ()) return if (simulation%update_weight) then factor = entry%get_kinematical_weight () else sqme_prc = entry%get_sqme_prc () weight_prc = entry%get_weight_prc () if (sqme_prc /= 0) then factor = weight_prc / sqme_prc else factor = 0 end if end if associate (alt_entry => simulation%alt_entry(i,j)) call alt_entry%update_process (saved=.false.) call alt_entry%select & (entry%get_i_mci (), entry%get_i_term (), entry%get_channel ()) call alt_entry%fill_particle_set (entry) call alt_entry%recalculate & (update_sqme = .true., & recover_beams = simulation%recover_beams, & weight_factor = factor) if (signal_is_pending ()) return call alt_entry%accept_sqme_prc () call alt_entry%update_normalization () call alt_entry%accept_weight_prc () call alt_entry%check () call alt_entry%set_index (simulation%get_event_index ()) call alt_entry%evaluate_expressions () if (signal_is_pending ()) return sqme_alt(j) = alt_entry%get_sqme_ref () if (alt_entry%passed_selection ()) then weight_alt(j) = alt_entry%get_weight_ref () end if end associate end do call entry%update_process (saved=.false.) call entry%set (sqme_alt = sqme_alt, weight_alt = weight_alt) call entry%check () call entry%store_alt_values () end associate end subroutine simulation_calculate_alt_entries @ %def simulation_calculate_alt_entries @ These routines take care of temporary parameter redefinitions that we want to take effect while recalculating the matrix elements. We extract the core(s) of the processes that we are simulating, apply the changes, and make sure that the changes are actually used. This is the duty of [[dispatch_core_update]]. When done, we restore the original versions using [[dispatch_core_restore]]. <>= procedure :: update_processes => simulation_update_processes procedure :: restore_processes => simulation_restore_processes <>= subroutine simulation_update_processes (simulation, & model, qcd, helicity_selection) class(simulation_t), intent(inout) :: simulation class(model_data_t), intent(in), optional, target :: model type(qcd_t), intent(in), optional :: qcd type(helicity_selection_t), intent(in), optional :: helicity_selection integer :: i do i = 1, simulation%n_prc call simulation%entry(i)%update_process & (model, qcd, helicity_selection) end do end subroutine simulation_update_processes subroutine simulation_restore_processes (simulation) class(simulation_t), intent(inout) :: simulation integer :: i do i = 1, simulation%n_prc call simulation%entry(i)%restore_process () end do end subroutine simulation_restore_processes @ %def simulation_update_processes @ %def simulation_restore_processes @ \subsubsection{Rescan-Events Loop} Rescan an undefined number of events. If [[update_event]] or [[update_sqme]] is set, we have to recalculate the event, starting from the particle set. If the latter is set, this includes the squared matrix element (i.e., the amplitude is evaluated). Otherwise, only kinematics and observables derived from it are recovered. If any of the update flags is set, we will come up with separate [[sqme_prc]] and [[weight_prc]] values. (The latter is only distinct if [[update_weight]] is set.) Otherwise, we accept the reference values. <>= procedure :: rescan => simulation_rescan <>= subroutine simulation_rescan (simulation, n, es_array, global) class(simulation_t), intent(inout) :: simulation integer, intent(in) :: n type(event_stream_array_t), intent(inout) :: es_array type(rt_data_t), intent(inout) :: global type(qcd_t) :: qcd type(string_t) :: str1, str2, str3 logical :: complete, check_match str1 = "Rescanning" if (simulation%entry(1)%config%unweighted) then str2 = "unweighted" else str2 = "weighted" end if simulation%n_evt_requested = n call simulation%entry%set_n (n) if (simulation%update_sqme .or. simulation%update_weight) then call dispatch_qcd (qcd, global%get_var_list_ptr (), global%os_data) call simulation%update_processes & (global%model, qcd, global%get_helicity_selection ()) str3 = "(process parameters updated) " else str3 = "" end if write (msg_buffer, "(A,1x,A,1x,A,A,A)") char (str1), char (str2), & "events ", char (str3), "..." call msg_message () call simulation%init_event_index () check_match = .not. global%var_list%get_lval (var_str ("?rescan_force")) do call simulation%increment_event_index () call simulation%read_event (es_array, .false., complete) if (complete) exit if (simulation%update_event & .or. simulation%update_sqme & .or. simulation%update_weight) then call simulation%recalculate (check_match = check_match) if (signal_is_pending ()) return associate (entry => simulation%entry(simulation%i_prc)) call entry%update_normalization () if (simulation%update_event) then call entry%evaluate_transforms () end if call entry%check () call entry%evaluate_expressions () if (signal_is_pending ()) return simulation%n_dropped = entry%get_n_dropped () simulation%weight = entry%get_weight_prc () call simulation%counter%record & (simulation%weight, n_dropped=simulation%n_dropped, from_file=.true.) call entry%record (simulation%i_mci, from_file=.true.) end associate else associate (entry => simulation%entry(simulation%i_prc)) call entry%accept_sqme_ref () call entry%accept_weight_ref () call entry%check () call entry%evaluate_expressions () if (signal_is_pending ()) return simulation%n_dropped = entry%get_n_dropped () simulation%weight = entry%get_weight_ref () call simulation%counter%record & (simulation%weight, n_dropped=simulation%n_dropped, from_file=.true.) call entry%record (simulation%i_mci, from_file=.true.) end associate end if call simulation%calculate_alt_entries () if (signal_is_pending ()) return call simulation%write_event (es_array) end do call simulation%counter%show_dropped () if (simulation%update_sqme .or. simulation%update_weight) then call simulation%restore_processes () end if end subroutine simulation_rescan @ %def simulation_rescan @ \subsubsection{Event index} Here we handle the event index that is kept in the simulation record. The event index is valid for the current sample. When generating or reading events, we initialize the index with the offset that the user provides (if any) and increment it for each event that is generated or read from file. The event index is stored in the event-entry that is current for the event. If an event on file comes with its own index, that index overwrites the predefined one and also resets the index within the simulation record. The event index is not connected to the [[counter]] object. The counter is supposed to collect statistical information. The event index is a user-level object that is visible in event records and analysis expressions. <>= procedure :: init_event_index => simulation_init_event_index procedure :: increment_event_index => simulation_increment_event_index procedure :: set_event_index => simulation_set_event_index procedure :: get_event_index => simulation_get_event_index <>= subroutine simulation_init_event_index (simulation) class(simulation_t), intent(inout) :: simulation call simulation%set_event_index (simulation%event_index_offset) end subroutine simulation_init_event_index subroutine simulation_increment_event_index (simulation) class(simulation_t), intent(inout) :: simulation if (simulation%event_index_set) then simulation%event_index = simulation%event_index + 1 end if end subroutine simulation_increment_event_index subroutine simulation_set_event_index (simulation, i) class(simulation_t), intent(inout) :: simulation integer, intent(in) :: i simulation%event_index = i simulation%event_index_set = .true. end subroutine simulation_set_event_index function simulation_get_event_index (simulation) result (i) class(simulation_t), intent(in) :: simulation integer :: i if (simulation%event_index_set) then i = simulation%event_index else i = 0 end if end function simulation_get_event_index @ %def simulation_init_event_index @ %def simulation_increment_event_index @ %def simulation_set_event_index @ %def simulation_get_event_index @ \subsection{Direct event access} If we want to retrieve event information, we should expose the currently selected event [[entry]] within the simulation object. We recall that this is an extension of the (generic) [[event]] type. Assuming that we will restrict this to read access, we return a pointer. <>= procedure :: get_process_index => simulation_get_process_index procedure :: get_event_ptr => simulation_get_event_ptr <>= function simulation_get_process_index (simulation) result (i_prc) class(simulation_t), intent(in), target :: simulation integer :: i_prc i_prc = simulation%i_prc end function simulation_get_process_index function simulation_get_event_ptr (simulation) result (event) class(simulation_t), intent(in), target :: simulation class(event_t), pointer :: event event => simulation%entry(simulation%i_prc) end function simulation_get_event_ptr @ %def simulation_get_process_index @ %def simulation_get_event_ptr @ \subsection{Event Stream I/O} Write an event to a generic [[eio]] event stream. The process index must be selected, or the current index must be available. <>= generic :: write_event => write_event_eio procedure :: write_event_eio => simulation_write_event_eio <>= subroutine simulation_write_event_eio (object, eio, i_prc) class(simulation_t), intent(in) :: object class(eio_t), intent(inout) :: eio integer, intent(in), optional :: i_prc logical :: increased integer :: current if (present (i_prc)) then current = i_prc else current = object%i_prc end if if (current > 0) then if (object%split_n_evt > 0 .and. object%counter%total > 1) then if (mod (object%counter%total, object%split_n_evt) == 1) then call eio%split_out () end if else if (object%split_n_kbytes > 0) then call eio%update_split_count (increased) if (increased) call eio%split_out () end if call eio%output (object%entry(current)%event_t, current, pacify = object%pacify) else call msg_fatal ("Simulation: write event: no process selected") end if end subroutine simulation_write_event_eio @ %def simulation_write_event @ Read an event from a generic [[eio]] event stream. The event stream element must specify the process within the sample ([[i_prc]]), the MC group for this process ([[i_mci]]), the selected term ([[i_term]]), the selected MC integration [[channel]], and the particle set of the event. We may encounter EOF, which we indicate by storing 0 for the process index [[i_prc]]. An I/O error will be reported, and we also abort reading. <>= generic :: read_event => read_event_eio procedure :: read_event_eio => simulation_read_event_eio <>= subroutine simulation_read_event_eio (object, eio) class(simulation_t), intent(inout) :: object class(eio_t), intent(inout) :: eio integer :: iostat, current call eio%input_i_prc (current, iostat) select case (iostat) case (0) object%i_prc = current call eio%input_event (object%entry(current)%event_t, iostat) end select select case (iostat) case (:-1) object%i_prc = 0 object%i_mci = 0 case (1:) call msg_error ("Reading events: I/O error, aborting read") object%i_prc = 0 object%i_mci = 0 case default object%i_mci = object%entry(current)%get_i_mci () end select end subroutine simulation_read_event_eio @ %def simulation_read_event @ \subsection{Event Stream Array} Write an event using an array of event I/O streams. The process index must be selected, or the current index must be available. <>= generic :: write_event => write_event_es_array procedure :: write_event_es_array => simulation_write_event_es_array <>= subroutine simulation_write_event_es_array & (object, es_array, passed, event_handle) class(simulation_t), intent(in), target :: object class(event_stream_array_t), intent(inout) :: es_array logical, intent(in), optional :: passed class(event_handle_t), intent(inout), optional :: event_handle integer :: i_prc, event_index integer :: i type(entry_t), pointer :: current_entry i_prc = object%i_prc if (i_prc > 0) then event_index = object%counter%total current_entry => object%entry(i_prc)%get_first () do i = 1, current_entry%count_nlo_entries () if (i > 1) current_entry => current_entry%get_next () call es_array%output (current_entry%event_t, i_prc, & event_index, & passed = passed, & pacify = object%pacify, & event_handle = event_handle) end do else call msg_fatal ("Simulation: write event: no process selected") end if end subroutine simulation_write_event_es_array @ %def simulation_write_event @ Read an event using an array of event I/O streams. Reading is successful if there is an input stream within the array, and if a valid event can be read from that stream. If there is a stream, but EOF is passed when reading the first item, we switch the channel to output and return failure but no error message, such that new events can be appended to that stream. <>= generic :: read_event => read_event_es_array procedure :: read_event_es_array => simulation_read_event_es_array <>= subroutine simulation_read_event_es_array & (object, es_array, enable_switch, fail, event_handle) class(simulation_t), intent(inout), target :: object class(event_stream_array_t), intent(inout), target :: es_array logical, intent(in) :: enable_switch logical, intent(out) :: fail class(event_handle_t), intent(inout), optional :: event_handle integer :: iostat, i_prc type(entry_t), pointer :: current_entry => null () integer :: i if (es_array%has_input ()) then fail = .false. call es_array%input_i_prc (i_prc, iostat) select case (iostat) case (0) object%i_prc = i_prc current_entry => object%entry(i_prc) do i = 1, current_entry%count_nlo_entries () if (i > 1) then call es_array%skip_eio_entry (iostat) current_entry => current_entry%get_next () end if call current_entry%set_index (object%get_event_index ()) call es_array%input_event & (current_entry%event_t, iostat, event_handle) end do case (:-1) write (msg_buffer, "(A,1x,I0,1x,A)") & "... event file terminates after", & object%counter%read, "events." call msg_message () if (enable_switch) then call es_array%switch_inout () write (msg_buffer, "(A,1x,I0,1x,A)") & "Generating remaining ", & object%n_evt_requested - object%counter%read, "events ..." call msg_message () end if fail = .true. return end select select case (iostat) case (0) object%i_mci = object%entry(i_prc)%get_i_mci () case default write (msg_buffer, "(A,1x,I0,1x,A)") & "Reading events: I/O error, aborting read after", & object%counter%read, "events." call msg_error () object%i_prc = 0 object%i_mci = 0 fail = .true. end select else fail = .true. end if end subroutine simulation_read_event_es_array @ %def simulation_read_event @ \subsection{Recover event} Recalculate the process instance contents, given an event with known particle set. The indices for MC, term, and channel must be already set. The [[recalculate]] method of the selected entry will import the result into [[sqme_prc]] and [[weight_prc]]. If [[recover_phs]] is set (and false), do not attempt any phase-space calculation. Useful if we need only matrix elements (esp. testing); this flag is not stored in the simulation record. <>= procedure :: recalculate => simulation_recalculate <>= subroutine simulation_recalculate (simulation, recover_phs, check_match) class(simulation_t), intent(inout) :: simulation logical, intent(in), optional :: recover_phs logical, intent(in), optional :: check_match integer :: i_prc, i_comp, i_term, k integer :: i_mci, i_mci0, i_mci1 integer, dimension(:), allocatable :: i_terms logical :: success i_prc = simulation%i_prc associate (entry => simulation%entry(i_prc)) if (entry%selected_i_mci /= 0) then i_mci0 = entry%selected_i_mci i_mci1 = i_mci0 else i_mci0 = 1 i_mci1 = entry%process%get_n_mci () end if SCAN_COMP: do i_mci = i_mci0, i_mci1 i_comp = entry%process%get_master_component (i_mci) call entry%process%reset_selected_cores () call entry%process%select_components ([i_comp]) i_terms = entry%process%get_component_i_terms (i_comp) SCAN_TERM: do k = 1, size (i_terms) i_term = i_terms(k) call entry%select (i_mci, i_term, entry%selected_channel) if (entry%selected_i_term /= 0 & .and. entry%selected_i_term /= i_term) cycle SCAN_TERM call entry%select (i_mci, i_term, entry%selected_channel) if (simulation%update_weight) then call entry%recalculate & (update_sqme = simulation%update_sqme, & recover_beams = simulation%recover_beams, & recover_phs = recover_phs, & weight_factor = entry%get_kinematical_weight (), & check_match = check_match, & success = success) else call entry%recalculate & (update_sqme = simulation%update_sqme, & recover_beams = simulation%recover_beams, & recover_phs = recover_phs, & check_match = check_match, & success = success) end if if (success) exit SCAN_COMP end do SCAN_TERM deallocate (i_terms) end do SCAN_COMP if (.not. success) then call entry%write () call msg_fatal ("Simulation/recalculate: & &event could not be matched to the specified process") end if end associate end subroutine simulation_recalculate @ %def simulation_recalculate @ \subsection{Extract contents of the simulation object} Return the MD5 sum that summarizes configuration and integration (but not the event file). Used for initializing the event streams. <>= procedure :: get_md5sum_prc => simulation_get_md5sum_prc procedure :: get_md5sum_cfg => simulation_get_md5sum_cfg procedure :: get_md5sum_alt => simulation_get_md5sum_alt <>= function simulation_get_md5sum_prc (simulation) result (md5sum) class(simulation_t), intent(in) :: simulation character(32) :: md5sum md5sum = simulation%md5sum_prc end function simulation_get_md5sum_prc function simulation_get_md5sum_cfg (simulation) result (md5sum) class(simulation_t), intent(in) :: simulation character(32) :: md5sum md5sum = simulation%md5sum_cfg end function simulation_get_md5sum_cfg function simulation_get_md5sum_alt (simulation, i) result (md5sum) class(simulation_t), intent(in) :: simulation integer, intent(in) :: i character(32) :: md5sum md5sum = simulation%md5sum_alt(i) end function simulation_get_md5sum_alt @ %def simulation_get_md5sum_prc @ %def simulation_get_md5sum_cfg @ Return data that may be useful for writing event files. Usually we can refer to a previously integrated process, for which we can fetch a process pointer. Occasionally, we do not have this because we are just rescanning an externally generated file without calculation. For that situation, we generate our local beam data object using the current enviroment, or, in simple cases, just fetch the necessary data from the process definition and environment. <>= procedure :: get_data => simulation_get_data <>= function simulation_get_data (simulation, alt) result (sdata) class(simulation_t), intent(in) :: simulation logical, intent(in), optional :: alt type(event_sample_data_t) :: sdata type(process_t), pointer :: process type(beam_data_t), pointer :: beam_data type(beam_structure_t), pointer :: beam_structure type(flavor_t), dimension(:), allocatable :: flv integer :: n, i logical :: enable_alt, construct_beam_data real(default) :: sqrts class(model_data_t), pointer :: model logical :: decay_rest_frame type(string_t) :: process_id enable_alt = .true.; if (present (alt)) enable_alt = alt if (debug_on) call msg_debug (D_CORE, "simulation_get_data") if (debug_on) call msg_debug (D_CORE, "alternative setup", enable_alt) if (enable_alt) then call sdata%init (simulation%n_prc, simulation%n_alt) do i = 1, simulation%n_alt sdata%md5sum_alt(i) = simulation%get_md5sum_alt (i) end do else call sdata%init (simulation%n_prc) end if sdata%unweighted = simulation%unweighted sdata%negative_weights = simulation%negative_weights sdata%norm_mode = simulation%norm_mode process => simulation%entry(1)%get_process_ptr () if (associated (process)) then beam_data => process%get_beam_data_ptr () construct_beam_data = .false. else n = simulation%entry(1)%n_in sqrts = simulation%local%get_sqrts () beam_structure => simulation%local%beam_structure call beam_structure%check_against_n_in (n, construct_beam_data) if (construct_beam_data) then allocate (beam_data) model => simulation%local%model decay_rest_frame = & simulation%local%get_lval (var_str ("?decay_rest_frame")) call beam_data%init_structure (beam_structure, & sqrts, model, decay_rest_frame) else beam_data => null () end if end if if (associated (beam_data)) then n = beam_data%get_n_in () sdata%n_beam = n allocate (flv (n)) flv = beam_data%get_flavor () sdata%pdg_beam(:n) = flv%get_pdg () sdata%energy_beam(:n) = beam_data%get_energy () if (construct_beam_data) deallocate (beam_data) else n = simulation%entry(1)%n_in sdata%n_beam = n process_id = simulation%entry(1)%process_id call simulation%local%prclib%get_pdg_in_1 & (process_id, sdata%pdg_beam(:n)) sdata%energy_beam(:n) = sqrts / n end if do i = 1, simulation%n_prc if (.not. simulation%entry(i)%valid) cycle process => simulation%entry(i)%get_process_ptr () if (associated (process)) then sdata%proc_num_id(i) = process%get_num_id () else process_id = simulation%entry(i)%process_id sdata%proc_num_id(i) = simulation%local%prclib%get_num_id (process_id) end if if (sdata%proc_num_id(i) == 0) sdata%proc_num_id(i) = i if (simulation%entry(i)%has_integral) then sdata%cross_section(i) = simulation%entry(i)%integral sdata%error(i) = simulation%entry(i)%error end if end do sdata%total_cross_section = sum (sdata%cross_section) sdata%md5sum_prc = simulation%get_md5sum_prc () sdata%md5sum_cfg = simulation%get_md5sum_cfg () if (simulation%split_n_evt > 0 .or. simulation%split_n_kbytes > 0) then sdata%split_n_evt = simulation%split_n_evt sdata%split_n_kbytes = simulation%split_n_kbytes sdata%split_index = simulation%split_index end if end function simulation_get_data @ %def simulation_get_data @ Return a default name for the current event sample. This is the process ID of the first process. <>= procedure :: get_default_sample_name => simulation_get_default_sample_name <>= function simulation_get_default_sample_name (simulation) result (sample) class(simulation_t), intent(in) :: simulation type(string_t) :: sample type(process_t), pointer :: process sample = "whizard" if (simulation%n_prc > 0) then process => simulation%entry(1)%get_process_ptr () if (associated (process)) then sample = process%get_id () end if end if end function simulation_get_default_sample_name @ %def simulation_get_default_sample_name @ <>= procedure :: is_valid => simulation_is_valid <>= function simulation_is_valid (simulation) result (valid) class(simulation_t), intent(inout) :: simulation logical :: valid valid = simulation%valid end function simulation_is_valid @ %def simulation_is_valid @ Return the hard-interaction particle set for event entry [[i_prc]]. <>= procedure :: get_hard_particle_set => simulation_get_hard_particle_set <>= function simulation_get_hard_particle_set (simulation, i_prc) result (pset) class(simulation_t), intent(in) :: simulation integer, intent(in) :: i_prc type(particle_set_t) :: pset call simulation%entry(i_prc)%get_hard_particle_set (pset) end function simulation_get_hard_particle_set @ %def simulation_get_hard_particle_set @ \subsection{Auxiliary} Call pacify: eliminate numerical noise. <>= public :: pacify <>= interface pacify module procedure pacify_simulation end interface <>= subroutine pacify_simulation (simulation) class(simulation_t), intent(inout) :: simulation integer :: i, j i = simulation%i_prc if (i > 0) then call pacify (simulation%entry(i)) do j = 1, simulation%n_alt call pacify (simulation%alt_entry(i,j)) end do end if end subroutine pacify_simulation @ %def pacify_simulation @ Manually evaluate expressions for the currently selected process. This is used only in the unit tests. <>= procedure :: evaluate_expressions => simulation_evaluate_expressions <>= subroutine simulation_evaluate_expressions (simulation) class(simulation_t), intent(inout) :: simulation call simulation%entry(simulation%i_prc)%evaluate_expressions () end subroutine simulation_evaluate_expressions @ %def simulation_evaluate_expressions @ Manually evaluate event transforms for the currently selected process. This is used only in the unit tests. <>= procedure :: evaluate_transforms => simulation_evaluate_transforms <>= subroutine simulation_evaluate_transforms (simulation) class(simulation_t), intent(inout) :: simulation associate (entry => simulation%entry(simulation%i_prc)) call entry%evaluate_transforms () end associate end subroutine simulation_evaluate_transforms @ %def simulation_evaluate_transforms @ \subsection{Unit tests} Test module, followed by the stand-alone unit-test procedures. <<[[simulations_ut.f90]]>>= <> module simulations_ut use unit_tests use simulations_uti <> <> contains <> end module simulations_ut @ %def simulations_ut @ <<[[simulations_uti.f90]]>>= <> module simulations_uti <> use kinds, only: i64 <> use io_units use format_defs, only: FMT_10, FMT_12 use ifiles use lexers use parser use lorentz use flavors use interactions, only: reset_interaction_counter use process_libraries, only: process_library_t use prclib_stacks use phs_forests use event_base, only: generic_event_t use event_base, only: event_callback_t use particles, only: particle_set_t use eio_data use eio_base use eio_direct, only: eio_direct_t use eio_raw use eio_ascii use eio_dump use eio_callback use eval_trees use model_data, only: model_data_t use models use rt_data use event_streams use decays_ut, only: prepare_testbed use process, only: process_t use process_stacks, only: process_entry_t use process_configurations_ut, only: prepare_test_library use compilations, only: compile_library use integrations, only: integrate_process use simulations use restricted_subprocesses_uti, only: prepare_resonance_test_library <> <> <> contains <> <> end module simulations_uti @ %def simulations_uti @ API: driver for the unit tests below. <>= public :: simulations_test <>= subroutine simulations_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine simulations_test @ %def simulations_test @ \subsubsection{Initialization} Initialize a [[simulation_t]] object, including the embedded event records. <>= call test (simulations_1, "simulations_1", & "initialization", & u, results) <>= public :: simulations_1 <>= subroutine simulations_1 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, procname2 type(rt_data_t), target :: global type(simulation_t), target :: simulation write (u, "(A)") "* Test output: simulations_1" write (u, "(A)") "* Purpose: initialize simulation" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_1a" procname1 = "simulation_1p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("simulations1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) procname2 = "sim_extra" call prepare_test_library (global, libname, 1, [procname2]) call compile_library (libname, global) call global%set_string (var_str ("$run_id"), & var_str ("simulations2"), is_known = .true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_string (var_str ("$sample"), & var_str ("sim1"), is_known = .true.) call integrate_process (procname2, global, local_stack=.true.) call simulation%init ([procname1, procname2], .false., .true., global) call simulation%init_process_selector () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Write the event record for the first process" write (u, "(A)") call simulation%write_event (u, i_prc = 1) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_1" end subroutine simulations_1 @ %def simulations_1 @ \subsubsection{Weighted events} Generate events for a single process. <>= call test (simulations_2, "simulations_2", & "weighted events", & u, results) <>= public :: simulations_2 <>= subroutine simulations_2 (u) integer, intent(in) :: u type(string_t) :: libname, procname1 type(rt_data_t), target :: global type(simulation_t), target :: simulation type(event_sample_data_t) :: data write (u, "(A)") "* Test output: simulations_2" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_2a" procname1 = "simulation_2p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("simulations1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () data = simulation%get_data () call data%write (u) write (u, "(A)") write (u, "(A)") "* Generate three events" write (u, "(A)") call simulation%set_n_events_requested (3) call simulation%generate () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Write the event record for the last event" write (u, "(A)") call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_2" end subroutine simulations_2 @ %def simulations_2 @ \subsubsection{Unweighted events} Generate events for a single process. <>= call test (simulations_3, "simulations_3", & "unweighted events", & u, results) <>= public :: simulations_3 <>= subroutine simulations_3 (u) integer, intent(in) :: u type(string_t) :: libname, procname1 type(rt_data_t), target :: global type(simulation_t), target :: simulation type(event_sample_data_t) :: data write (u, "(A)") "* Test output: simulations_3" write (u, "(A)") "* Purpose: generate unweighted events & &for a single process" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_3a" procname1 = "simulation_3p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("simulations1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () data = simulation%get_data () call data%write (u) write (u, "(A)") write (u, "(A)") "* Generate three events" write (u, "(A)") call simulation%set_n_events_requested (3) call simulation%generate () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Write the event record for the last event" write (u, "(A)") call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_3" end subroutine simulations_3 @ %def simulations_3 @ \subsubsection{Simulating process with structure functions} Generate events for a single process. <>= call test (simulations_4, "simulations_4", & "process with structure functions", & u, results) <>= public :: simulations_4 <>= subroutine simulations_4 (u) integer, intent(in) :: u type(string_t) :: libname, procname1 type(rt_data_t), target :: global type(flavor_t) :: flv type(string_t) :: name type(simulation_t), target :: simulation type(event_sample_data_t) :: data write (u, "(A)") "* Test output: simulations_4" write (u, "(A)") "* Purpose: generate events for a single process & &with structure functions" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_4a" procname1 = "simulation_4p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), & 0._default) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call reset_interaction_counter () call flv%init (25, global%model) name = flv%get_name () call global%beam_structure%init_sf ([name, name], [1]) call global%beam_structure%set_sf (1, 1, var_str ("sf_test_1")) write (u, "(A)") "* Integrate" write (u, "(A)") call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) call global%set_string (var_str ("$sample"), & var_str ("simulations4"), is_known = .true.) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () data = simulation%get_data () call data%write (u) write (u, "(A)") write (u, "(A)") "* Generate three events" write (u, "(A)") call simulation%set_n_events_requested (3) call simulation%generate () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Write the event record for the last event" write (u, "(A)") call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_4" end subroutine simulations_4 @ %def simulations_4 @ \subsubsection{Event I/O} Generate event for a test process, write to file and reread. <>= call test (simulations_5, "simulations_5", & "raw event I/O", & u, results) <>= public :: simulations_5 <>= subroutine simulations_5 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global class(eio_t), allocatable :: eio type(simulation_t), allocatable, target :: simulation write (u, "(A)") "* Test output: simulations_5" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* write to file and reread" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_5a" procname1 = "simulation_5p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("simulations5"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations5" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () write (u, "(A)") "* Initialize raw event file" write (u, "(A)") allocate (eio_raw_t :: eio) call eio%init_out (sample) write (u, "(A)") "* Generate an event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate () call simulation%write_event (u) call simulation%write_event (eio) call eio%final () deallocate (eio) call simulation%final () deallocate (simulation) write (u, "(A)") write (u, "(A)") "* Re-read the event from file" write (u, "(A)") call global%set_log (var_str ("?update_sqme"), & .true., is_known = .true.) call global%set_log (var_str ("?update_weight"), & .true., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () allocate (eio_raw_t :: eio) call eio%init_in (sample) call simulation%read_event (eio) call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Recalculate process instance" write (u, "(A)") call simulation%recalculate () call simulation%evaluate_expressions () call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call eio%final () call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_5" end subroutine simulations_5 @ %def simulations_5 @ \subsubsection{Event I/O} Generate event for a real process with structure functions, write to file and reread. <>= call test (simulations_6, "simulations_6", & "raw event I/O with structure functions", & u, results) <>= public :: simulations_6 <>= subroutine simulations_6 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global class(eio_t), allocatable :: eio type(simulation_t), allocatable, target :: simulation type(flavor_t) :: flv type(string_t) :: name write (u, "(A)") "* Test output: simulations_6" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* write to file and reread" write (u, "(A)") write (u, "(A)") "* Initialize process and integrate" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_6" procname1 = "simulation_6p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), & 0._default) call flv%init (25, global%model) name = flv%get_name () call global%beam_structure%init_sf ([name, name], [1]) call global%beam_structure%set_sf (1, 1, var_str ("sf_test_1")) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call reset_interaction_counter () call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations6" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () write (u, "(A)") "* Initialize raw event file" write (u, "(A)") allocate (eio_raw_t :: eio) call eio%init_out (sample) write (u, "(A)") "* Generate an event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate () call pacify (simulation) call simulation%write_event (u, verbose = .true., testflag = .true.) call simulation%write_event (eio) call eio%final () deallocate (eio) call simulation%final () deallocate (simulation) write (u, "(A)") write (u, "(A)") "* Re-read the event from file" write (u, "(A)") call reset_interaction_counter () call global%set_log (var_str ("?update_sqme"), & .true., is_known = .true.) call global%set_log (var_str ("?update_weight"), & .true., is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () allocate (eio_raw_t :: eio) call eio%init_in (sample) call simulation%read_event (eio) call simulation%write_event (u, verbose = .true., testflag = .true.) write (u, "(A)") write (u, "(A)") "* Recalculate process instance" write (u, "(A)") call simulation%recalculate () call simulation%evaluate_expressions () call simulation%write_event (u, verbose = .true., testflag = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call eio%final () call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_6" end subroutine simulations_6 @ %def simulations_6 @ \subsubsection{Automatic Event I/O} Generate events with raw-format event file as cache: generate, reread, append. <>= call test (simulations_7, "simulations_7", & "automatic raw event I/O", & u, results) <>= public :: simulations_7 <>= subroutine simulations_7 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global type(string_t), dimension(0) :: empty_string_array type(event_sample_data_t) :: data type(event_stream_array_t) :: es_array type(simulation_t), allocatable, target :: simulation type(flavor_t) :: flv type(string_t) :: name write (u, "(A)") "* Test output: simulations_7" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* write to file and reread" write (u, "(A)") write (u, "(A)") "* Initialize process and integrate" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_7" procname1 = "simulation_7p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), & 0._default) call flv%init (25, global%model) name = flv%get_name () call global%beam_structure%init_sf ([name, name], [1]) call global%beam_structure%set_sf (1, 1, var_str ("sf_test_1")) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call reset_interaction_counter () call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations7" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () write (u, "(A)") "* Initialize raw event file" write (u, "(A)") data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = simulation%get_md5sum_cfg () call es_array%init (sample, [var_str ("raw")], global, data) write (u, "(A)") "* Generate an event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate (es_array) call es_array%final () call simulation%final () deallocate (simulation) write (u, "(A)") "* Re-read the event from file and generate another one" write (u, "(A)") call global%set_log (& var_str ("?rebuild_events"), .false., is_known = .true.) call reset_interaction_counter () allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = simulation%get_md5sum_cfg () call es_array%init (sample, empty_string_array, global, data, & input = var_str ("raw")) call simulation%set_n_events_requested (2) call simulation%generate (es_array) call pacify (simulation) call simulation%write_event (u, verbose = .true.) call es_array%final () call simulation%final () deallocate (simulation) write (u, "(A)") write (u, "(A)") "* Re-read both events from file" write (u, "(A)") call reset_interaction_counter () allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = simulation%get_md5sum_cfg () call es_array%init (sample, empty_string_array, global, data, & input = var_str ("raw")) call simulation%set_n_events_requested (2) call simulation%generate (es_array) call pacify (simulation) call simulation%write_event (u, verbose = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call es_array%final () call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_7" end subroutine simulations_7 @ %def simulations_7 @ \subsubsection{Rescanning Events} Generate events and rescan the resulting raw event file. <>= call test (simulations_8, "simulations_8", & "rescan raw event file", & u, results) <>= public :: simulations_8 <>= subroutine simulations_8 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global type(string_t), dimension(0) :: empty_string_array type(event_sample_data_t) :: data type(event_stream_array_t) :: es_array type(simulation_t), allocatable, target :: simulation type(flavor_t) :: flv type(string_t) :: name write (u, "(A)") "* Test output: simulations_8" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* write to file and rescan" write (u, "(A)") write (u, "(A)") "* Initialize process and integrate" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_8" procname1 = "simulation_8p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), & 0._default) call flv%init (25, global%model) name = flv%get_name () call global%beam_structure%init_sf ([name, name], [1]) call global%beam_structure%set_sf (1, 1, var_str ("sf_test_1")) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call reset_interaction_counter () call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations8" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () write (u, "(A)") "* Initialize raw event file" write (u, "(A)") data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = simulation%get_md5sum_cfg () write (u, "(1x,A,A,A)") "MD5 sum (proc) = '", data%md5sum_prc, "'" write (u, "(1x,A,A,A)") "MD5 sum (config) = '", data%md5sum_cfg, "'" call es_array%init (sample, [var_str ("raw")], global, & data) write (u, "(A)") write (u, "(A)") "* Generate an event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate (es_array) call pacify (simulation) call simulation%write_event (u, verbose = .true., testflag = .true.) call es_array%final () call simulation%final () deallocate (simulation) write (u, "(A)") write (u, "(A)") "* Re-read the event from file" write (u, "(A)") call reset_interaction_counter () allocate (simulation) call simulation%init ([procname1], .false., .false., global) call simulation%init_process_selector () data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = "" write (u, "(1x,A,A,A)") "MD5 sum (proc) = '", data%md5sum_prc, "'" write (u, "(1x,A,A,A)") "MD5 sum (config) = '", data%md5sum_cfg, "'" call es_array%init (sample, empty_string_array, global, data, & input = var_str ("raw"), input_sample = sample, allow_switch = .false.) call simulation%rescan (1, es_array, global = global) write (u, "(A)") call pacify (simulation) call simulation%write_event (u, verbose = .true., testflag = .true.) call es_array%final () call simulation%final () deallocate (simulation) write (u, "(A)") write (u, "(A)") "* Re-read again and recalculate" write (u, "(A)") call reset_interaction_counter () call global%set_log (var_str ("?update_sqme"), & .true., is_known = .true.) call global%set_log (var_str ("?update_event"), & .true., is_known = .true.) allocate (simulation) call simulation%init ([procname1], .false., .false., global) call simulation%init_process_selector () data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = "" write (u, "(1x,A,A,A)") "MD5 sum (proc) = '", data%md5sum_prc, "'" write (u, "(1x,A,A,A)") "MD5 sum (config) = '", data%md5sum_cfg, "'" call es_array%init (sample, empty_string_array, global, data, & input = var_str ("raw"), input_sample = sample, allow_switch = .false.) call simulation%rescan (1, es_array, global = global) write (u, "(A)") call pacify (simulation) call simulation%write_event (u, verbose = .true., testflag = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call es_array%final () call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_8" end subroutine simulations_8 @ %def simulations_8 @ \subsubsection{Rescanning Check} Generate events and rescan with process mismatch. <>= call test (simulations_9, "simulations_9", & "rescan mismatch", & u, results) <>= public :: simulations_9 <>= subroutine simulations_9 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global type(string_t), dimension(0) :: empty_string_array type(event_sample_data_t) :: data type(event_stream_array_t) :: es_array type(simulation_t), allocatable, target :: simulation type(flavor_t) :: flv type(string_t) :: name logical :: error write (u, "(A)") "* Test output: simulations_9" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* write to file and rescan" write (u, "(A)") write (u, "(A)") "* Initialize process and integrate" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_9" procname1 = "simulation_9p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("wood"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("vamp"), is_known = .true.) call global%set_log (var_str ("?use_vamp_equivalences"),& .true., is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), & 0._default) call flv%init (25, global%model) name = flv%get_name () call global%beam_structure%init_sf ([name, name], [1]) call global%beam_structure%set_sf (1, 1, var_str ("sf_test_1")) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call reset_interaction_counter () call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations9" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Initialize raw event file" write (u, "(A)") data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = simulation%get_md5sum_cfg () write (u, "(1x,A,A,A)") "MD5 sum (proc) = '", data%md5sum_prc, "'" write (u, "(1x,A,A,A)") "MD5 sum (config) = '", data%md5sum_cfg, "'" call es_array%init (sample, [var_str ("raw")], global, & data) write (u, "(A)") write (u, "(A)") "* Generate an event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate (es_array) call es_array%final () call simulation%final () deallocate (simulation) write (u, "(A)") "* Initialize event generation for different parameters" write (u, "(A)") call reset_interaction_counter () allocate (simulation) call simulation%init ([procname1, procname1], .false., .false., global) call simulation%init_process_selector () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Attempt to re-read the events (should fail)" write (u, "(A)") data%md5sum_prc = simulation%get_md5sum_prc () data%md5sum_cfg = "" write (u, "(1x,A,A,A)") "MD5 sum (proc) = '", data%md5sum_prc, "'" write (u, "(1x,A,A,A)") "MD5 sum (config) = '", data%md5sum_cfg, "'" call es_array%init (sample, empty_string_array, global, data, & input = var_str ("raw"), input_sample = sample, & allow_switch = .false., error = error) write (u, "(1x,A,L1)") "error = ", error call simulation%rescan (1, es_array, global = global) call es_array%final () call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_9" end subroutine simulations_9 @ %def simulations_9 @ \subsubsection{Alternative weights} Generate an event for a single process and reweight it in a simultaneous calculation. <>= call test (simulations_10, "simulations_10", & "alternative weight", & u, results) <>= public :: simulations_10 <>= subroutine simulations_10 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, expr_text type(rt_data_t), target :: global type(rt_data_t), dimension(1), target :: alt_env type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: pt_weight type(simulation_t), target :: simulation type(event_sample_data_t) :: data write (u, "(A)") "* Test output: simulations_10" write (u, "(A)") "* Purpose: reweight event" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call syntax_pexpr_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_10a" procname1 = "simulation_10p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("simulations1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize alternative environment with custom weight" write (u, "(A)") call alt_env(1)%local_init (global) call alt_env(1)%activate () expr_text = "2" write (u, "(A,A)") "weight = ", char (expr_text) write (u, *) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_weight, stream, .true.) call stream_final (stream) alt_env(1)%pn%weight_expr => pt_weight%get_root_ptr () call alt_env(1)%write_expr (u) write (u, "(A)") write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) call simulation%init ([procname1], .true., .true., global, alt_env=alt_env) call simulation%init_process_selector () data = simulation%get_data () call data%write (u) write (u, "(A)") write (u, "(A)") "* Generate an event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Write the event record for the last event" write (u, "(A)") call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Write the event record for the alternative setup" write (u, "(A)") call simulation%write_alt_event (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () call syntax_model_file_final () call syntax_pexpr_final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_10" end subroutine simulations_10 @ %def simulations_10 @ \subsubsection{Decays} Generate an event with subsequent partonic decays. <>= call test (simulations_11, "simulations_11", & "decay", & u, results) <>= public :: simulations_11 <>= subroutine simulations_11 (u) integer, intent(in) :: u type(rt_data_t), target :: global type(prclib_entry_t), pointer :: lib type(string_t) :: prefix, procname1, procname2 type(simulation_t), target :: simulation write (u, "(A)") "* Test output: simulations_11" write (u, "(A)") "* Purpose: apply decay" write (u, "(A)") write (u, "(A)") "* Initialize processes" write (u, "(A)") call syntax_model_file_init () call global%global_init () allocate (lib) call global%add_prclib (lib) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) prefix = "simulation_11" procname1 = prefix // "_p" procname2 = prefix // "_d" call prepare_testbed & (global%prclib, global%process_stack, & prefix, global%os_data, & scattering=.true., decay=.true.) call global%select_model (var_str ("Test")) call global%model%set_par (var_str ("ff"), 0.4_default) call global%model%set_par (var_str ("mf"), & global%model%get_real (var_str ("ff")) & * global%model%get_real (var_str ("ms"))) call global%model%set_unstable (25, [procname2]) write (u, "(A)") "* Initialize simulation object" write (u, "(A)") call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () write (u, "(A)") "* Generate event" write (u, "(A)") call simulation%set_n_events_requested (1) call simulation%generate () call simulation%write (u) write (u, *) call simulation%write_event (u) write (u, "(A)") write (u, "(A)") "* Cleanup" write (u, "(A)") call simulation%final () call global%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_11" end subroutine simulations_11 @ %def simulations_11 @ \subsubsection{Split Event Files} Generate event for a real process with structure functions and write to file, accepting a limit for the number of events per file. <>= call test (simulations_12, "simulations_12", & "split event files", & u, results) <>= public :: simulations_12 <>= subroutine simulations_12 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global class(eio_t), allocatable :: eio type(simulation_t), allocatable, target :: simulation type(flavor_t) :: flv integer :: i_evt write (u, "(A)") "* Test output: simulations_12" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* and write to split event files" write (u, "(A)") write (u, "(A)") "* Initialize process and integrate" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_12" procname1 = "simulation_12p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%model_set_real (var_str ("ms"), & 0._default) call flv%init (25, global%model) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations_12" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) call global%set_int (var_str ("sample_split_n_evt"), & 2, is_known = .true.) call global%set_int (var_str ("sample_split_index"), & 42, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () call simulation%write (u) write (u, "(A)") write (u, "(A)") "* Initialize ASCII event file" write (u, "(A)") allocate (eio_ascii_short_t :: eio) select type (eio) class is (eio_ascii_t); call eio%set_parameters () end select call eio%init_out (sample, data = simulation%get_data ()) write (u, "(A)") "* Generate 5 events, distributed among three files" do i_evt = 1, 5 call simulation%set_n_events_requested (1) call simulation%generate () call simulation%write_event (eio) end do call eio%final () deallocate (eio) call simulation%final () deallocate (simulation) write (u, *) call display_file ("simulations_12.42.short.evt", u) write (u, *) call display_file ("simulations_12.43.short.evt", u) write (u, *) call display_file ("simulations_12.44.short.evt", u) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_12" end subroutine simulations_12 @ %def simulations_12 @ Auxiliary: display file contents. <>= public :: display_file <>= subroutine display_file (file, u) use io_units, only: free_unit character(*), intent(in) :: file integer, intent(in) :: u character(256) :: buffer integer :: u_file write (u, "(3A)") "* Contents of file '", file, "':" write (u, *) u_file = free_unit () open (u_file, file = file, action = "read", status = "old") do read (u_file, "(A)", end = 1) buffer write (u, "(A)") trim (buffer) end do 1 continue end subroutine display_file @ %def display_file @ \subsubsection{Callback} Generate events and execute a callback in place of event I/O. <>= call test (simulations_13, "simulations_13", & "callback", & u, results) <>= public :: simulations_13 <>= subroutine simulations_13 (u) integer, intent(in) :: u type(string_t) :: libname, procname1, sample type(rt_data_t), target :: global class(eio_t), allocatable :: eio type(simulation_t), allocatable, target :: simulation type(flavor_t) :: flv integer :: i_evt type(simulations_13_callback_t) :: event_callback write (u, "(A)") "* Test output: simulations_13" write (u, "(A)") "* Purpose: generate events for a single process" write (u, "(A)") "* and execute callback" write (u, "(A)") write (u, "(A)") "* Initialize process and integrate" write (u, "(A)") call syntax_model_file_init () call global%global_init () call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) libname = "simulation_13" procname1 = "simulation_13p" call prepare_test_library (global, libname, 1, [procname1]) call compile_library (libname, global) call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_string (var_str ("$method"), & var_str ("unit_test"), is_known = .true.) call global%set_string (var_str ("$phs_method"), & var_str ("single"), is_known = .true.) call global%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known = .true.) call global%set_log (var_str ("?vis_history"),& .false., is_known = .true.) call global%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call flv%init (25, global%model) call global%it_list%init ([1], [1000]) call global%set_string (var_str ("$run_id"), & var_str ("r1"), is_known = .true.) call integrate_process (procname1, global, local_stack=.true.) write (u, "(A)") "* Initialize event generation" write (u, "(A)") call global%set_log (var_str ("?unweighted"), & .false., is_known = .true.) sample = "simulations_13" call global%set_string (var_str ("$sample"), & sample, is_known = .true.) allocate (simulation) call simulation%init ([procname1], .true., .true., global) call simulation%init_process_selector () write (u, "(A)") "* Prepare callback object" write (u, "(A)") event_callback%u = u call global%set_event_callback (event_callback) write (u, "(A)") "* Initialize callback I/O object" write (u, "(A)") allocate (eio_callback_t :: eio) select type (eio) class is (eio_callback_t) call eio%set_parameters (callback = event_callback, & count_interval = 3) end select call eio%init_out (sample, data = simulation%get_data ()) write (u, "(A)") "* Generate 7 events, with callback every 3 events" write (u, "(A)") do i_evt = 1, 7 call simulation%set_n_events_requested (1) call simulation%generate () call simulation%write_event (eio) end do call eio%final () deallocate (eio) call simulation%final () deallocate (simulation) write (u, "(A)") write (u, "(A)") "* Cleanup" call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_13" end subroutine simulations_13 @ %def simulations_13 @ The callback object and procedure. In the type extension, we can store the output channel [[u]] so we know where to write into. <>= type, extends (event_callback_t) :: simulations_13_callback_t integer :: u contains procedure :: write => simulations_13_callback_write procedure :: proc => simulations_13_callback end type simulations_13_callback_t @ %def simulations_13_callback_t <>= subroutine simulations_13_callback_write (event_callback, unit) class(simulations_13_callback_t), intent(in) :: event_callback integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) write (u, "(1x,A)") "Hello" end subroutine simulations_13_callback_write subroutine simulations_13_callback (event_callback, i, event) class(simulations_13_callback_t), intent(in) :: event_callback integer(i64), intent(in) :: i class(generic_event_t), intent(in) :: event write (event_callback%u, "(A,I0)") "hello event #", i end subroutine simulations_13_callback @ %def simulations_13_callback_write @ %def simulations_13_callback @ \subsubsection{Resonant subprocess setup} Prepare a process with resonances and enter resonant subprocesses in the simulation object. Select a kinematics configuration and compute probabilities for resonant subprocesses. The process and its initialization is taken from [[processes_18]], but we need a complete \oMega\ matrix element here. <>= call test (simulations_14, "simulations_14", & "resonant subprocesses evaluation", & u, results) <>= public :: simulations_14 <>= subroutine simulations_14 (u) integer, intent(in) :: u type(string_t) :: libname, libname_generated type(string_t) :: procname type(string_t) :: model_name type(rt_data_t), target :: global type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib class(model_t), pointer :: model class(model_data_t), pointer :: model_data type(simulation_t), target :: simulation type(particle_set_t) :: pset type(eio_direct_t) :: eio_in type(eio_dump_t) :: eio_out real(default) :: sqrts, mw, pp real(default), dimension(3) :: p3 type(vector4_t), dimension(:), allocatable :: p real(default), dimension(:), allocatable :: m integer :: u_verbose, i real(default) :: sqme_proc real(default), dimension(:), allocatable :: sqme real(default) :: on_shell_limit integer, dimension(:), allocatable :: i_array real(default), dimension(:), allocatable :: prob_array write (u, "(A)") "* Test output: simulations_14" write (u, "(A)") "* Purpose: construct resonant subprocesses & &in the simulation object" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () libname = "simulations_14_lib" procname = "simulations_14_p" call global%global_init () call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_log (var_str ("?update_sqme"), & .true., is_known = .true.) call global%set_log (var_str ("?update_weight"), & .true., is_known = .true.) call global%set_log (var_str ("?update_event"), & .true., is_known = .true.) model_name = "SM" call global%select_model (model_name) allocate (model) call model%init_instance (global%model) model_data => model write (u, "(A)") "* Initialize process library and process" write (u, "(A)") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) call prepare_resonance_test_library & (lib, libname, procname, model_data, global, u) write (u, "(A)") write (u, "(A)") "* Initialize simulation object & &with resonant subprocesses" write (u, "(A)") call global%set_log (var_str ("?resonance_history"), & .true., is_known = .true.) call global%set_real (var_str ("resonance_on_shell_limit"), & 10._default, is_known = .true.) call simulation%init ([procname], & integrate=.false., generate=.false., local=global) call simulation%write_resonant_subprocess_data (u, 1) write (u, "(A)") write (u, "(A)") "* Resonant subprocesses: generated library" write (u, "(A)") libname_generated = procname // "_R" lib => global%prclib_stack%get_library_ptr (libname_generated) if (associated (lib)) call lib%write (u, libpath=.false.) write (u, "(A)") write (u, "(A)") "* Generated process stack" write (u, "(A)") call global%process_stack%show (u) write (u, "(A)") write (u, "(A)") "* Particle set" write (u, "(A)") pset = simulation%get_hard_particle_set (1) call pset%write (u) write (u, "(A)") write (u, "(A)") "* Initialize object for direct access" write (u, "(A)") call eio_in%init_direct & (n_beam = 0, n_in = 2, n_rem = 0, n_vir = 0, n_out = 3, & pdg = [-11, 11, 1, -2, 24], model=global%model) call eio_in%set_selection_indices (1, 1, 1, 1) sqrts = global%get_rval (var_str ("sqrts")) mw = 80._default ! deliberately slightly different from true mw pp = sqrt (sqrts**2 - 4 * mw**2) / 2 allocate (p (5), m (5)) p(1) = vector4_moving (sqrts/2, sqrts/2, 3) m(1) = 0 p(2) = vector4_moving (sqrts/2,-sqrts/2, 3) m(2) = 0 p3(1) = pp/2 p3(2) = mw/2 p3(3) = 0 p(3) = vector4_moving (sqrts/4, vector3_moving (p3)) m(3) = 0 p3(2) = -mw/2 p(4) = vector4_moving (sqrts/4, vector3_moving (p3)) m(4) = 0 p(5) = vector4_moving (sqrts/2,-pp, 1) m(5) = mw call eio_in%set_momentum (p, m**2) call eio_in%write (u) write (u, "(A)") write (u, "(A)") "* Transfer and show particle set" write (u, "(A)") call simulation%read_event (eio_in) pset = simulation%get_hard_particle_set (1) call pset%write (u) write (u, "(A)") write (u, "(A)") "* (Re)calculate matrix element" write (u, "(A)") call simulation%recalculate (recover_phs = .false.) call simulation%evaluate_transforms () write (u, "(A)") "* Show event with sqme" write (u, "(A)") call eio_out%set_parameters (unit = u, & weights = .true., pacify = .true., compressed = .true.) call eio_out%init_out (var_str ("")) call simulation%write_event (eio_out) write (u, "(A)") write (u, "(A)") "* Write event to separate file & &'simulations_14_event_verbose.log'" u_verbose = free_unit () open (unit = u_verbose, file = "simulations_14_event_verbose.log", & status = "replace", action = "write") call simulation%write (u_verbose) write (u_verbose, *) call simulation%write_event (u_verbose, verbose =.true., testflag = .true.) close (u_verbose) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_14" end subroutine simulations_14 @ %def simulations_14 @ \subsubsection{Resonant subprocess simulation} Prepare a process with resonances and enter resonant subprocesses in the simulation object. Simulate events with selection of resonance histories. The process and its initialization is taken from [[processes_18]], but we need a complete \oMega\ matrix element here. <>= call test (simulations_15, "simulations_15", & "resonant subprocesses in simulation", & u, results) <>= public :: simulations_15 <>= subroutine simulations_15 (u) integer, intent(in) :: u type(string_t) :: libname, libname_generated type(string_t) :: procname type(string_t) :: model_name type(rt_data_t), target :: global type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib class(model_t), pointer :: model class(model_data_t), pointer :: model_data type(simulation_t), target :: simulation real(default) :: sqrts type(eio_dump_t) :: eio_out integer :: u_verbose write (u, "(A)") "* Test output: simulations_15" write (u, "(A)") "* Purpose: generate event with resonant subprocess" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") call syntax_model_file_init () call syntax_phs_forest_init () libname = "simulations_15_lib" procname = "simulations_15_p" call global%global_init () call global%append_log (& var_str ("?rebuild_phase_space"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_grids"), .true., intrinsic = .true.) call global%append_log (& var_str ("?rebuild_events"), .true., intrinsic = .true.) call global%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) call global%set_int (var_str ("seed"), & 0, is_known = .true.) call global%set_real (var_str ("sqrts"),& 1000._default, is_known = .true.) call global%set_log (var_str ("?recover_beams"), & .false., is_known = .true.) call global%set_log (var_str ("?update_sqme"), & .true., is_known = .true.) call global%set_log (var_str ("?update_weight"), & .true., is_known = .true.) call global%set_log (var_str ("?update_event"), & .true., is_known = .true.) call global%set_log (var_str ("?resonance_history"), & .true., is_known = .true.) call global%set_real (var_str ("resonance_on_shell_limit"), & 10._default, is_known = .true.) model_name = "SM" call global%select_model (model_name) allocate (model) call model%init_instance (global%model) model_data => model write (u, "(A)") "* Initialize process library and process" write (u, "(A)") allocate (lib_entry) call lib_entry%init (libname) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) call prepare_resonance_test_library & (lib, libname, procname, model_data, global, u) write (u, "(A)") write (u, "(A)") "* Initialize simulation object & &with resonant subprocesses" write (u, "(A)") call global%it_list%init ([1], [1000]) call simulation%init ([procname], & integrate=.true., generate=.true., local=global) call simulation%write_resonant_subprocess_data (u, 1) write (u, "(A)") write (u, "(A)") "* Generate event" write (u, "(A)") call simulation%init_process_selector () call simulation%set_n_events_requested (1) call simulation%generate () call eio_out%set_parameters (unit = u, & weights = .true., pacify = .true., compressed = .true.) call eio_out%init_out (var_str ("")) call simulation%write_event (eio_out) write (u, "(A)") write (u, "(A)") "* Write event to separate file & &'simulations_15_event_verbose.log'" u_verbose = free_unit () open (unit = u_verbose, file = "simulations_15_event_verbose.log", & status = "replace", action = "write") call simulation%write (u_verbose) write (u_verbose, *) call simulation%write_event (u_verbose, verbose =.true., testflag = .true.) close (u_verbose) write (u, "(A)") write (u, "(A)") "* Cleanup" call simulation%final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: simulations_15" end subroutine simulations_15 @ %def simulations_15 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{More Unit Tests} This chapter collects some procedures for testing that can't be provided at the point where the corresponding modules are defined, because they use other modules of a different level. (We should move them back, collecting the high-level functionality in init/final hooks that we can set at runtime.) \section{Expression Testing} Expression objects are part of process and event objects, but the process and event object modules should not depend on the implementation of expressions. Here, we collect unit tests that depend on expression implementation. <<[[expr_tests_ut.f90]]>>= <> module expr_tests_ut use unit_tests use expr_tests_uti <> <> contains <> end module expr_tests_ut @ %def expr_tests_ut @ <<[[expr_tests_uti.f90]]>>= <> module expr_tests_uti <> <> use format_defs, only: FMT_12 use format_utils, only: write_separator use os_interface use sm_qcd use lorentz use ifiles use lexers use parser use model_data use interactions, only: reset_interaction_counter use process_libraries use subevents use subevt_expr use rng_base use mci_base use phs_base use variables, only: var_list_t use eval_trees use models use prc_core use prc_test use process, only: process_t use instances, only: process_instance_t use events use rng_base_ut, only: rng_test_factory_t use phs_base_ut, only: phs_test_config_t <> <> contains <> <> end module expr_tests_uti @ %def expr_tests_uti @ \subsection{Test} This is the master for calling self-test procedures. <>= public :: subevt_expr_test <>= subroutine subevt_expr_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine subevt_expr_test @ %def subevt_expr_test @ \subsubsection{Parton-event expressions} <>= call test (subevt_expr_1, "subevt_expr_1", & "parton-event expressions", & u, results) <>= public :: subevt_expr_1 <>= subroutine subevt_expr_1 (u) integer, intent(in) :: u type(string_t) :: expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: pt_cuts, pt_scale, pt_fac_scale, pt_ren_scale type(parse_tree_t) :: pt_weight type(parse_node_t), pointer :: pn_cuts, pn_scale, pn_fac_scale, pn_ren_scale type(parse_node_t), pointer :: pn_weight type(eval_tree_factory_t) :: expr_factory type(os_data_t) :: os_data type(model_t), target :: model type(parton_expr_t), target :: expr real(default) :: E, Ex, m type(vector4_t), dimension(6) :: p integer :: i, pdg logical :: passed real(default) :: scale, fac_scale, ren_scale, weight write (u, "(A)") "* Test output: subevt_expr_1" write (u, "(A)") "* Purpose: Set up a subevt and associated & &process-specific expressions" write (u, "(A)") call syntax_pexpr_init () call syntax_model_file_init () call os_data%init () call model%read (var_str ("Test.mdl"), os_data) write (u, "(A)") "* Expression texts" write (u, "(A)") expr_text = "all Pt > 100 [s]" write (u, "(A,A)") "cuts = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (pt_cuts, stream, .true.) call stream_final (stream) pn_cuts => pt_cuts%get_root_ptr () expr_text = "sqrts" write (u, "(A,A)") "scale = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_scale, stream, .true.) call stream_final (stream) pn_scale => pt_scale%get_root_ptr () expr_text = "sqrts_hat" write (u, "(A,A)") "fac_scale = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_fac_scale, stream, .true.) call stream_final (stream) pn_fac_scale => pt_fac_scale%get_root_ptr () expr_text = "100" write (u, "(A,A)") "ren_scale = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_ren_scale, stream, .true.) call stream_final (stream) pn_ren_scale => pt_ren_scale%get_root_ptr () expr_text = "n_tot - n_in - n_out" write (u, "(A,A)") "weight = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_weight, stream, .true.) call stream_final (stream) pn_weight => pt_weight%get_root_ptr () call ifile_final (ifile) write (u, "(A)") write (u, "(A)") "* Initialize process expr" write (u, "(A)") call expr%setup_vars (1000._default) call expr%var_list%append_real (var_str ("tolerance"), 0._default) call expr%link_var_list (model%get_var_list_ptr ()) call expr_factory%init (pn_cuts) call expr%setup_selection (expr_factory) call expr_factory%init (pn_scale) call expr%setup_scale (expr_factory) call expr_factory%init (pn_fac_scale) call expr%setup_fac_scale (expr_factory) call expr_factory%init (pn_ren_scale) call expr%setup_ren_scale (expr_factory) call expr_factory%init (pn_weight) call expr%setup_weight (expr_factory) call write_separator (u) call expr%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Fill subevt and evaluate expressions" write (u, "(A)") call subevt_init (expr%subevt_t, 6) E = 500._default Ex = 400._default m = 125._default pdg = 25 p(1) = vector4_moving (E, sqrt (E**2 - m**2), 3) p(2) = vector4_moving (E, -sqrt (E**2 - m**2), 3) p(3) = vector4_moving (Ex, sqrt (Ex**2 - m**2), 3) p(4) = vector4_moving (Ex, -sqrt (Ex**2 - m**2), 3) p(5) = vector4_moving (Ex, sqrt (Ex**2 - m**2), 1) p(6) = vector4_moving (Ex, -sqrt (Ex**2 - m**2), 1) call expr%reset_contents () do i = 1, 2 call subevt_set_beam (expr%subevt_t, i, pdg, p(i), m**2) end do do i = 3, 4 call subevt_set_incoming (expr%subevt_t, i, pdg, p(i), m**2) end do do i = 5, 6 call subevt_set_outgoing (expr%subevt_t, i, pdg, p(i), m**2) end do expr%sqrts_hat = subevt_get_sqrts_hat (expr%subevt_t) expr%n_in = 2 expr%n_out = 2 expr%n_tot = 4 expr%subevt_filled = .true. call expr%evaluate (passed, scale, fac_scale, ren_scale, weight) write (u, "(A,L1)") "Event has passed = ", passed write (u, "(A," // FMT_12 // ")") "Scale = ", scale write (u, "(A," // FMT_12 // ")") "Factorization scale = ", fac_scale write (u, "(A," // FMT_12 // ")") "Renormalization scale = ", ren_scale write (u, "(A," // FMT_12 // ")") "Weight = ", weight write (u, "(A)") call write_separator (u) call expr%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call expr%final () call model%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: subevt_expr_1" end subroutine subevt_expr_1 @ %def subevt_expr_1 @ \subsubsection{Parton-event expressions} <>= call test (subevt_expr_2, "subevt_expr_2", & "parton-event expressions", & u, results) <>= public :: subevt_expr_2 <>= subroutine subevt_expr_2 (u) integer, intent(in) :: u type(string_t) :: expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: pt_selection type(parse_tree_t) :: pt_reweight, pt_analysis type(parse_node_t), pointer :: pn_selection type(parse_node_t), pointer :: pn_reweight, pn_analysis type(os_data_t) :: os_data type(model_t), target :: model type(eval_tree_factory_t) :: expr_factory type(event_expr_t), target :: expr real(default) :: E, Ex, m type(vector4_t), dimension(6) :: p integer :: i, pdg logical :: passed real(default) :: reweight logical :: analysis_flag write (u, "(A)") "* Test output: subevt_expr_2" write (u, "(A)") "* Purpose: Set up a subevt and associated & &process-specific expressions" write (u, "(A)") call syntax_pexpr_init () call syntax_model_file_init () call os_data%init () call model%read (var_str ("Test.mdl"), os_data) write (u, "(A)") "* Expression texts" write (u, "(A)") expr_text = "all Pt > 100 [s]" write (u, "(A,A)") "selection = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (pt_selection, stream, .true.) call stream_final (stream) pn_selection => pt_selection%get_root_ptr () expr_text = "n_tot - n_in - n_out" write (u, "(A,A)") "reweight = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_reweight, stream, .true.) call stream_final (stream) pn_reweight => pt_reweight%get_root_ptr () expr_text = "true" write (u, "(A,A)") "analysis = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (pt_analysis, stream, .true.) call stream_final (stream) pn_analysis => pt_analysis%get_root_ptr () call ifile_final (ifile) write (u, "(A)") write (u, "(A)") "* Initialize process expr" write (u, "(A)") call expr%setup_vars (1000._default) call expr%link_var_list (model%get_var_list_ptr ()) call expr%var_list%append_real (var_str ("tolerance"), 0._default) call expr_factory%init (pn_selection) call expr%setup_selection (expr_factory) call expr_factory%init (pn_analysis) call expr%setup_analysis (expr_factory) call expr_factory%init (pn_reweight) call expr%setup_reweight (expr_factory) call write_separator (u) call expr%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Fill subevt and evaluate expressions" write (u, "(A)") call subevt_init (expr%subevt_t, 6) E = 500._default Ex = 400._default m = 125._default pdg = 25 p(1) = vector4_moving (E, sqrt (E**2 - m**2), 3) p(2) = vector4_moving (E, -sqrt (E**2 - m**2), 3) p(3) = vector4_moving (Ex, sqrt (Ex**2 - m**2), 3) p(4) = vector4_moving (Ex, -sqrt (Ex**2 - m**2), 3) p(5) = vector4_moving (Ex, sqrt (Ex**2 - m**2), 1) p(6) = vector4_moving (Ex, -sqrt (Ex**2 - m**2), 1) call expr%reset_contents () do i = 1, 2 call subevt_set_beam (expr%subevt_t, i, pdg, p(i), m**2) end do do i = 3, 4 call subevt_set_incoming (expr%subevt_t, i, pdg, p(i), m**2) end do do i = 5, 6 call subevt_set_outgoing (expr%subevt_t, i, pdg, p(i), m**2) end do expr%sqrts_hat = subevt_get_sqrts_hat (expr%subevt_t) expr%n_in = 2 expr%n_out = 2 expr%n_tot = 4 expr%subevt_filled = .true. call expr%evaluate (passed, reweight, analysis_flag) write (u, "(A,L1)") "Event has passed = ", passed write (u, "(A," // FMT_12 // ")") "Reweighting factor = ", reweight write (u, "(A,L1)") "Analysis flag = ", analysis_flag write (u, "(A)") call write_separator (u) call expr%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call expr%final () call model%final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: subevt_expr_2" end subroutine subevt_expr_2 @ %def subevt_expr_2 @ \subsubsection{Processes: handle partonic cuts} Initialize a process and process instance, choose a sampling point and fill the process instance, evaluating a given cut configuration. We use the same trivial process as for the previous test. All momentum and state dependence is trivial, so we just test basic functionality. <>= call test (processes_5, "processes_5", & "handle cuts (partonic event)", & u, results) <>= public :: processes_5 <>= subroutine processes_5 (u) integer, intent(in) :: u type(string_t) :: cut_expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: parse_tree type(eval_tree_factory_t) :: expr_factory type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), pointer :: model_tmp type(model_t), pointer :: model type(var_list_t), target :: var_list type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance write (u, "(A)") "* Test output: processes_5" write (u, "(A)") "* Purpose: create a process & &and fill a process instance" write (u, "(A)") write (u, "(A)") "* Prepare a cut expression" write (u, "(A)") call syntax_pexpr_init () cut_expr_text = "all Pt > 100 [s]" call ifile_append (ifile, cut_expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (parse_tree, stream, .true.) write (u, "(A)") "* Build and initialize a test process" write (u, "(A)") libname = "processes5" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call syntax_model_file_init () allocate (model_tmp) call model_tmp%read (var_str ("Test.mdl"), os_data) call var_list%init_snapshot (model_tmp%get_var_list_ptr ()) model => model_tmp call reset_interaction_counter () call var_list%append_real (var_str ("tolerance"), 0._default) call var_list%append_log (var_str ("?alphas_is_fixed"), .true.) call var_list%append_int (var_str ("seed"), 0) allocate (process) call process%init (procname, lib, os_data, model, var_list) call var_list%final () allocate (phs_test_config_t :: phs_config_template) call process%setup_test_cores () call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_empty) write (u, "(A)") "* Complete process initialization and set cuts" write (u, "(A)") call process%setup_terms () call expr_factory%init (parse_tree%get_root_ptr ()) call process%set_cuts (expr_factory) call process%write (.false., u, & show_var_list=.true., show_expressions=.true., show_os_data=.false.) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) write (u, "(A)") write (u, "(A)") "* Inject a set of random numbers" write (u, "(A)") call process_instance%choose_mci (1) call process_instance%set_mcpar ([0._default, 0._default]) write (u, "(A)") write (u, "(A)") "* Set up kinematics and subevt, check cuts (should fail)" write (u, "(A)") call process_instance%select_channel (1) call process_instance%compute_seed_kinematics () call process_instance%compute_hard_kinematics () call process_instance%compute_eff_kinematics () call process_instance%evaluate_expressions () call process_instance%compute_other_channels () call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Evaluate for another set (should succeed)" write (u, "(A)") call process_instance%reset () call process_instance%set_mcpar ([0.5_default, 0.125_default]) call process_instance%select_channel (1) call process_instance%compute_seed_kinematics () call process_instance%compute_hard_kinematics () call process_instance%compute_eff_kinematics () call process_instance%evaluate_expressions () call process_instance%compute_other_channels () call process_instance%evaluate_trace () call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Evaluate for another set using convenience procedure & &(failure)" write (u, "(A)") call process_instance%evaluate_sqme (1, [0.0_default, 0.2_default]) call process_instance%write_header (u) write (u, "(A)") write (u, "(A)") "* Evaluate for another set using convenience procedure & &(success)" write (u, "(A)") call process_instance%evaluate_sqme (1, [0.1_default, 0.2_default]) call process_instance%write_header (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call parse_tree_final (parse_tree) call stream_final (stream) call ifile_final (ifile) call syntax_pexpr_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_5" end subroutine processes_5 @ %def processes_5 @ Trivial for testing: do not allocate the MCI record. <>= subroutine dispatch_mci_empty (mci, var_list, process_id, is_nlo) class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo end subroutine dispatch_mci_empty @ %def dispatch_mci_empty @ \subsubsection{Processes: scales and such} Initialize a process and process instance, choose a sampling point and fill the process instance, evaluating a given cut configuration. We use the same trivial process as for the previous test. All momentum and state dependence is trivial, so we just test basic functionality. <>= call test (processes_6, "processes_6", & "handle scales and weight (partonic event)", & u, results) <>= public :: processes_6 <>= subroutine processes_6 (u) integer, intent(in) :: u type(string_t) :: expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: pt_scale, pt_fac_scale, pt_ren_scale, pt_weight type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), pointer :: model_tmp type(model_t), pointer :: model type(var_list_t), target :: var_list type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance type(eval_tree_factory_t) :: expr_factory write (u, "(A)") "* Test output: processes_6" write (u, "(A)") "* Purpose: create a process & &and fill a process instance" write (u, "(A)") write (u, "(A)") "* Prepare expressions" write (u, "(A)") call syntax_pexpr_init () expr_text = "sqrts - 100 GeV" write (u, "(A,A)") "scale = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_scale, stream, .true.) call stream_final (stream) expr_text = "sqrts_hat" write (u, "(A,A)") "fac_scale = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_fac_scale, stream, .true.) call stream_final (stream) expr_text = "eval sqrt (M2) [collect [s]]" write (u, "(A,A)") "ren_scale = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_ren_scale, stream, .true.) call stream_final (stream) expr_text = "n_tot * n_in * n_out * (eval Phi / pi [s])" write (u, "(A,A)") "weight = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_weight, stream, .true.) call stream_final (stream) call ifile_final (ifile) write (u, "(A)") write (u, "(A)") "* Build and initialize a test process" write (u, "(A)") libname = "processes4" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call syntax_model_file_init () allocate (model_tmp) call model_tmp%read (var_str ("Test.mdl"), os_data) call var_list%init_snapshot (model_tmp%get_var_list_ptr ()) model => model_tmp call var_list%append_log (var_str ("?alphas_is_fixed"), .true.) call var_list%append_int (var_str ("seed"), 0) call reset_interaction_counter () allocate (process) call process%init (procname, lib, os_data, model, var_list) call var_list%final () call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_empty) write (u, "(A)") "* Complete process initialization and set cuts" write (u, "(A)") call process%setup_terms () call expr_factory%init (pt_scale%get_root_ptr ()) call process%set_scale (expr_factory) call expr_factory%init (pt_fac_scale%get_root_ptr ()) call process%set_fac_scale (expr_factory) call expr_factory%init (pt_ren_scale%get_root_ptr ()) call process%set_ren_scale (expr_factory) call expr_factory%init (pt_weight%get_root_ptr ()) call process%set_weight (expr_factory) call process%write (.false., u, show_expressions=.true.) write (u, "(A)") write (u, "(A)") "* Create a process instance and evaluate" write (u, "(A)") allocate (process_instance) call process_instance%init (process) call process_instance%choose_mci (1) call process_instance%evaluate_sqme (1, [0.5_default, 0.125_default]) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call parse_tree_final (pt_scale) call parse_tree_final (pt_fac_scale) call parse_tree_final (pt_ren_scale) call parse_tree_final (pt_weight) call syntax_pexpr_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_6" end subroutine processes_6 @ %def processes_6 @ \subsubsection{Event expressions} After generating an event, fill the [[subevt]] and evaluate expressions for selection, reweighting, and analysis. <>= call test (events_3, "events_3", & "expression evaluation", & u, results) <>= public :: events_3 <>= subroutine events_3 (u) use processes_ut, only: prepare_test_process, cleanup_test_process integer, intent(in) :: u type(string_t) :: expr_text type(ifile_t) :: ifile type(stream_t) :: stream type(parse_tree_t) :: pt_selection, pt_reweight, pt_analysis type(eval_tree_factory_t) :: expr_factory type(event_t), allocatable, target :: event type(process_t), allocatable, target :: process type(process_instance_t), allocatable, target :: process_instance type(os_data_t) :: os_data type(model_t), pointer :: model type(var_list_t), target :: var_list write (u, "(A)") "* Test output: events_3" write (u, "(A)") "* Purpose: generate an event and evaluate expressions" write (u, "(A)") call syntax_pexpr_init () write (u, "(A)") "* Expression texts" write (u, "(A)") expr_text = "all Pt > 100 [s]" write (u, "(A,A)") "selection = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (pt_selection, stream, .true.) call stream_final (stream) expr_text = "1 + sqrts_hat / sqrts" write (u, "(A,A)") "reweight = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_expr (pt_reweight, stream, .true.) call stream_final (stream) expr_text = "true" write (u, "(A,A)") "analysis = ", char (expr_text) call ifile_clear (ifile) call ifile_append (ifile, expr_text) call stream_init (stream, ifile) call parse_tree_init_lexpr (pt_analysis, stream, .true.) call stream_final (stream) call ifile_final (ifile) write (u, "(A)") write (u, "(A)") "* Initialize test process event" call os_data%init () call syntax_model_file_init () allocate (model) call model%read (var_str ("Test.mdl"), os_data) call var_list%init_snapshot (model%get_var_list_ptr ()) call var_list%append_log (var_str ("?alphas_is_fixed"), .true.) call var_list%append_int (var_str ("seed"), 0) allocate (process) allocate (process_instance) call prepare_test_process (process, process_instance, model, var_list) call var_list%final () call process_instance%setup_event_data () write (u, "(A)") write (u, "(A)") "* Initialize event object and set expressions" allocate (event) call event%basic_init () call expr_factory%init (pt_selection%get_root_ptr ()) call event%set_selection (expr_factory) call expr_factory%init (pt_reweight%get_root_ptr ()) call event%set_reweight (expr_factory) call expr_factory%init (pt_analysis%get_root_ptr ()) call event%set_analysis (expr_factory) call event%connect (process_instance, process%get_model_ptr ()) call event%expr%var_list%append_real (var_str ("tolerance"), 0._default) call event%setup_expressions () write (u, "(A)") write (u, "(A)") "* Generate test process event" call process_instance%generate_weighted_event (1) write (u, "(A)") write (u, "(A)") "* Fill event object and evaluate expressions" write (u, "(A)") call event%generate (1, [0.4_default, 0.4_default]) call event%set_index (42) call event%evaluate_expressions () call event%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call event%final () deallocate (event) call cleanup_test_process (process, process_instance) deallocate (process_instance) deallocate (process) call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: events_3" end subroutine events_3 @ %def events_3 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Top Level} The top level consists of \begin{description} \item[commands] Defines generic command-list and command objects, and all specific implementations. Each command type provides a specific functionality. Together with the modules that provide expressions and variables, this module defines the Sindarin language. \item[whizard] This module interprets streams of various kind in terms of the command language. It also contains the unit-test feature. We also define the externally visible procedures here, for the \whizard\ as a library. \item[main] The driver for \whizard\ as a stand-alone program. Contains the command-line interpreter. \item[whizard\_c\_interface] Alternative top-level procedures, for use in the context of a C-compatible caller program. \end{description} \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Commands} This module defines the command language of the main input file. <<[[commands.f90]]>>= <> module commands <> <> <> use io_units use string_utils, only: lower_case, split_string, str use format_utils, only: write_indent use format_defs, only: FMT_14, FMT_19 use diagnostics use constants, only: one use physics_defs use sorting use sf_lhapdf, only: lhapdf_global_reset use os_interface use ifiles use lexers use syntax_rules use parser use analysis use pdg_arrays use variables, only: var_list_t, V_NONE, V_LOG, V_INT, V_REAL, V_CMPLX, V_STR, V_PDG use observables, only: var_list_check_observable use observables, only: var_list_check_result_var use eval_trees use models use auto_components use flavors use polarizations use particle_specifiers use process_libraries use process use instances use prclib_stacks use slha_interface use user_files use eio_data use rt_data use process_configurations use compilations, only: compile_library, compile_executable use integrations, only: integrate_process use restricted_subprocesses, only: get_libname_res use restricted_subprocesses, only: spawn_resonant_subprocess_libraries use event_streams use simulations use radiation_generator <> <> <> <> <> <> <> contains <> end module commands @ %def commands @ \subsection{The command type} The command type is a generic type that holds any command, compiled for execution. Each command may come with its own local environment. The command list that determines this environment is allocated as [[options]], if necessary. (It has to be allocated as a pointer because the type definition is recursive.) The local environment is available as a pointer which either points to the global environment, or is explicitly allocated and initialized. <>= type, abstract :: command_t type(parse_node_t), pointer :: pn => null () class(command_t), pointer :: next => null () type(parse_node_t), pointer :: pn_opt => null () type(command_list_t), pointer :: options => null () type(rt_data_t), pointer :: local => null () contains <> end type command_t @ %def command_t @ Finalizer: If there is an option list, finalize the option list and deallocate. If not, the local environment is just a pointer. <>= procedure :: final => command_final <>= recursive subroutine command_final (cmd) class(command_t), intent(inout) :: cmd if (associated (cmd%options)) then call cmd%options%final () deallocate (cmd%options) call cmd%local%local_final () deallocate (cmd%local) else cmd%local => null () end if end subroutine command_final @ %def command_final @ Allocate a command with the appropriate concrete type. Store the parse node pointer in the command object, so we can reference to it when compiling. <>= subroutine dispatch_command (command, pn) class(command_t), intent(inout), pointer :: command type(parse_node_t), intent(in), target :: pn select case (char (parse_node_get_rule_key (pn))) case ("cmd_model") allocate (cmd_model_t :: command) case ("cmd_library") allocate (cmd_library_t :: command) case ("cmd_process") allocate (cmd_process_t :: command) case ("cmd_nlo") allocate (cmd_nlo_t :: command) case ("cmd_compile") allocate (cmd_compile_t :: command) case ("cmd_exec") allocate (cmd_exec_t :: command) case ("cmd_num", "cmd_complex", "cmd_real", "cmd_int", & "cmd_log_decl", "cmd_log", "cmd_string", "cmd_string_decl", & "cmd_alias", "cmd_result") allocate (cmd_var_t :: command) case ("cmd_slha") allocate (cmd_slha_t :: command) case ("cmd_show") allocate (cmd_show_t :: command) case ("cmd_clear") allocate (cmd_clear_t :: command) case ("cmd_expect") allocate (cmd_expect_t :: command) case ("cmd_beams") allocate (cmd_beams_t :: command) case ("cmd_beams_pol_density") allocate (cmd_beams_pol_density_t :: command) case ("cmd_beams_pol_fraction") allocate (cmd_beams_pol_fraction_t :: command) case ("cmd_beams_momentum") allocate (cmd_beams_momentum_t :: command) case ("cmd_beams_theta") allocate (cmd_beams_theta_t :: command) case ("cmd_beams_phi") allocate (cmd_beams_phi_t :: command) case ("cmd_cuts") allocate (cmd_cuts_t :: command) case ("cmd_scale") allocate (cmd_scale_t :: command) case ("cmd_fac_scale") allocate (cmd_fac_scale_t :: command) case ("cmd_ren_scale") allocate (cmd_ren_scale_t :: command) case ("cmd_weight") allocate (cmd_weight_t :: command) case ("cmd_selection") allocate (cmd_selection_t :: command) case ("cmd_reweight") allocate (cmd_reweight_t :: command) case ("cmd_iterations") allocate (cmd_iterations_t :: command) case ("cmd_integrate") allocate (cmd_integrate_t :: command) case ("cmd_observable") allocate (cmd_observable_t :: command) case ("cmd_histogram") allocate (cmd_histogram_t :: command) case ("cmd_plot") allocate (cmd_plot_t :: command) case ("cmd_graph") allocate (cmd_graph_t :: command) case ("cmd_record") allocate (cmd_record_t :: command) case ("cmd_analysis") allocate (cmd_analysis_t :: command) case ("cmd_alt_setup") allocate (cmd_alt_setup_t :: command) case ("cmd_unstable") allocate (cmd_unstable_t :: command) case ("cmd_stable") allocate (cmd_stable_t :: command) case ("cmd_polarized") allocate (cmd_polarized_t :: command) case ("cmd_unpolarized") allocate (cmd_unpolarized_t :: command) case ("cmd_sample_format") allocate (cmd_sample_format_t :: command) case ("cmd_simulate") allocate (cmd_simulate_t :: command) case ("cmd_rescan") allocate (cmd_rescan_t :: command) case ("cmd_write_analysis") allocate (cmd_write_analysis_t :: command) case ("cmd_compile_analysis") allocate (cmd_compile_analysis_t :: command) case ("cmd_open_out") allocate (cmd_open_out_t :: command) case ("cmd_close_out") allocate (cmd_close_out_t :: command) case ("cmd_printf") allocate (cmd_printf_t :: command) case ("cmd_scan") allocate (cmd_scan_t :: command) case ("cmd_if") allocate (cmd_if_t :: command) case ("cmd_include") allocate (cmd_include_t :: command) case ("cmd_export") allocate (cmd_export_t :: command) case ("cmd_quit") allocate (cmd_quit_t :: command) case default print *, char (parse_node_get_rule_key (pn)) call msg_bug ("Command not implemented") end select command%pn => pn end subroutine dispatch_command @ %def dispatch_command @ Output. We allow for indentation so we can display a command tree. <>= procedure (command_write), deferred :: write <>= abstract interface subroutine command_write (cmd, unit, indent) import class(command_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent end subroutine command_write end interface @ %def command_write @ Compile a command. The command type is already fixed, so this is a deferred type-bound procedure. <>= procedure (command_compile), deferred :: compile <>= abstract interface subroutine command_compile (cmd, global) import class(command_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global end subroutine command_compile end interface @ %def command_compile @ Execute a command. This will use and/or modify the runtime data set. If the [[quit]] flag is set, the caller should terminate command execution. <>= procedure (command_execute), deferred :: execute <>= abstract interface subroutine command_execute (cmd, global) import class(command_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global end subroutine command_execute end interface @ %def command_execute @ \subsection{Options} The [[options]] command list is allocated, initialized, and executed, if the command is associated with an option text in curly braces. If present, a separate local runtime data set [[local]] will be allocated and initialized; otherwise, [[local]] becomes a pointer to the global dataset. For output, we indent the options list. <>= procedure :: write_options => command_write_options <>= recursive subroutine command_write_options (cmd, unit, indent) class(command_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: ind ind = 1; if (present (indent)) ind = indent + 1 if (associated (cmd%options)) call cmd%options%write (unit, ind) end subroutine command_write_options @ %def command_write_options @ Compile the options list, if any. This implies initialization of the local environment. Should be done once the [[pn_opt]] node has been assigned (if applicable), but before the actual command compilation. <>= procedure :: compile_options => command_compile_options <>= recursive subroutine command_compile_options (cmd, global) class(command_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (associated (cmd%pn_opt)) then allocate (cmd%local) call cmd%local%local_init (global) call global%copy_globals (cmd%local) allocate (cmd%options) call cmd%options%compile (cmd%pn_opt, cmd%local) call global%restore_globals (cmd%local) call cmd%local%deactivate () else cmd%local => global end if end subroutine command_compile_options @ %def command_compile_options @ Execute options. First prepare the local environment, then execute the command list. <>= procedure :: execute_options => cmd_execute_options <>= recursive subroutine cmd_execute_options (cmd, global) class(command_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (associated (cmd%options)) then call cmd%local%activate () call cmd%options%execute (cmd%local) end if end subroutine cmd_execute_options @ %def cmd_execute_options @ This must be called after the parent command has been executed, to undo temporary modifications to the environment. Note that some modifications to [[global]] can become permanent. <>= procedure :: reset_options => cmd_reset_options <>= subroutine cmd_reset_options (cmd, global) class(command_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (associated (cmd%options)) then call cmd%local%deactivate (global) end if end subroutine cmd_reset_options @ %def cmd_reset_options @ \subsection{Specific command types} \subsubsection{Model configuration} The command declares a model, looks for the specified file and loads it. <>= type, extends (command_t) :: cmd_model_t private type(string_t) :: name type(string_t) :: scheme logical :: ufo_model = .false. logical :: ufo_path_set = .false. type(string_t) :: ufo_path contains <> end type cmd_model_t @ %def cmd_model_t @ Output <>= procedure :: write => cmd_model_write <>= subroutine cmd_model_write (cmd, unit, indent) class(cmd_model_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,1x,'""',A,'""')", advance="no") "model =", char (cmd%name) if (cmd%ufo_model) then if (cmd%ufo_path_set) then write (u, "(1x,A,A,A)") "(ufo (", char (cmd%ufo_path), "))" else write (u, "(1x,A)") "(ufo)" end if else if (cmd%scheme /= "") then write (u, "(1x,'(',A,')')") char (cmd%scheme) else write (u, *) end if end subroutine cmd_model_write @ %def cmd_model_write @ Compile. Get the model name and read the model from file, so it is readily available when the command list is executed. If the model has a scheme argument, take this into account. Assign the model pointer in the [[global]] record, so it can be used for (read-only) variable lookup while compiling further commands. <>= procedure :: compile => cmd_model_compile <>= subroutine cmd_model_compile (cmd, global) class(cmd_model_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_name, pn_arg, pn_scheme type(parse_node_t), pointer :: pn_ufo_arg, pn_path type(model_t), pointer :: model type(string_t) :: scheme pn_name => cmd%pn%get_sub_ptr (3) pn_arg => pn_name%get_next_ptr () if (associated (pn_arg)) then pn_scheme => pn_arg%get_sub_ptr () else pn_scheme => null () end if cmd%name = pn_name%get_string () if (associated (pn_scheme)) then select case (char (pn_scheme%get_rule_key ())) case ("ufo_spec") cmd%ufo_model = .true. pn_ufo_arg => pn_scheme%get_sub_ptr (2) if (associated (pn_ufo_arg)) then pn_path => pn_ufo_arg%get_sub_ptr () cmd%ufo_path_set = .true. cmd%ufo_path = pn_path%get_string () end if case default scheme = pn_scheme%get_string () select case (char (lower_case (scheme))) case ("ufo"); cmd%ufo_model = .true. case default; cmd%scheme = scheme end select end select if (cmd%ufo_model) then if (cmd%ufo_path_set) then call preload_ufo_model (model, cmd%name, cmd%ufo_path) else call preload_ufo_model (model, cmd%name) end if else call preload_model (model, cmd%name, cmd%scheme) end if else cmd%scheme = "" call preload_model (model, cmd%name) end if global%model => model if (associated (global%model)) then call global%model%link_var_list (global%var_list) end if contains subroutine preload_model (model, name, scheme) type(model_t), pointer, intent(out) :: model type(string_t), intent(in) :: name type(string_t), intent(in), optional :: scheme model => null () if (associated (global%model)) then if (global%model%matches (name, scheme)) then model => global%model end if end if if (.not. associated (model)) then if (global%model_list%model_exists (name, scheme)) then model => global%model_list%get_model_ptr (name, scheme) else call global%read_model (name, model, scheme) end if end if end subroutine preload_model subroutine preload_ufo_model (model, name, ufo_path) type(model_t), pointer, intent(out) :: model type(string_t), intent(in) :: name type(string_t), intent(in), optional :: ufo_path model => null () if (associated (global%model)) then if (global%model%matches (name, ufo=.true., ufo_path=ufo_path)) then model => global%model end if end if if (.not. associated (model)) then if (global%model_list%model_exists (name, & ufo=.true., ufo_path=ufo_path)) then model => global%model_list%get_model_ptr (name, & ufo=.true., ufo_path=ufo_path) else call global%read_ufo_model (name, model, ufo_path=ufo_path) end if end if end subroutine preload_ufo_model end subroutine cmd_model_compile @ %def cmd_model_compile @ Execute: Insert a pointer into the global data record and reassign the variable list. <>= procedure :: execute => cmd_model_execute <>= subroutine cmd_model_execute (cmd, global) class(cmd_model_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (cmd%ufo_model) then if (cmd%ufo_path_set) then call global%select_model (cmd%name, ufo=.true., ufo_path=cmd%ufo_path) else call global%select_model (cmd%name, ufo=.true.) end if else if (cmd%scheme /= "") then call global%select_model (cmd%name, cmd%scheme) else call global%select_model (cmd%name) end if if (.not. associated (global%model)) & call msg_fatal ("Switching to model '" & // char (cmd%name) // "': model not found") end subroutine cmd_model_execute @ %def cmd_model_execute @ \subsubsection{Library configuration} We configure a process library that should hold the subsequently defined processes. If the referenced library exists already, just make it the currently active one. <>= type, extends (command_t) :: cmd_library_t private type(string_t) :: name contains <> end type cmd_library_t @ %def cmd_library_t @ Output. <>= procedure :: write => cmd_library_write <>= subroutine cmd_library_write (cmd, unit, indent) class(cmd_library_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit) call write_indent (u, indent) write (u, "(1x,A,1x,'""',A,'""')") "library =", char (cmd%name) end subroutine cmd_library_write @ %def cmd_library_write @ Compile. Get the library name. <>= procedure :: compile => cmd_library_compile <>= subroutine cmd_library_compile (cmd, global) class(cmd_library_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_name pn_name => parse_node_get_sub_ptr (cmd%pn, 3) cmd%name = parse_node_get_string (pn_name) end subroutine cmd_library_compile @ %def cmd_library_compile @ Execute: Initialize a new library and push it on the library stack (if it does not yet exist). Insert a pointer to the library into the global data record. Then, try to load the library unless the [[rebuild]] flag is set. <>= procedure :: execute => cmd_library_execute <>= subroutine cmd_library_execute (cmd, global) class(cmd_library_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib logical :: rebuild_library lib => global%prclib_stack%get_library_ptr (cmd%name) rebuild_library = & global%var_list%get_lval (var_str ("?rebuild_library")) if (.not. (associated (lib))) then allocate (lib_entry) call lib_entry%init (cmd%name) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) else call global%update_prclib (lib) end if if (associated (lib) .and. .not. rebuild_library) then call lib%update_status (global%os_data) end if end subroutine cmd_library_execute @ %def cmd_library_execute @ \subsubsection{Process configuration} We define a process-configuration command as a specific type. The incoming and outgoing particles are given evaluation-trees which we transform to PDG-code arrays. For transferring to \oMega, they are reconverted to strings. For the incoming particles, we store parse nodes individually. We do not yet resolve the outgoing state, so we store just a single parse node. This also includes the choice of method for the corresponding process: [[omega]] for \oMega\ matrix elements as Fortran code, [[ovm]] for \oMega\ matrix elements as a bytecode virtual machine, [[test]] for special processes, [[unit_test]] for internal test matrix elements generated by \whizard, [[template]] and [[template_unity]] for test matrix elements generated by \whizard\ as Fortran code similar to the \oMega\ code. If the one-loop program (OLP) \gosam\ is linked, also matrix elements from there (at leading and next-to-leading order) can be generated via [[gosam]]. <>= type, extends (command_t) :: cmd_process_t private type(string_t) :: id integer :: n_in = 0 type(parse_node_p), dimension(:), allocatable :: pn_pdg_in type(parse_node_t), pointer :: pn_out => null () contains <> end type cmd_process_t @ %def cmd_process_t @ Output. The particle expressions are not resolved, so we just list the number of incoming particles. <>= procedure :: write => cmd_process_write <>= subroutine cmd_process_write (cmd, unit, indent) class(cmd_process_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A,A,I0,A)") "process: ", char (cmd%id), " (", & size (cmd%pn_pdg_in), " -> X)" call cmd%write_options (u, indent) end subroutine cmd_process_write @ %def cmd_process_write @ Compile. Find and assign the parse nodes. <>= procedure :: compile => cmd_process_compile <>= subroutine cmd_process_compile (cmd, global) class(cmd_process_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_id, pn_in, pn_codes integer :: i pn_id => parse_node_get_sub_ptr (cmd%pn, 2) pn_in => parse_node_get_next_ptr (pn_id, 2) cmd%pn_out => parse_node_get_next_ptr (pn_in, 2) cmd%pn_opt => parse_node_get_next_ptr (cmd%pn_out) call cmd%compile_options (global) cmd%id = parse_node_get_string (pn_id) cmd%n_in = parse_node_get_n_sub (pn_in) pn_codes => parse_node_get_sub_ptr (pn_in) allocate (cmd%pn_pdg_in (cmd%n_in)) do i = 1, cmd%n_in cmd%pn_pdg_in(i)%ptr => pn_codes pn_codes => parse_node_get_next_ptr (pn_codes) end do end subroutine cmd_process_compile @ %def cmd_process_compile @ Command execution. Evaluate the subevents, transform PDG codes into strings, and add the current process configuration to the process library. The initial state will be unique (one or two particles). For the final state, we allow for expressions. The expressions will be expanded until we have a sum of final states. Each distinct final state will get its own process component. To identify equivalent final states, we transform the final state into an array of PDG codes, which we sort and compare. If a particle entry is actually a PDG array, only the first entry in the array is used for the comparison. The user should make sure that there is no overlap between different particles or arrays which would make the expansion ambiguous. There are two possibilities that a process contains more than one component: by an explicit component statement by the user for inclusive processes, or by having one process at NLO level. The first option is determined in the chunk [[scan components]], and determines [[n_components]]. <>= procedure :: execute => cmd_process_execute <>= subroutine cmd_process_execute (cmd, global) class(cmd_process_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(pdg_array_t) :: pdg_in, pdg_out type(pdg_array_t), dimension(:), allocatable :: pdg_out_tab type(string_t), dimension(:), allocatable :: prt_in type(string_t) :: prt_out, prt_out1 type(process_configuration_t) :: prc_config type(prt_expr_t) :: prt_expr_out type(prt_spec_t), dimension(:), allocatable :: prt_spec_in type(prt_spec_t), dimension(:), allocatable :: prt_spec_out type(var_list_t), pointer :: var_list integer, dimension(:), allocatable :: ipdg integer, dimension(:), allocatable :: i_term integer, dimension(:), allocatable :: nlo_comp integer :: i, j, n_in, n_out, n_terms, n_components logical :: nlo_fixed_order logical :: qcd_corr, qed_corr type(string_t), dimension(:), allocatable :: prt_in_nlo, prt_out_nlo type(radiation_generator_t) :: radiation_generator type(pdg_list_t) :: pl_in, pl_out, pl_excluded_gauge_splittings type(string_t) :: method, born_me_method, loop_me_method, & correlation_me_method, real_tree_me_method, dglap_me_method integer, dimension(:), allocatable :: i_list logical :: use_real_finite logical :: gks_active logical :: initial_state_colored logical :: neg_sf integer :: comp_mult integer :: gks_multiplicity integer :: n_components_init integer :: alpha_power, alphas_power logical :: requires_soft_mismatch, requires_dglap_remnants type(string_t) :: nlo_correction_type type(pdg_array_t), dimension(:), allocatable :: pdg if (debug_on) call msg_debug (D_CORE, "cmd_process_execute") var_list => cmd%local%get_var_list_ptr () n_in = size (cmd%pn_pdg_in) allocate (prt_in (n_in), prt_spec_in (n_in)) do i = 1, n_in pdg_in = & eval_pdg_array (cmd%pn_pdg_in(i)%ptr, var_list) prt_in(i) = make_flavor_string (pdg_in, cmd%local%model) prt_spec_in(i) = new_prt_spec (prt_in(i)) end do call compile_prt_expr & (prt_expr_out, cmd%pn_out, var_list, cmd%local%model) call prt_expr_out%expand () <> allocate (nlo_comp (n_components)) nlo_fixed_order = cmd%local%nlo_fixed_order gks_multiplicity = var_list%get_ival (var_str ("gks_multiplicity")) gks_active = gks_multiplicity > 2 neg_sf = .false. select case (char (var_list%get_sval (var_str ("$negative_sf")))) case ("default") neg_sf = nlo_fixed_order case ("negative") neg_sf = .true. case ("positive") neg_sf = .false. case default call msg_fatal ("Negative PDF handling can only be " // & "default, negative or positive.") end select <> method = var_list%get_sval (var_str ("$method")) born_me_method = var_list%get_sval (var_str ("$born_me_method")) if (born_me_method == var_str ("")) born_me_method = method select case (char (var_list%get_sval (var_str ("$real_partition_mode")))) case ("default", "off", "singular") use_real_finite = .false. case ("all", "on", "finite") use_real_finite = .true. case default call msg_fatal ("The real partition mode can only be " // & "default, off, all, on, singular or finite.") end select if (nlo_fixed_order) then real_tree_me_method = & var_list%get_sval (var_str ("$real_tree_me_method")) if (real_tree_me_method == var_str ("")) & real_tree_me_method = method loop_me_method = var_list%get_sval (var_str ("$loop_me_method")) if (loop_me_method == var_str ("")) & loop_me_method = method correlation_me_method = & var_list%get_sval (var_str ("$correlation_me_method")) if (correlation_me_method == var_str ("")) & correlation_me_method = method dglap_me_method = var_list%get_sval (var_str ("$dglap_me_method")) if (dglap_me_method == var_str ("")) & dglap_me_method = method call check_nlo_options (cmd%local) end if call determine_needed_components () call prc_config%init (cmd%id, n_in, n_components_init, & cmd%local%model, cmd%local%var_list, & nlo_process = nlo_fixed_order, & negative_sf = neg_sf) alpha_power = var_list%get_ival (var_str ("alpha_power")) alphas_power = var_list%get_ival (var_str ("alphas_power")) call prc_config%set_coupling_powers (alpha_power, alphas_power) call setup_components () call prc_config%record (cmd%local) contains <> end subroutine cmd_process_execute @ %def cmd_process_execute @ <>= elemental function is_threshold (method) logical :: is_threshold type(string_t), intent(in) :: method is_threshold = method == var_str ("threshold") end function is_threshold subroutine check_threshold_consistency () if (nlo_fixed_order .and. is_threshold (born_me_method)) then if (.not. (is_threshold (real_tree_me_method) .and. is_threshold (loop_me_method) & .and. is_threshold (correlation_me_method))) then print *, 'born: ', char (born_me_method) print *, 'real: ', char (real_tree_me_method) print *, 'loop: ', char (loop_me_method) print *, 'correlation: ', char (correlation_me_method) call msg_fatal ("Inconsistent methods: All components need to be threshold") end if end if end subroutine check_threshold_consistency @ %def check_threshold_consistency <>= if (nlo_fixed_order .or. gks_active) then nlo_correction_type = & var_list%get_sval (var_str ('$nlo_correction_type')) select case (char (nlo_correction_type)) case ("QCD") qcd_corr = .true.; qed_corr = .false. case ("EW") qcd_corr = .false.; qed_corr = .true. case ("Full") qcd_corr =.true.; qed_corr = .true. case default call msg_fatal ("Invalid NLO correction type. " // & "Valid inputs are: QCD, EW, Full (default: QCD)") end select call check_for_excluded_gauge_boson_splitting_partners () call setup_radiation_generator () end if if (nlo_fixed_order) then call radiation_generator%find_splittings () if (debug2_active (D_CORE)) then print *, '' print *, 'Found (pdg) splittings: ' do i = 1, radiation_generator%if_table%get_length () call radiation_generator%if_table%get_pdg_out (i, pdg) call pdg_array_write_set (pdg) print *, '----------------' end do end if nlo_fixed_order = radiation_generator%contains_emissions () if (.not. nlo_fixed_order) call msg_warning & (arr = [var_str ("No NLO corrections found for process ") // & cmd%id // var_str("."), var_str ("Proceed with usual " // & "leading-order integration and simulation")]) end if @ %def check_for_nlo_corrections @ <>= subroutine check_for_excluded_gauge_boson_splitting_partners () type(string_t) :: str_excluded_partners type(string_t), dimension(:), allocatable :: excluded_partners type(pdg_list_t) :: pl_tmp, pl_anti integer :: i, n_anti str_excluded_partners = var_list%get_sval & (var_str ("$exclude_gauge_splittings")) if (str_excluded_partners == "") then return else call split_string (str_excluded_partners, & var_str (":"), excluded_partners) call pl_tmp%init (size (excluded_partners)) do i = 1, size (excluded_partners) call pl_tmp%set (i, & cmd%local%model%get_pdg (excluded_partners(i), .true.)) end do call pl_tmp%create_antiparticles (pl_anti, n_anti) call pl_excluded_gauge_splittings%init (pl_tmp%get_size () + n_anti) do i = 1, pl_tmp%get_size () call pl_excluded_gauge_splittings%set (i, pl_tmp%get(i)) end do do i = 1, n_anti j = i + pl_tmp%get_size () call pl_excluded_gauge_splittings%set (j, pl_anti%get(i)) end do end if end subroutine check_for_excluded_gauge_boson_splitting_partners @ %def check_for_excluded_gauge_boson_splitting_partners @ <>= subroutine determine_needed_components () type(string_t) :: fks_method comp_mult = 1 if (nlo_fixed_order) then fks_method = var_list%get_sval (var_str ('$fks_mapping_type')) call check_threshold_consistency () requires_soft_mismatch = fks_method == var_str ('resonances') comp_mult = needed_extra_components (requires_dglap_remnants, & use_real_finite, requires_soft_mismatch) allocate (i_list (comp_mult)) else if (gks_active) then call radiation_generator%generate_multiple & (gks_multiplicity, cmd%local%model) comp_mult = radiation_generator%get_n_gks_states () + 1 end if n_components_init = n_components * comp_mult end subroutine determine_needed_components @ %def determine_needed_components @ <>= subroutine setup_radiation_generator () call split_prt (prt_spec_in, n_in, pl_in) call split_prt (prt_spec_out, n_out, pl_out) call radiation_generator%init (pl_in, pl_out, & pl_excluded_gauge_splittings, qcd = qcd_corr, qed = qed_corr) call radiation_generator%set_n (n_in, n_out, 0) initial_state_colored = pdg_in%has_colored_particles () if ((n_in == 2 .and. initial_state_colored) .or. qed_corr) then requires_dglap_remnants = n_in == 2 .and. initial_state_colored call radiation_generator%set_initial_state_emissions () else requires_dglap_remnants = .false. end if call radiation_generator%set_constraints (.false., .false., .true., .true.) call radiation_generator%setup_if_table (cmd%local%model) end subroutine setup_radiation_generator @ %def setup_radiation_generator @ <>= n_terms = prt_expr_out%get_n_terms () allocate (pdg_out_tab (n_terms)) allocate (i_term (n_terms), source = 0) n_components = 0 SCAN: do i = 1, n_terms if (allocated (ipdg)) deallocate (ipdg) call prt_expr_out%term_to_array (prt_spec_out, i) n_out = size (prt_spec_out) allocate (ipdg (n_out)) do j = 1, n_out prt_out = prt_spec_out(j)%to_string () call split (prt_out, prt_out1, ":") ipdg(j) = cmd%local%model%get_pdg (prt_out1) end do pdg_out = sort (ipdg) do j = 1, n_components if (pdg_out == pdg_out_tab(j)) cycle SCAN end do n_components = n_components + 1 i_term(n_components) = i pdg_out_tab(n_components) = pdg_out end do SCAN @ <>= subroutine split_prt (prt, n_out, pl) type(prt_spec_t), intent(in), dimension(:), allocatable :: prt integer, intent(in) :: n_out type(pdg_list_t), intent(out) :: pl type(pdg_array_t) :: pdg type(string_t) :: prt_string, prt_tmp integer, parameter :: max_particle_number = 25 integer, dimension(max_particle_number) :: i_particle integer :: i, j, n i_particle = 0 call pl%init (n_out) do i = 1, n_out n = 1 prt_string = prt(i)%to_string () do call split (prt_string, prt_tmp, ":") if (prt_tmp /= "") then i_particle(n) = cmd%local%model%get_pdg (prt_tmp) n = n + 1 else exit end if end do call pdg_array_init (pdg, n - 1) do j = 1, n - 1 call pdg%set (j, i_particle(j)) end do call pl%set (i, pdg) call pdg_array_delete (pdg) end do end subroutine split_prt @ %def split_prt @ <>= subroutine setup_components() integer :: k, i_comp, add_index i_comp = 0 add_index = 0 if (debug_on) call msg_debug (D_CORE, "setup_components") do i = 1, n_components call prt_expr_out%term_to_array (prt_spec_out, i_term(i)) if (nlo_fixed_order) then associate (selected_nlo_parts => cmd%local%selected_nlo_parts) if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 1) call prc_config%setup_component (i_comp + 1, & prt_spec_in, prt_spec_out, & cmd%local%model, var_list, BORN, & can_be_integrated = selected_nlo_parts (BORN)) call radiation_generator%generate_real_particle_strings & (prt_in_nlo, prt_out_nlo) if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 2) call prc_config%setup_component (i_comp + 2, & new_prt_spec (prt_in_nlo), new_prt_spec (prt_out_nlo), & cmd%local%model, var_list, NLO_REAL, & can_be_integrated = selected_nlo_parts (NLO_REAL)) if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 3) call prc_config%setup_component (i_comp + 3, & prt_spec_in, prt_spec_out, & cmd%local%model, var_list, NLO_VIRTUAL, & can_be_integrated = selected_nlo_parts (NLO_VIRTUAL)) if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 4) call prc_config%setup_component (i_comp + 4, & prt_spec_in, prt_spec_out, & cmd%local%model, var_list, NLO_SUBTRACTION, & can_be_integrated = selected_nlo_parts (NLO_SUBTRACTION)) do k = 1, 4 i_list(k) = i_comp + k end do if (requires_dglap_remnants) then if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 5) call prc_config%setup_component (i_comp + 5, & prt_spec_in, prt_spec_out, & cmd%local%model, var_list, NLO_DGLAP, & can_be_integrated = selected_nlo_parts (NLO_DGLAP)) i_list(5) = i_comp + 5 add_index = add_index + 1 end if if (use_real_finite) then if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 5 + add_index) call prc_config%setup_component (i_comp + 5 + add_index, & new_prt_spec (prt_in_nlo), new_prt_spec (prt_out_nlo), & cmd%local%model, var_list, NLO_REAL, & can_be_integrated = selected_nlo_parts (NLO_REAL)) i_list(5 + add_index) = i_comp + 5 + add_index add_index = add_index + 1 end if if (requires_soft_mismatch) then if (debug_on) call msg_debug (D_CORE, "Setting up this NLO component:", & i_comp + 5 + add_index) call prc_config%setup_component (i_comp + 5 + add_index, & prt_spec_in, prt_spec_out, & cmd%local%model, var_list, NLO_MISMATCH, & can_be_integrated = selected_nlo_parts (NLO_MISMATCH)) i_list(5 + add_index) = i_comp + 5 + add_index end if call prc_config%set_component_associations (i_list, & requires_dglap_remnants, use_real_finite, & requires_soft_mismatch) end associate else if (gks_active) then call prc_config%setup_component (i_comp + 1, prt_spec_in, & prt_spec_out, cmd%local%model, var_list, BORN, & can_be_integrated = .true.) call radiation_generator%reset_queue () do j = 1, comp_mult prt_out_nlo = radiation_generator%get_next_state () call prc_config%setup_component (i_comp + 1 + j, & new_prt_spec (prt_in), new_prt_spec (prt_out_nlo), & cmd%local%model, var_list, GKS, can_be_integrated = .false.) end do else call prc_config%setup_component (i, & prt_spec_in, prt_spec_out, & cmd%local%model, var_list, can_be_integrated = .true.) end if i_comp = i_comp + comp_mult end do end subroutine setup_components @ @ These three functions should be bundled with the logicals they depend on into an object (the pcm?). <>= subroutine check_nlo_options (local) type(rt_data_t), intent(in) :: local type(var_list_t), pointer :: var_list => null () real :: mult_real, mult_virt, mult_dglap logical :: combined, powheg logical :: case_lo_but_any_other logical :: fixed_order_nlo_events logical :: real_finite_only var_list => local%get_var_list_ptr () combined = var_list%get_lval (var_str ('?combined_nlo_integration')) powheg = var_list%get_lval (var_str ('?powheg_matching')) if (powheg .and. .not. combined) then call msg_fatal ("POWHEG matching requires the 'combined_nlo_integration' & &-option to be set to true.") end if fixed_order_nlo_events = & var_list%get_lval (var_str ('?fixed_order_nlo_events')) if (fixed_order_nlo_events .and. .not. combined .and. & count (local%selected_nlo_parts) > 1) & call msg_fatal ("Option mismatch: Fixed order NLO events of multiple ", & [var_str ("components are requested, but ?combined_nlo_integration "), & var_str ("is false. You can either switch to the combined NLO "), & var_str ("integration mode for the full process or choose one "), & var_str ("individual NLO component to generate events with.")]) real_finite_only = local%var_list%get_sval (var_str ("$real_partition_mode")) == "finite" associate (nlo_parts => local%selected_nlo_parts) ! TODO (PS-2020-03-26): This technically leaves the possibility to skip this ! message by deactivating the dglap component for a proton collider process. ! To circumvent this, the selected_nlo_parts should be refactored. if (combined .and. .not. (nlo_parts(BORN) & .and. nlo_parts(NLO_VIRTUAL) .and. nlo_parts(NLO_REAL))) then call msg_fatal ("A combined integration of anything else than", & [var_str ("all NLO components together is not supported.")]) end if if (real_finite_only .and. combined) then call msg_fatal ("You cannot do a combined integration without", & [var_str ("the real singular component.")]) end if if (real_finite_only .and. count(nlo_parts([BORN,NLO_VIRTUAL,NLO_DGLAP])) > 1) then call msg_fatal ("You cannot do a full NLO integration without", & [var_str ("the real singular component.")]) end if end associate mult_real = local%var_list%get_rval (var_str ("mult_call_real")) mult_virt = local%var_list%get_rval (var_str ("mult_call_virt")) mult_dglap = local%var_list%get_rval (var_str ("mult_call_dglap")) if (combined .and. (mult_real /= one .or. mult_virt /= one .or. mult_dglap /= one)) then call msg_warning ("mult_call_real, mult_call_virt and mult_call_dglap", & [var_str (" will be ignored because of ?combined_nlo_integration = true. ")]) end if end subroutine check_nlo_options @ %def check_nlo_options @ There are four components for a general NLO process, namely Born, real, virtual and subtraction. There will be additional components for DGLAP remnant, in case real contributions are split into singular and finite pieces, and for resonance-aware FKS subtraction for the needed soft mismatch component. <>= pure function needed_extra_components (requires_dglap_remnant, & use_real_finite, requires_soft_mismatch) result (n) integer :: n logical, intent(in) :: requires_dglap_remnant, & use_real_finite, requires_soft_mismatch n = 4 if (requires_dglap_remnant) n = n + 1 if (use_real_finite) n = n + 1 if (requires_soft_mismatch) n = n + 1 end function needed_extra_components @ %def needed_extra_components @ This is a method of the eval tree, but cannot be coded inside the [[expressions]] module since it uses the [[model]] and [[flv]] types which are not available there. <>= function make_flavor_string (aval, model) result (prt) type(string_t) :: prt type(pdg_array_t), intent(in) :: aval type(model_t), intent(in), target :: model integer, dimension(:), allocatable :: pdg type(flavor_t), dimension(:), allocatable :: flv integer :: i pdg = aval allocate (flv (size (pdg))) call flv%init (pdg, model) if (size (pdg) /= 0) then prt = flv(1)%get_name () do i = 2, size (flv) prt = prt // ":" // flv(i)%get_name () end do else prt = "?" end if end function make_flavor_string @ %def make_flavor_string @ Create a pdg array from a particle-specification array <>= function make_pdg_array (prt, model) result (pdg_array) type(prt_spec_t), intent(in), dimension(:) :: prt type(model_t), intent(in) :: model integer, dimension(:), allocatable :: aval type(pdg_array_t) :: pdg_array type(flavor_t) :: flv integer :: k allocate (aval (size (prt))) do k = 1, size (prt) call flv%init (prt(k)%to_string (), model) aval (k) = flv%get_pdg () end do pdg_array = aval end function make_pdg_array @ %def make_pdg_array @ Compile a (possible nested) expression, to obtain a particle-specifier expression which we can process further. <>= recursive subroutine compile_prt_expr (prt_expr, pn, var_list, model) type(prt_expr_t), intent(out) :: prt_expr type(parse_node_t), intent(in), target :: pn type(var_list_t), intent(in), target :: var_list type(model_t), intent(in), target :: model type(parse_node_t), pointer :: pn_entry, pn_term, pn_addition type(pdg_array_t) :: pdg type(string_t) :: prt_string integer :: n_entry, n_term, i select case (char (parse_node_get_rule_key (pn))) case ("prt_state_list") n_entry = parse_node_get_n_sub (pn) pn_entry => parse_node_get_sub_ptr (pn) if (n_entry == 1) then call compile_prt_expr (prt_expr, pn_entry, var_list, model) else call prt_expr%init_list (n_entry) select type (x => prt_expr%x) type is (prt_spec_list_t) do i = 1, n_entry call compile_prt_expr (x%expr(i), pn_entry, var_list, model) pn_entry => parse_node_get_next_ptr (pn_entry) end do end select end if case ("prt_state_sum") n_term = parse_node_get_n_sub (pn) pn_term => parse_node_get_sub_ptr (pn) pn_addition => pn_term if (n_term == 1) then call compile_prt_expr (prt_expr, pn_term, var_list, model) else call prt_expr%init_sum (n_term) select type (x => prt_expr%x) type is (prt_spec_sum_t) do i = 1, n_term call compile_prt_expr (x%expr(i), pn_term, var_list, model) pn_addition => parse_node_get_next_ptr (pn_addition) if (associated (pn_addition)) & pn_term => parse_node_get_sub_ptr (pn_addition, 2) end do end select end if case ("cexpr") pdg = eval_pdg_array (pn, var_list) prt_string = make_flavor_string (pdg, model) call prt_expr%init_spec (new_prt_spec (prt_string)) case default call parse_node_write_rec (pn) call msg_bug ("compile prt expr: impossible syntax rule") end select end subroutine compile_prt_expr @ %def compile_prt_expr @ \subsubsection{Initiating a NLO calculation} <>= type, extends (command_t) :: cmd_nlo_t private integer, dimension(:), allocatable :: nlo_component contains <> end type cmd_nlo_t @ %def cmd_nlo_t @ <>= procedure :: write => cmd_nlo_write <>= subroutine cmd_nlo_write (cmd, unit, indent) class(cmd_nlo_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent end subroutine cmd_nlo_write @ %def cmd_nlo_write @ As it is, the NLO calculation is switched on by putting {nlo} behind the process definition. This should be made nicer in the future. <>= procedure :: compile => cmd_nlo_compile <>= subroutine cmd_nlo_compile (cmd, global) class(cmd_nlo_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_comp integer :: i, n_comp pn_arg => parse_node_get_sub_ptr (cmd%pn, 3) if (associated (pn_arg)) then n_comp = parse_node_get_n_sub (pn_arg) allocate (cmd%nlo_component (n_comp)) pn_comp => parse_node_get_sub_ptr (pn_arg) i = 0 do while (associated (pn_comp)) i = i + 1 cmd%nlo_component(i) = component_status & (parse_node_get_rule_key (pn_comp)) pn_comp => parse_node_get_next_ptr (pn_comp) end do else allocate (cmd%nlo_component (0)) end if end subroutine cmd_nlo_compile @ %def cmd_nlo_compile @ % TODO (PS-2020-03-26): This routine still needs to be adopted % to cope with more than 5 components. <>= procedure :: execute => cmd_nlo_execute <>= subroutine cmd_nlo_execute (cmd, global) class(cmd_nlo_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(string_t) :: string integer :: n, i, j logical, dimension(0:5) :: selected_nlo_parts if (debug_on) call msg_debug (D_CORE, "cmd_nlo_execute") selected_nlo_parts = .false. if (allocated (cmd%nlo_component)) then n = size (cmd%nlo_component) else n = 0 end if do i = 1, n select case (cmd%nlo_component (i)) case (BORN, NLO_VIRTUAL, NLO_MISMATCH, NLO_DGLAP, NLO_REAL) selected_nlo_parts(cmd%nlo_component (i)) = .true. case (NLO_FULL) selected_nlo_parts = .true. selected_nlo_parts (NLO_SUBTRACTION) = .false. case default string = var_str ("") do j = BORN, NLO_DGLAP string = string // component_status (j) // ", " end do string = string // component_status (NLO_FULL) call msg_fatal ("Invalid NLO mode. Valid modes are: " // & char (string)) end select end do global%nlo_fixed_order = any (selected_nlo_parts) global%selected_nlo_parts = selected_nlo_parts allocate (global%nlo_component (size (cmd%nlo_component))) global%nlo_component = cmd%nlo_component end subroutine cmd_nlo_execute @ %def cmd_nlo_execute @ \subsubsection{Process compilation} <>= type, extends (command_t) :: cmd_compile_t private type(string_t), dimension(:), allocatable :: libname logical :: make_executable = .false. type(string_t) :: exec_name contains <> end type cmd_compile_t @ %def cmd_compile_t @ Output: list all libraries to be compiled. <>= procedure :: write => cmd_compile_write <>= subroutine cmd_compile_write (cmd, unit, indent) class(cmd_compile_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "compile (" if (allocated (cmd%libname)) then do i = 1, size (cmd%libname) if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "('""',A,'""')", advance="no") char (cmd%libname(i)) end do end if write (u, "(A)") ")" end subroutine cmd_compile_write @ %def cmd_compile_write @ Compile the libraries specified in the argument. If the argument is empty, compile all libraries which can be found in the process library stack. <>= procedure :: compile => cmd_compile_compile <>= subroutine cmd_compile_compile (cmd, global) class(cmd_compile_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_cmd, pn_clause, pn_arg, pn_lib type(parse_node_t), pointer :: pn_exec_name_spec, pn_exec_name integer :: n_lib, i pn_cmd => parse_node_get_sub_ptr (cmd%pn) pn_clause => parse_node_get_sub_ptr (pn_cmd) pn_exec_name_spec => parse_node_get_sub_ptr (pn_clause, 2) if (associated (pn_exec_name_spec)) then pn_exec_name => parse_node_get_sub_ptr (pn_exec_name_spec, 2) else pn_exec_name => null () end if pn_arg => parse_node_get_next_ptr (pn_clause) cmd%pn_opt => parse_node_get_next_ptr (pn_cmd) call cmd%compile_options (global) if (associated (pn_arg)) then n_lib = parse_node_get_n_sub (pn_arg) else n_lib = 0 end if if (n_lib > 0) then allocate (cmd%libname (n_lib)) pn_lib => parse_node_get_sub_ptr (pn_arg) do i = 1, n_lib cmd%libname(i) = parse_node_get_string (pn_lib) pn_lib => parse_node_get_next_ptr (pn_lib) end do end if if (associated (pn_exec_name)) then cmd%make_executable = .true. cmd%exec_name = parse_node_get_string (pn_exec_name) end if end subroutine cmd_compile_compile @ %def cmd_compile_compile @ Command execution. Generate code, write driver, compile and link. Do this for all libraries in the list. If no library names have been given and stored while compiling this command, we collect all libraries from the current stack and compile those. As a bonus, a compiled library may be able to spawn new process libraries. For instance, a processes may ask for a set of resonant subprocesses which go into their own library, but this can be determined only after the process is available as a compiled object. Therefore, the compilation loop is implemented as a recursive internal subroutine. We can compile static libraries (which actually just loads them). However, we can't incorporate in a generated executable. <>= procedure :: execute => cmd_compile_execute <>= subroutine cmd_compile_execute (cmd, global) class(cmd_compile_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(string_t), dimension(:), allocatable :: libname, libname_static integer :: i, n_lib <> <> if (allocated (cmd%libname)) then allocate (libname (size (cmd%libname))) libname = cmd%libname else call cmd%local%prclib_stack%get_names (libname) end if n_lib = size (libname) if (cmd%make_executable) then call get_prclib_static (libname_static) do i = 1, n_lib if (any (libname_static == libname(i))) then call msg_fatal ("Compile: can't include static library '" & // char (libname(i)) // "'") end if end do call compile_executable (cmd%exec_name, libname, cmd%local) else call compile_libraries (libname) call global%update_prclib & (global%prclib_stack%get_library_ptr (libname(n_lib))) end if <> contains recursive subroutine compile_libraries (libname) type(string_t), dimension(:), intent(in) :: libname integer :: i type(string_t), dimension(:), allocatable :: libname_extra type(process_library_t), pointer :: lib_saved do i = 1, size (libname) call compile_library (libname(i), cmd%local) lib_saved => global%prclib call spawn_extra_libraries & (libname(i), cmd%local, global, libname_extra) call compile_libraries (libname_extra) call global%update_prclib (lib_saved) end do end subroutine compile_libraries end subroutine cmd_compile_execute @ %def cmd_compile_execute <>= <>= <>= @ The parallelization leads to undefined behavior while writing simultaneously to one file. The master worker has to initialize single-handed the corresponding library files. The slave worker will wait with a blocking [[MPI_BCAST]] until they receive a logical flag. <>= logical :: compile_init integer :: rank, n_size <>= if (debug_on) call msg_debug (D_MPI, "cmd_compile_execute") compile_init = .false. call mpi_get_comm_id (n_size, rank) if (debug_on) call msg_debug (D_MPI, "n_size", rank) if (debug_on) call msg_debug (D_MPI, "rank", rank) if (rank /= 0) then if (debug_on) call msg_debug (D_MPI, "wait for master") call MPI_bcast (compile_init, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD) else compile_init = .true. end if if (compile_init) then <>= if (rank == 0) then if (debug_on) call msg_debug (D_MPI, "load slaves") call MPI_bcast (compile_init, 1, MPI_LOGICAL, 0, MPI_COMM_WORLD) end if end if call MPI_barrier (MPI_COMM_WORLD) @ %def cmd_compile_execute_mpi @ This is the interface to the external procedure which returns the names of all static libraries which are part of the executable. (The default is none.) The routine must allocate the array. <>= public :: get_prclib_static <>= interface subroutine get_prclib_static (libname) import type(string_t), dimension(:), intent(inout), allocatable :: libname end subroutine get_prclib_static end interface @ %def get_prclib_static @ Spawn extra libraries. We can ask the processes within a compiled library, which we have available at this point, whether they need additional processes which should go into their own libraries. The current implementation only concerns resonant subprocesses. Note that the libraries should be created (source code), but not be compiled here. This is done afterwards. <>= subroutine spawn_extra_libraries (libname, local, global, libname_extra) type(string_t), intent(in) :: libname type(rt_data_t), intent(inout), target :: local type(rt_data_t), intent(inout), target :: global type(string_t), dimension(:), allocatable, intent(out) :: libname_extra type(string_t), dimension(:), allocatable :: libname_res allocate (libname_extra (0)) call spawn_resonant_subprocess_libraries & (libname, local, global, libname_res) if (allocated (libname_res)) libname_extra = [libname_extra, libname_res] end subroutine spawn_extra_libraries @ %def spawn_extra_libraries @ \subsubsection{Execute a shell command} The argument is a string expression. <>= type, extends (command_t) :: cmd_exec_t private type(parse_node_t), pointer :: pn_command => null () contains <> end type cmd_exec_t @ %def cmd_exec_t @ Simply tell the status. <>= procedure :: write => cmd_exec_write <>= subroutine cmd_exec_write (cmd, unit, indent) class(cmd_exec_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) if (associated (cmd%pn_command)) then write (u, "(1x,A)") "exec: [command associated]" else write (u, "(1x,A)") "exec: [undefined]" end if end subroutine cmd_exec_write @ %def cmd_exec_write @ Compile the exec command. <>= procedure :: compile => cmd_exec_compile <>= subroutine cmd_exec_compile (cmd, global) class(cmd_exec_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_command pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) pn_command => parse_node_get_sub_ptr (pn_arg) cmd%pn_command => pn_command end subroutine cmd_exec_compile @ %def cmd_exec_compile @ Execute the specified shell command. <>= procedure :: execute => cmd_exec_execute <>= subroutine cmd_exec_execute (cmd, global) class(cmd_exec_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(string_t) :: command logical :: is_known integer :: status command = eval_string (cmd%pn_command, global%var_list, is_known=is_known) if (is_known) then if (command /= "") then call os_system_call (command, status, verbose=.true.) if (status /= 0) then write (msg_buffer, "(A,I0)") "Return code = ", status call msg_message () call msg_error ("System command returned with nonzero status code") end if end if end if end subroutine cmd_exec_execute @ %def cmd_exec_execute @ \subsubsection{Variable declaration} A variable can have various types. Hold the definition as an eval tree. There are intrinsic variables, user variables, and model variables. The latter are further divided in independent variables and dependent variables. Regarding model variables: When dealing with them, we always look at two variable lists in parallel. The global (or local) variable list contains the user-visible values. It includes variables that correspond to variables in the current model's list. These, in turn, are pointers to the model's parameter list, so the model is always in sync, internally. To keep the global variable list in sync with the model, the global variables carry the [[is_copy]] property and contain a separate pointer to the model variable. (The pointer is reassigned whenever the model changes.) Modifying the global variable changes two values simultaneously: the visible value and the model variable, via this extra pointer. After each modification, we update dependent parameters in the model variable list and re-synchronize the global variable list (again, using these pointers) with the model variable this. In the last step, modifications in the derived parameters become visible. When we integrate a process, we capture the current variable list of the current model in a separate model instance, which is stored in the process object. Thus, the model parameters associated to this process at this time are preserved for the lifetime of the process object. When we generate or rescan events, we can again capture a local model variable list in a model instance. This allows us to reweight event by event with different parameter sets simultaneously. <>= type, extends (command_t) :: cmd_var_t private type(string_t) :: name integer :: type = V_NONE type(parse_node_t), pointer :: pn_value => null () logical :: is_intrinsic = .false. logical :: is_model_var = .false. contains <> end type cmd_var_t @ %def cmd_var_t @ Output. We know name, type, and properties, but not the value. <>= procedure :: write => cmd_var_write <>= subroutine cmd_var_write (cmd, unit, indent) class(cmd_var_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A,A)", advance="no") "var: ", char (cmd%name), " (" select case (cmd%type) case (V_NONE) write (u, "(A)", advance="no") "[unknown]" case (V_LOG) write (u, "(A)", advance="no") "logical" case (V_INT) write (u, "(A)", advance="no") "int" case (V_REAL) write (u, "(A)", advance="no") "real" case (V_CMPLX) write (u, "(A)", advance="no") "complex" case (V_STR) write (u, "(A)", advance="no") "string" case (V_PDG) write (u, "(A)", advance="no") "alias" end select if (cmd%is_intrinsic) then write (u, "(A)", advance="no") ", intrinsic" end if if (cmd%is_model_var) then write (u, "(A)", advance="no") ", model" end if write (u, "(A)") ")" end subroutine cmd_var_write @ %def cmd_var_write @ Compile the lhs and determine the variable name and type. Check whether this variable can be created or modified as requested, and append the value to the variable list, if appropriate. The value is initially undefined. The rhs is assigned to a pointer, to be compiled and evaluated when the command is executed. <>= procedure :: compile => cmd_var_compile <>= subroutine cmd_var_compile (cmd, global) class(cmd_var_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_var, pn_name type(parse_node_t), pointer :: pn_result, pn_proc type(string_t) :: var_name type(var_list_t), pointer :: model_vars integer :: type logical :: new pn_result => null () new = .false. select case (char (parse_node_get_rule_key (cmd%pn))) case ("cmd_log_decl"); type = V_LOG pn_var => parse_node_get_sub_ptr (cmd%pn, 2) if (.not. associated (pn_var)) then ! handle masked syntax error cmd%type = V_NONE; return end if pn_name => parse_node_get_sub_ptr (pn_var, 2) new = .true. case ("cmd_log"); type = V_LOG pn_name => parse_node_get_sub_ptr (cmd%pn, 2) case ("cmd_int"); type = V_INT pn_name => parse_node_get_sub_ptr (cmd%pn, 2) new = .true. case ("cmd_real"); type = V_REAL pn_name => parse_node_get_sub_ptr (cmd%pn, 2) new = .true. case ("cmd_complex"); type = V_CMPLX pn_name => parse_node_get_sub_ptr (cmd%pn, 2) new = .true. case ("cmd_num"); type = V_NONE pn_name => parse_node_get_sub_ptr (cmd%pn) case ("cmd_string_decl"); type = V_STR pn_var => parse_node_get_sub_ptr (cmd%pn, 2) if (.not. associated (pn_var)) then ! handle masked syntax error cmd%type = V_NONE; return end if pn_name => parse_node_get_sub_ptr (pn_var, 2) new = .true. case ("cmd_string"); type = V_STR pn_name => parse_node_get_sub_ptr (cmd%pn, 2) case ("cmd_alias"); type = V_PDG pn_name => parse_node_get_sub_ptr (cmd%pn, 2) new = .true. case ("cmd_result"); type = V_REAL pn_name => parse_node_get_sub_ptr (cmd%pn) pn_result => parse_node_get_sub_ptr (pn_name) pn_proc => parse_node_get_next_ptr (pn_result) case default call parse_node_mismatch & ("logical|int|real|complex|?|$|alias|var_name", cmd%pn) ! $ end select if (.not. associated (pn_name)) then ! handle masked syntax error cmd%type = V_NONE; return end if if (.not. associated (pn_result)) then var_name = parse_node_get_string (pn_name) else var_name = parse_node_get_key (pn_result) & // "(" // parse_node_get_string (pn_proc) // ")" end if select case (type) case (V_LOG); var_name = "?" // var_name case (V_STR); var_name = "$" // var_name ! $ end select if (associated (global%model)) then model_vars => global%model%get_var_list_ptr () else model_vars => null () end if call var_list_check_observable (global%var_list, var_name, type) call var_list_check_result_var (global%var_list, var_name, type) call global%var_list%check_user_var (var_name, type, new) cmd%name = var_name cmd%pn_value => parse_node_get_next_ptr (pn_name, 2) if (global%var_list%contains (cmd%name, follow_link = .false.)) then ! local variable cmd%is_intrinsic = & global%var_list%is_intrinsic (cmd%name, follow_link = .false.) cmd%type = & global%var_list%get_type (cmd%name, follow_link = .false.) else if (new) cmd%type = type if (global%var_list%contains (cmd%name, follow_link = .true.)) then ! global variable cmd%is_intrinsic = & global%var_list%is_intrinsic (cmd%name, follow_link = .true.) if (cmd%type == V_NONE) then cmd%type = & global%var_list%get_type (cmd%name, follow_link = .true.) end if else if (associated (model_vars)) then ! check model variable cmd%is_model_var = & model_vars%contains (cmd%name) if (cmd%type == V_NONE) then cmd%type = & model_vars%get_type (cmd%name) end if end if if (cmd%type == V_NONE) then call msg_fatal ("Variable '" // char (cmd%name) // "' " & // "set without declaration") cmd%type = V_NONE; return end if if (cmd%is_model_var) then if (new) then call msg_fatal ("Model variable '" // char (cmd%name) // "' " & // "redeclared") else if (model_vars%is_locked (cmd%name)) then call msg_fatal ("Model variable '" // char (cmd%name) // "' " & // "is locked") end if else select case (cmd%type) case (V_LOG) call global%var_list%append_log (cmd%name, & intrinsic=cmd%is_intrinsic, user=.true.) case (V_INT) call global%var_list%append_int (cmd%name, & intrinsic=cmd%is_intrinsic, user=.true.) case (V_REAL) call global%var_list%append_real (cmd%name, & intrinsic=cmd%is_intrinsic, user=.true.) case (V_CMPLX) call global%var_list%append_cmplx (cmd%name, & intrinsic=cmd%is_intrinsic, user=.true.) case (V_PDG) call global%var_list%append_pdg_array (cmd%name, & intrinsic=cmd%is_intrinsic, user=.true.) case (V_STR) call global%var_list%append_string (cmd%name, & intrinsic=cmd%is_intrinsic, user=.true.) end select end if end if end subroutine cmd_var_compile @ %def cmd_var_compile @ Execute. Evaluate the definition and assign the variable value. If the variable is a model variable, take a snapshot of the model if necessary and set the variable in the local model. <>= procedure :: execute => cmd_var_execute <>= subroutine cmd_var_execute (cmd, global) class(cmd_var_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list real(default) :: rval logical :: is_known, pacified var_list => global%get_var_list_ptr () if (cmd%is_model_var) then pacified = var_list%get_lval (var_str ("?pacify")) rval = eval_real (cmd%pn_value, var_list, is_known=is_known) call global%model_set_real & (cmd%name, rval, verbose=.true., pacified=pacified) else if (cmd%type /= V_NONE) then call cmd%set_value (var_list, verbose=.true.) end if end subroutine cmd_var_execute @ %def cmd_var_execute @ Copy the value to the variable list, where the variable should already exist. <>= procedure :: set_value => cmd_var_set_value <>= subroutine cmd_var_set_value (var, var_list, verbose, model_name) class(cmd_var_t), intent(inout) :: var type(var_list_t), intent(inout), target :: var_list logical, intent(in), optional :: verbose type(string_t), intent(in), optional :: model_name logical :: lval, pacified integer :: ival real(default) :: rval complex(default) :: cval type(pdg_array_t) :: aval type(string_t) :: sval logical :: is_known pacified = var_list%get_lval (var_str ("?pacify")) select case (var%type) case (V_LOG) lval = eval_log (var%pn_value, var_list, is_known=is_known) call var_list%set_log (var%name, & lval, is_known, verbose=verbose, model_name=model_name) case (V_INT) ival = eval_int (var%pn_value, var_list, is_known=is_known) call var_list%set_int (var%name, & ival, is_known, verbose=verbose, model_name=model_name) case (V_REAL) rval = eval_real (var%pn_value, var_list, is_known=is_known) call var_list%set_real (var%name, & rval, is_known, verbose=verbose, & model_name=model_name, pacified = pacified) case (V_CMPLX) cval = eval_cmplx (var%pn_value, var_list, is_known=is_known) call var_list%set_cmplx (var%name, & cval, is_known, verbose=verbose, & model_name=model_name, pacified = pacified) case (V_PDG) aval = eval_pdg_array (var%pn_value, var_list, is_known=is_known) call var_list%set_pdg_array (var%name, & aval, is_known, verbose=verbose, model_name=model_name) case (V_STR) sval = eval_string (var%pn_value, var_list, is_known=is_known) call var_list%set_string (var%name, & sval, is_known, verbose=verbose, model_name=model_name) end select end subroutine cmd_var_set_value @ %def cmd_var_set_value @ \subsubsection{SLHA} Read a SLHA (SUSY Les Houches Accord) file to fill the appropriate model parameters. We do not access the current variable record, but directly work on the appropriate SUSY model, which is loaded if necessary. We may be in read or write mode. In the latter case, we may write just input parameters, or the complete spectrum, or the spectrum with all decays. <>= type, extends (command_t) :: cmd_slha_t private type(string_t) :: file logical :: write_mode = .false. contains <> end type cmd_slha_t @ %def cmd_slha_t @ Output. <>= procedure :: write => cmd_slha_write <>= subroutine cmd_slha_write (cmd, unit, indent) class(cmd_slha_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A)") "slha: file name = ", char (cmd%file) write (u, "(1x,A,L1)") "slha: write mode = ", cmd%write_mode end subroutine cmd_slha_write @ %def cmd_slha_write @ Compile. Read the filename and store it. <>= procedure :: compile => cmd_slha_compile <>= subroutine cmd_slha_compile (cmd, global) class(cmd_slha_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_key, pn_arg, pn_file pn_key => parse_node_get_sub_ptr (cmd%pn) pn_arg => parse_node_get_next_ptr (pn_key) pn_file => parse_node_get_sub_ptr (pn_arg) call cmd%compile_options (global) cmd%pn_opt => parse_node_get_next_ptr (pn_arg) select case (char (parse_node_get_key (pn_key))) case ("read_slha") cmd%write_mode = .false. case ("write_slha") cmd%write_mode = .true. case default call parse_node_mismatch ("read_slha|write_slha", cmd%pn) end select cmd%file = parse_node_get_string (pn_file) end subroutine cmd_slha_compile @ %def cmd_slha_compile @ Execute. Read or write the specified SLHA file. Behind the scenes, this will first read the WHIZARD model file, then read the SLHA file and assign the SLHA parameters as far as determined by [[dispatch_slha]]. Finally, the global variables are synchronized with the model. This is similar to executing [[cmd_model]]. <>= procedure :: execute => cmd_slha_execute <>= subroutine cmd_slha_execute (cmd, global) class(cmd_slha_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global logical :: input, spectrum, decays if (cmd%write_mode) then input = .true. spectrum = .false. decays = .false. if (.not. associated (cmd%local%model)) then call msg_fatal ("SLHA: local model not associated") return end if call slha_write_file & (cmd%file, cmd%local%model, & input = input, spectrum = spectrum, decays = decays) else if (.not. associated (global%model)) then call msg_fatal ("SLHA: global model not associated") return end if call dispatch_slha (cmd%local%var_list, & input = input, spectrum = spectrum, decays = decays) call global%ensure_model_copy () call slha_read_file & (cmd%file, cmd%local%os_data, global%model, & input = input, spectrum = spectrum, decays = decays) end if end subroutine cmd_slha_execute @ %def cmd_slha_execute @ \subsubsection{Show values} This command shows the current values of variables or other objects, in a suitably condensed form. <>= type, extends (command_t) :: cmd_show_t private type(string_t), dimension(:), allocatable :: name contains <> end type cmd_show_t @ %def cmd_show_t @ Output: list the object names, not values. <>= procedure :: write => cmd_show_write <>= subroutine cmd_show_write (cmd, unit, indent) class(cmd_show_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "show: " if (allocated (cmd%name)) then do i = 1, size (cmd%name) write (u, "(1x,A)", advance="no") char (cmd%name(i)) end do write (u, *) else write (u, "(5x,A)") "[undefined]" end if end subroutine cmd_show_write @ %def cmd_show_write @ Compile. Allocate an array which is filled with the names of the variables to show. <>= procedure :: compile => cmd_show_compile <>= subroutine cmd_show_compile (cmd, global) class(cmd_show_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_var, pn_prefix, pn_name type(string_t) :: key integer :: i, n_args pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) if (associated (pn_arg)) then select case (char (parse_node_get_rule_key (pn_arg))) case ("show_arg") cmd%pn_opt => parse_node_get_next_ptr (pn_arg) case default cmd%pn_opt => pn_arg pn_arg => null () end select end if call cmd%compile_options (global) if (associated (pn_arg)) then n_args = parse_node_get_n_sub (pn_arg) allocate (cmd%name (n_args)) pn_var => parse_node_get_sub_ptr (pn_arg) i = 0 do while (associated (pn_var)) i = i + 1 select case (char (parse_node_get_rule_key (pn_var))) case ("model", "library", "beams", "iterations", & "cuts", "weight", "int", "real", "complex", & "scale", "factorization_scale", "renormalization_scale", & "selection", "reweight", "analysis", "pdg", & "stable", "unstable", "polarized", "unpolarized", & "results", "expect", "intrinsic", "string", "logical") cmd%name(i) = parse_node_get_key (pn_var) case ("result_var") pn_prefix => parse_node_get_sub_ptr (pn_var) pn_name => parse_node_get_next_ptr (pn_prefix) if (associated (pn_name)) then cmd%name(i) = parse_node_get_key (pn_prefix) & // "(" // parse_node_get_string (pn_name) // ")" else cmd%name(i) = parse_node_get_key (pn_prefix) end if case ("log_var", "string_var", "alias_var") pn_prefix => parse_node_get_sub_ptr (pn_var) pn_name => parse_node_get_next_ptr (pn_prefix) key = parse_node_get_key (pn_prefix) if (associated (pn_name)) then select case (char (parse_node_get_rule_key (pn_name))) case ("var_name") select case (char (key)) case ("?", "$") ! $ sign cmd%name(i) = key // parse_node_get_string (pn_name) case ("alias") cmd%name(i) = parse_node_get_string (pn_name) end select case default call parse_node_mismatch & ("var_name", pn_name) end select else cmd%name(i) = key end if case default cmd%name(i) = parse_node_get_string (pn_var) end select pn_var => parse_node_get_next_ptr (pn_var) end do else allocate (cmd%name (0)) end if end subroutine cmd_show_compile @ %def cmd_show_compile @ Execute. Scan the list of objects to show. <>= integer, parameter, public :: SHOW_BUFFER_SIZE = 4096 <>= procedure :: execute => cmd_show_execute <>= subroutine cmd_show_execute (cmd, global) class(cmd_show_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list, model_vars type(model_t), pointer :: model type(string_t) :: name integer :: n, pdg type(flavor_t) :: flv type(process_library_t), pointer :: prc_lib type(process_t), pointer :: process logical :: pacified character(SHOW_BUFFER_SIZE) :: buffer type(string_t) :: out_file integer :: i, j, u, u_log, u_out, u_ext u = free_unit () var_list => cmd%local%var_list if (associated (cmd%local%model)) then model_vars => cmd%local%model%get_var_list_ptr () else model_vars => null () end if pacified = var_list%get_lval (var_str ("?pacify")) out_file = var_list%get_sval (var_str ("$out_file")) if (file_list_is_open (global%out_files, out_file, action="write")) then call msg_message ("show: copying output to file '" & // char (out_file) // "'") u_ext = file_list_get_unit (global%out_files, out_file) else u_ext = -1 end if open (u, status = "scratch", action = "readwrite") if (associated (cmd%local%model)) then name = cmd%local%model%get_name () end if if (size (cmd%name) == 0) then if (associated (model_vars)) then call model_vars%write (model_name = name, & unit = u, pacified = pacified, follow_link = .false.) end if call var_list%write (unit = u, pacified = pacified) else do i = 1, size (cmd%name) select case (char (cmd%name(i))) case ("model") if (associated (cmd%local%model)) then call cmd%local%model%show (u) else write (u, "(A)") "Model: [undefined]" end if case ("library") if (associated (cmd%local%prclib)) then call cmd%local%prclib%show (u) else write (u, "(A)") "Process library: [undefined]" end if case ("beams") call cmd%local%show_beams (u) case ("iterations") call cmd%local%it_list%write (u) case ("results") call cmd%local%process_stack%show (u, fifo=.true.) case ("stable") call cmd%local%model%show_stable (u) case ("polarized") call cmd%local%model%show_polarized (u) case ("unpolarized") call cmd%local%model%show_unpolarized (u) case ("unstable") model => cmd%local%model call model%show_unstable (u) n = model%get_n_field () do j = 1, n pdg = model%get_pdg (j) call flv%init (pdg, model) if (.not. flv%is_stable ()) & call show_unstable (cmd%local, pdg, u) if (flv%has_antiparticle ()) then associate (anti => flv%anti ()) if (.not. anti%is_stable ()) & call show_unstable (cmd%local, -pdg, u) end associate end if end do case ("cuts", "weight", "scale", & "factorization_scale", "renormalization_scale", & "selection", "reweight", "analysis") call cmd%local%pn%show (cmd%name(i), u) case ("expect") call expect_summary (force = .true.) case ("intrinsic") call var_list%write (intrinsic=.true., unit=u, & pacified = pacified) case ("logical") if (associated (model_vars)) then call model_vars%write (only_type=V_LOG, & model_name = name, unit=u, pacified = pacified, & follow_link=.false.) end if call var_list%write (& only_type=V_LOG, unit=u, pacified = pacified) case ("int") if (associated (model_vars)) then call model_vars%write (only_type=V_INT, & model_name = name, unit=u, pacified = pacified, & follow_link=.false.) end if call var_list%write (only_type=V_INT, & unit=u, pacified = pacified) case ("real") if (associated (model_vars)) then call model_vars%write (only_type=V_REAL, & model_name = name, unit=u, pacified = pacified, & follow_link=.false.) end if call var_list%write (only_type=V_REAL, & unit=u, pacified = pacified) case ("complex") if (associated (model_vars)) then call model_vars%write (only_type=V_CMPLX, & model_name = name, unit=u, pacified = pacified, & follow_link=.false.) end if call var_list%write (only_type=V_CMPLX, & unit=u, pacified = pacified) case ("pdg") if (associated (model_vars)) then call model_vars%write (only_type=V_PDG, & model_name = name, unit=u, pacified = pacified, & follow_link=.false.) end if call var_list%write (only_type=V_PDG, & unit=u, pacified = pacified) case ("string") if (associated (model_vars)) then call model_vars%write (only_type=V_STR, & model_name = name, unit=u, pacified = pacified, & follow_link=.false.) end if call var_list%write (only_type=V_STR, & unit=u, pacified = pacified) case default if (analysis_exists (cmd%name(i))) then call analysis_write (cmd%name(i), u) else if (cmd%local%process_stack%exists (cmd%name(i))) then process => cmd%local%process_stack%get_process_ptr (cmd%name(i)) call process%show (u) else if (associated (cmd%local%prclib_stack%get_library_ptr & (cmd%name(i)))) then prc_lib => cmd%local%prclib_stack%get_library_ptr (cmd%name(i)) call prc_lib%show (u) else if (associated (model_vars)) then if (model_vars%contains (cmd%name(i), follow_link=.false.)) then call model_vars%write_var (cmd%name(i), & unit = u, model_name = name, pacified = pacified) else if (var_list%contains (cmd%name(i))) then call var_list%write_var (cmd%name(i), & unit = u, pacified = pacified) else call msg_error ("show: object '" // char (cmd%name(i)) & // "' not found") end if else if (var_list%contains (cmd%name(i))) then call var_list%write_var (cmd%name(i), & unit = u, pacified = pacified) else call msg_error ("show: object '" // char (cmd%name(i)) & // "' not found") end if end select end do end if rewind (u) u_log = logfile_unit () u_out = given_output_unit () do read (u, "(A)", end = 1) buffer if (u_log > 0) write (u_log, "(A)") trim (buffer) if (u_out > 0) write (u_out, "(A)") trim (buffer) if (u_ext > 0) write (u_ext, "(A)") trim (buffer) end do 1 close (u) if (u_log > 0) flush (u_log) if (u_out > 0) flush (u_out) if (u_ext > 0) flush (u_ext) end subroutine cmd_show_execute @ %def cmd_show_execute @ \subsubsection{Clear values} This command clears the current values of variables or other objects, where this makes sense. It parallels the [[show]] command. The objects are cleared, but not deleted. <>= type, extends (command_t) :: cmd_clear_t private type(string_t), dimension(:), allocatable :: name contains <> end type cmd_clear_t @ %def cmd_clear_t @ Output: list the names of the objects to be cleared. <>= procedure :: write => cmd_clear_write <>= subroutine cmd_clear_write (cmd, unit, indent) class(cmd_clear_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "clear: " if (allocated (cmd%name)) then do i = 1, size (cmd%name) write (u, "(1x,A)", advance="no") char (cmd%name(i)) end do write (u, *) else write (u, "(5x,A)") "[undefined]" end if end subroutine cmd_clear_write @ %def cmd_clear_write @ Compile. Allocate an array which is filled with the names of the objects to be cleared. Note: there is currently no need to account for options, but we prepare for that possibility. <>= procedure :: compile => cmd_clear_compile <>= subroutine cmd_clear_compile (cmd, global) class(cmd_clear_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_var, pn_prefix, pn_name type(string_t) :: key integer :: i, n_args pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) if (associated (pn_arg)) then select case (char (parse_node_get_rule_key (pn_arg))) case ("clear_arg") cmd%pn_opt => parse_node_get_next_ptr (pn_arg) case default cmd%pn_opt => pn_arg pn_arg => null () end select end if call cmd%compile_options (global) if (associated (pn_arg)) then n_args = parse_node_get_n_sub (pn_arg) allocate (cmd%name (n_args)) pn_var => parse_node_get_sub_ptr (pn_arg) i = 0 do while (associated (pn_var)) i = i + 1 select case (char (parse_node_get_rule_key (pn_var))) case ("beams", "iterations", & "cuts", "weight", & "scale", "factorization_scale", "renormalization_scale", & "selection", "reweight", "analysis", & "unstable", "polarized", & "expect") cmd%name(i) = parse_node_get_key (pn_var) case ("log_var", "string_var") pn_prefix => parse_node_get_sub_ptr (pn_var) pn_name => parse_node_get_next_ptr (pn_prefix) key = parse_node_get_key (pn_prefix) if (associated (pn_name)) then select case (char (parse_node_get_rule_key (pn_name))) case ("var_name") select case (char (key)) case ("?", "$") ! $ sign cmd%name(i) = key // parse_node_get_string (pn_name) end select case default call parse_node_mismatch & ("var_name", pn_name) end select else cmd%name(i) = key end if case default cmd%name(i) = parse_node_get_string (pn_var) end select pn_var => parse_node_get_next_ptr (pn_var) end do else allocate (cmd%name (0)) end if end subroutine cmd_clear_compile @ %def cmd_clear_compile @ Execute. Scan the list of objects to clear. Objects that can be shown but not cleared: model, library, results <>= procedure :: execute => cmd_clear_execute <>= subroutine cmd_clear_execute (cmd, global) class(cmd_clear_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global integer :: i logical :: success type(var_list_t), pointer :: model_vars if (size (cmd%name) == 0) then call msg_warning ("clear: no object specified") else do i = 1, size (cmd%name) success = .true. select case (char (cmd%name(i))) case ("beams") call cmd%local%clear_beams () case ("iterations") call cmd%local%it_list%clear () case ("polarized") call cmd%local%model%clear_polarized () case ("unstable") call cmd%local%model%clear_unstable () case ("cuts", "weight", "scale", & "factorization_scale", "renormalization_scale", & "selection", "reweight", "analysis") call cmd%local%pn%clear (cmd%name(i)) case ("expect") call expect_clear () case default if (analysis_exists (cmd%name(i))) then call analysis_clear (cmd%name(i)) else if (cmd%local%var_list%contains (cmd%name(i))) then if (.not. cmd%local%var_list%is_locked (cmd%name(i))) then call cmd%local%var_list%unset (cmd%name(i)) else call msg_error ("clear: variable '" // char (cmd%name(i)) & // "' is locked and can't be cleared") success = .false. end if else if (associated (cmd%local%model)) then model_vars => cmd%local%model%get_var_list_ptr () if (model_vars%contains (cmd%name(i), follow_link=.false.)) then call msg_error ("clear: variable '" // char (cmd%name(i)) & // "' is a model variable and can't be cleared") else call msg_error ("clear: object '" // char (cmd%name(i)) & // "' not found") end if success = .false. else call msg_error ("clear: object '" // char (cmd%name(i)) & // "' not found") success = .false. end if end select if (success) call msg_message ("cleared: " // char (cmd%name(i))) end do end if end subroutine cmd_clear_execute @ %def cmd_clear_execute @ \subsubsection{Compare values of variables to expectation} The implementation is similar to the [[show]] command. There are just two arguments: two values that should be compared. For providing local values for the numerical tolerance, the command has a local argument list. If the expectation fails, an error condition is recorded. <>= type, extends (command_t) :: cmd_expect_t private type(parse_node_t), pointer :: pn_lexpr => null () contains <> end type cmd_expect_t @ %def cmd_expect_t @ Simply tell the status. <>= procedure :: write => cmd_expect_write <>= subroutine cmd_expect_write (cmd, unit, indent) class(cmd_expect_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) if (associated (cmd%pn_lexpr)) then write (u, "(1x,A)") "expect: [expression associated]" else write (u, "(1x,A)") "expect: [undefined]" end if end subroutine cmd_expect_write @ %def cmd_expect_write @ Compile. This merely assigns the parse node, the actual compilation is done at execution. This is necessary because the origin of variables (local/global) may change during execution. <>= procedure :: compile => cmd_expect_compile <>= subroutine cmd_expect_compile (cmd, global) class(cmd_expect_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_arg) cmd%pn_lexpr => parse_node_get_sub_ptr (pn_arg) call cmd%compile_options (global) end subroutine cmd_expect_compile @ %def cmd_expect_compile @ Execute. Evaluate both arguments, print them and their difference (if numerical), and whether they agree. Record the result. <>= procedure :: execute => cmd_expect_execute <>= subroutine cmd_expect_execute (cmd, global) class(cmd_expect_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list logical :: success, is_known var_list => cmd%local%get_var_list_ptr () success = eval_log (cmd%pn_lexpr, var_list, is_known=is_known) if (is_known) then if (success) then call msg_message ("expect: success") else call msg_error ("expect: failure") end if else call msg_error ("expect: undefined result") success = .false. end if call expect_record (success) end subroutine cmd_expect_execute @ %def cmd_expect_execute @ \subsubsection{Beams} The beam command includes both beam and structure-function definition. <>= type, extends (command_t) :: cmd_beams_t private integer :: n_in = 0 type(parse_node_p), dimension(:), allocatable :: pn_pdg integer :: n_sf_record = 0 integer, dimension(:), allocatable :: n_entry type(parse_node_p), dimension(:,:), allocatable :: pn_sf_entry contains <> end type cmd_beams_t @ %def cmd_beams_t @ Output. The particle expressions are not resolved. <>= procedure :: write => cmd_beams_write <>= subroutine cmd_beams_write (cmd, unit, indent) class(cmd_beams_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_in) case (1) write (u, "(1x,A)") "beams: 1 [decay]" case (2) write (u, "(1x,A)") "beams: 2 [scattering]" case default write (u, "(1x,A)") "beams: [undefined]" end select if (allocated (cmd%n_entry)) then if (cmd%n_sf_record > 0) then write (u, "(1x,A,99(1x,I0))") "structure function entries:", & cmd%n_entry end if end if end subroutine cmd_beams_write @ %def cmd_beams_write @ Compile. Find and assign the parse nodes. Note: local environments are not yet supported. <>= procedure :: compile => cmd_beams_compile <>= subroutine cmd_beams_compile (cmd, global) class(cmd_beams_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_beam_def, pn_beam_spec type(parse_node_t), pointer :: pn_beam_list type(parse_node_t), pointer :: pn_codes type(parse_node_t), pointer :: pn_strfun_seq, pn_strfun_pair type(parse_node_t), pointer :: pn_strfun_def integer :: i pn_beam_def => parse_node_get_sub_ptr (cmd%pn, 3) pn_beam_spec => parse_node_get_sub_ptr (pn_beam_def) pn_strfun_seq => parse_node_get_next_ptr (pn_beam_spec) pn_beam_list => parse_node_get_sub_ptr (pn_beam_spec) call cmd%compile_options (global) cmd%n_in = parse_node_get_n_sub (pn_beam_list) allocate (cmd%pn_pdg (cmd%n_in)) pn_codes => parse_node_get_sub_ptr (pn_beam_list) do i = 1, cmd%n_in cmd%pn_pdg(i)%ptr => pn_codes pn_codes => parse_node_get_next_ptr (pn_codes) end do if (associated (pn_strfun_seq)) then cmd%n_sf_record = parse_node_get_n_sub (pn_beam_def) - 1 allocate (cmd%n_entry (cmd%n_sf_record), source = 1) allocate (cmd%pn_sf_entry (2, cmd%n_sf_record)) do i = 1, cmd%n_sf_record pn_strfun_pair => parse_node_get_sub_ptr (pn_strfun_seq, 2) pn_strfun_def => parse_node_get_sub_ptr (pn_strfun_pair) cmd%pn_sf_entry(1,i)%ptr => pn_strfun_def pn_strfun_def => parse_node_get_next_ptr (pn_strfun_def) cmd%pn_sf_entry(2,i)%ptr => pn_strfun_def if (associated (pn_strfun_def)) cmd%n_entry(i) = 2 pn_strfun_seq => parse_node_get_next_ptr (pn_strfun_seq) end do else allocate (cmd%n_entry (0)) allocate (cmd%pn_sf_entry (0, 0)) end if end subroutine cmd_beams_compile @ %def cmd_beams_compile @ Command execution: Determine beam particles and structure-function names, if any. The results are stored in the [[beam_structure]] component of the [[global]] data block. <>= procedure :: execute => cmd_beams_execute <>= subroutine cmd_beams_execute (cmd, global) class(cmd_beams_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(pdg_array_t) :: pdg_array integer, dimension(:), allocatable :: pdg type(flavor_t), dimension(:), allocatable :: flv type(parse_node_t), pointer :: pn_key type(string_t) :: sf_name integer :: i, j call lhapdf_global_reset () var_list => cmd%local%get_var_list_ptr () allocate (flv (cmd%n_in)) do i = 1, cmd%n_in pdg_array = eval_pdg_array (cmd%pn_pdg(i)%ptr, var_list) pdg = pdg_array select case (size (pdg)) case (1) call flv(i)%init ( pdg(1), cmd%local%model) case default call msg_fatal ("Beams: beam particles must be unique") end select end do select case (cmd%n_in) case (1) if (cmd%n_sf_record > 0) then call msg_fatal ("Beam setup: no structure functions allowed & &for decay") end if call global%beam_structure%init_sf (flv%get_name ()) case (2) call global%beam_structure%init_sf (flv%get_name (), cmd%n_entry) do i = 1, cmd%n_sf_record do j = 1, cmd%n_entry(i) pn_key => parse_node_get_sub_ptr (cmd%pn_sf_entry(j,i)%ptr) sf_name = parse_node_get_key (pn_key) call global%beam_structure%set_sf (i, j, sf_name) end do end do end select end subroutine cmd_beams_execute @ %def cmd_beams_execute @ \subsubsection{Density matrices for beam polarization} For holding beam polarization, we define a notation and a data structure for sparse matrices. The entries (and the index expressions) are numerical expressions, so we use evaluation trees. Each entry in the sparse matrix is an n-tuple of expressions. The first tuple elements represent index values, the last one is an arbitrary (complex) number. Absent expressions are replaced by default-value rules. Note: Here, and in some other commands, we would like to store an evaluation tree, not just a parse node pointer. However, the current expression handler wants all variables defined, so the evaluation tree can only be built by [[evaluate]], i.e., compiled just-in-time and evaluated immediately. <>= type :: sentry_expr_t type(parse_node_p), dimension(:), allocatable :: expr contains <> end type sentry_expr_t @ %def sentry_expr_t @ Compile parse nodes into evaluation trees. <>= procedure :: compile => sentry_expr_compile <>= subroutine sentry_expr_compile (sentry, pn) class(sentry_expr_t), intent(out) :: sentry type(parse_node_t), intent(in), target :: pn type(parse_node_t), pointer :: pn_expr, pn_extra integer :: n_expr, i n_expr = parse_node_get_n_sub (pn) allocate (sentry%expr (n_expr)) if (n_expr > 0) then i = 0 pn_expr => parse_node_get_sub_ptr (pn) pn_extra => parse_node_get_next_ptr (pn_expr) do i = 1, n_expr sentry%expr(i)%ptr => pn_expr if (associated (pn_extra)) then pn_expr => parse_node_get_sub_ptr (pn_extra, 2) pn_extra => parse_node_get_next_ptr (pn_extra) end if end do end if end subroutine sentry_expr_compile @ %def sentry_expr_compile @ Evaluate the expressions and return an index array of predefined length together with a complex value. If the value (as the last expression) is undefined, set it to unity. If index values are undefined, repeat the previous index value. <>= procedure :: evaluate => sentry_expr_evaluate <>= subroutine sentry_expr_evaluate (sentry, index, value, global) class(sentry_expr_t), intent(inout) :: sentry integer, dimension(:), intent(out) :: index complex(default), intent(out) :: value type(rt_data_t), intent(in), target :: global type(var_list_t), pointer :: var_list integer :: i, n_expr, n_index type(eval_tree_t) :: eval_tree var_list => global%get_var_list_ptr () n_expr = size (sentry%expr) n_index = size (index) if (n_expr <= n_index + 1) then do i = 1, min (n_expr, n_index) associate (expr => sentry%expr(i)) call eval_tree%init_expr (expr%ptr, var_list) call eval_tree%evaluate () if (eval_tree%is_known ()) then index(i) = eval_tree%get_int () else call msg_fatal ("Evaluating density matrix: undefined index") end if end associate end do do i = n_expr + 1, n_index index(i) = index(n_expr) end do if (n_expr == n_index + 1) then associate (expr => sentry%expr(n_expr)) call eval_tree%init_expr (expr%ptr, var_list) call eval_tree%evaluate () if (eval_tree%is_known ()) then value = eval_tree%get_cmplx () else call msg_fatal ("Evaluating density matrix: undefined index") end if call eval_tree%final () end associate else value = 1 end if else call msg_fatal ("Evaluating density matrix: index expression too long") end if end subroutine sentry_expr_evaluate @ %def sentry_expr_evaluate @ The sparse matrix itself consists of an arbitrary number of entries. <>= type :: smatrix_expr_t type(sentry_expr_t), dimension(:), allocatable :: entry contains <> end type smatrix_expr_t @ %def smatrix_expr_t @ Compile: assign sub-nodes to sentry-expressions and compile those. <>= procedure :: compile => smatrix_expr_compile <>= subroutine smatrix_expr_compile (smatrix_expr, pn) class(smatrix_expr_t), intent(out) :: smatrix_expr type(parse_node_t), intent(in), target :: pn type(parse_node_t), pointer :: pn_arg, pn_entry integer :: n_entry, i pn_arg => parse_node_get_sub_ptr (pn, 2) if (associated (pn_arg)) then n_entry = parse_node_get_n_sub (pn_arg) allocate (smatrix_expr%entry (n_entry)) pn_entry => parse_node_get_sub_ptr (pn_arg) do i = 1, n_entry call smatrix_expr%entry(i)%compile (pn_entry) pn_entry => parse_node_get_next_ptr (pn_entry) end do else allocate (smatrix_expr%entry (0)) end if end subroutine smatrix_expr_compile @ %def smatrix_expr_compile @ Evaluate the entries and build a new [[smatrix]] object, which contains just the numerical results. <>= procedure :: evaluate => smatrix_expr_evaluate <>= subroutine smatrix_expr_evaluate (smatrix_expr, smatrix, global) class(smatrix_expr_t), intent(inout) :: smatrix_expr type(smatrix_t), intent(out) :: smatrix type(rt_data_t), intent(in), target :: global integer, dimension(2) :: idx complex(default) :: value integer :: i, n_entry n_entry = size (smatrix_expr%entry) call smatrix%init (2, n_entry) do i = 1, n_entry call smatrix_expr%entry(i)%evaluate (idx, value, global) call smatrix%set_entry (i, idx, value) end do end subroutine smatrix_expr_evaluate @ %def smatrix_expr_evaluate @ \subsubsection{Beam polarization density} The beam polarization command defines spin density matrix for one or two beams (scattering or decay). <>= type, extends (command_t) :: cmd_beams_pol_density_t private integer :: n_in = 0 type(smatrix_expr_t), dimension(:), allocatable :: smatrix contains <> end type cmd_beams_pol_density_t @ %def cmd_beams_pol_density_t @ Output. <>= procedure :: write => cmd_beams_pol_density_write <>= subroutine cmd_beams_pol_density_write (cmd, unit, indent) class(cmd_beams_pol_density_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_in) case (1) write (u, "(1x,A)") "beams polarization setup: 1 [decay]" case (2) write (u, "(1x,A)") "beams polarization setup: 2 [scattering]" case default write (u, "(1x,A)") "beams polarization setup: [undefined]" end select end subroutine cmd_beams_pol_density_write @ %def cmd_beams_pol_density_write @ Compile. Find and assign the parse nodes. Note: local environments are not yet supported. <>= procedure :: compile => cmd_beams_pol_density_compile <>= subroutine cmd_beams_pol_density_compile (cmd, global) class(cmd_beams_pol_density_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_pol_spec, pn_smatrix integer :: i pn_pol_spec => parse_node_get_sub_ptr (cmd%pn, 3) call cmd%compile_options (global) cmd%n_in = parse_node_get_n_sub (pn_pol_spec) allocate (cmd%smatrix (cmd%n_in)) pn_smatrix => parse_node_get_sub_ptr (pn_pol_spec) do i = 1, cmd%n_in call cmd%smatrix(i)%compile (pn_smatrix) pn_smatrix => parse_node_get_next_ptr (pn_smatrix) end do end subroutine cmd_beams_pol_density_compile @ %def cmd_beams_pol_density_compile @ Command execution: Fill polarization density matrices. No check yet, the matrices are checked and normalized when the actual beam object is created, just before integration. For intermediate storage, we use the [[beam_structure]] object in the [[global]] data set. <>= procedure :: execute => cmd_beams_pol_density_execute <>= subroutine cmd_beams_pol_density_execute (cmd, global) class(cmd_beams_pol_density_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(smatrix_t) :: smatrix integer :: i call global%beam_structure%init_pol (cmd%n_in) do i = 1, cmd%n_in call cmd%smatrix(i)%evaluate (smatrix, global) call global%beam_structure%set_smatrix (i, smatrix) end do end subroutine cmd_beams_pol_density_execute @ %def cmd_beams_pol_density_execute @ \subsubsection{Beam polarization fraction} In addition to the polarization density matrix, we can independently specify the polarization fraction for one or both beams. <>= type, extends (command_t) :: cmd_beams_pol_fraction_t private integer :: n_in = 0 type(parse_node_p), dimension(:), allocatable :: expr contains <> end type cmd_beams_pol_fraction_t @ %def cmd_beams_pol_fraction_t @ Output. <>= procedure :: write => cmd_beams_pol_fraction_write <>= subroutine cmd_beams_pol_fraction_write (cmd, unit, indent) class(cmd_beams_pol_fraction_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_in) case (1) write (u, "(1x,A)") "beams polarization fraction: 1 [decay]" case (2) write (u, "(1x,A)") "beams polarization fraction: 2 [scattering]" case default write (u, "(1x,A)") "beams polarization fraction: [undefined]" end select end subroutine cmd_beams_pol_fraction_write @ %def cmd_beams_pol_fraction_write @ Compile. Find and assign the parse nodes. Note: local environments are not yet supported. <>= procedure :: compile => cmd_beams_pol_fraction_compile <>= subroutine cmd_beams_pol_fraction_compile (cmd, global) class(cmd_beams_pol_fraction_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_frac_spec, pn_expr integer :: i pn_frac_spec => parse_node_get_sub_ptr (cmd%pn, 3) call cmd%compile_options (global) cmd%n_in = parse_node_get_n_sub (pn_frac_spec) allocate (cmd%expr (cmd%n_in)) pn_expr => parse_node_get_sub_ptr (pn_frac_spec) do i = 1, cmd%n_in cmd%expr(i)%ptr => pn_expr pn_expr => parse_node_get_next_ptr (pn_expr) end do end subroutine cmd_beams_pol_fraction_compile @ %def cmd_beams_pol_fraction_compile @ Command execution: Retrieve the numerical values of the beam polarization fractions. The results are stored in the [[beam_structure]] component of the [[global]] data block. <>= procedure :: execute => cmd_beams_pol_fraction_execute <>= subroutine cmd_beams_pol_fraction_execute (cmd, global) class(cmd_beams_pol_fraction_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list real(default), dimension(:), allocatable :: pol_f type(eval_tree_t) :: expr integer :: i var_list => global%get_var_list_ptr () allocate (pol_f (cmd%n_in)) do i = 1, cmd%n_in call expr%init_expr (cmd%expr(i)%ptr, var_list) call expr%evaluate () if (expr%is_known ()) then pol_f(i) = expr%get_real () else call msg_fatal ("beams polarization fraction: undefined value") end if call expr%final () end do call global%beam_structure%set_pol_f (pol_f) end subroutine cmd_beams_pol_fraction_execute @ %def cmd_beams_pol_fraction_execute @ \subsubsection{Beam momentum} This is completely analogous to the previous command, hence we can use inheritance. <>= type, extends (cmd_beams_pol_fraction_t) :: cmd_beams_momentum_t contains <> end type cmd_beams_momentum_t @ %def cmd_beams_momentum_t @ Output. <>= procedure :: write => cmd_beams_momentum_write <>= subroutine cmd_beams_momentum_write (cmd, unit, indent) class(cmd_beams_momentum_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_in) case (1) write (u, "(1x,A)") "beams momentum: 1 [decay]" case (2) write (u, "(1x,A)") "beams momentum: 2 [scattering]" case default write (u, "(1x,A)") "beams momentum: [undefined]" end select end subroutine cmd_beams_momentum_write @ %def cmd_beams_momentum_write @ Compile: inherited. Command execution: Not inherited, but just the error string and the final command are changed. <>= procedure :: execute => cmd_beams_momentum_execute <>= subroutine cmd_beams_momentum_execute (cmd, global) class(cmd_beams_momentum_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list real(default), dimension(:), allocatable :: p type(eval_tree_t) :: expr integer :: i var_list => global%get_var_list_ptr () allocate (p (cmd%n_in)) do i = 1, cmd%n_in call expr%init_expr (cmd%expr(i)%ptr, var_list) call expr%evaluate () if (expr%is_known ()) then p(i) = expr%get_real () else call msg_fatal ("beams momentum: undefined value") end if call expr%final () end do call global%beam_structure%set_momentum (p) end subroutine cmd_beams_momentum_execute @ %def cmd_beams_momentum_execute @ \subsubsection{Beam angles} Again, this is analogous. There are two angles, polar angle $\theta$ and azimuthal angle $\phi$, which can be set independently for both beams. <>= type, extends (cmd_beams_pol_fraction_t) :: cmd_beams_theta_t contains <> end type cmd_beams_theta_t type, extends (cmd_beams_pol_fraction_t) :: cmd_beams_phi_t contains <> end type cmd_beams_phi_t @ %def cmd_beams_theta_t @ %def cmd_beams_phi_t @ Output. <>= procedure :: write => cmd_beams_theta_write <>= procedure :: write => cmd_beams_phi_write <>= subroutine cmd_beams_theta_write (cmd, unit, indent) class(cmd_beams_theta_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_in) case (1) write (u, "(1x,A)") "beams theta: 1 [decay]" case (2) write (u, "(1x,A)") "beams theta: 2 [scattering]" case default write (u, "(1x,A)") "beams theta: [undefined]" end select end subroutine cmd_beams_theta_write subroutine cmd_beams_phi_write (cmd, unit, indent) class(cmd_beams_phi_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_in) case (1) write (u, "(1x,A)") "beams phi: 1 [decay]" case (2) write (u, "(1x,A)") "beams phi: 2 [scattering]" case default write (u, "(1x,A)") "beams phi: [undefined]" end select end subroutine cmd_beams_phi_write @ %def cmd_beams_theta_write @ %def cmd_beams_phi_write @ Compile: inherited. Command execution: Not inherited, but just the error string and the final command are changed. <>= procedure :: execute => cmd_beams_theta_execute <>= procedure :: execute => cmd_beams_phi_execute <>= subroutine cmd_beams_theta_execute (cmd, global) class(cmd_beams_theta_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list real(default), dimension(:), allocatable :: theta type(eval_tree_t) :: expr integer :: i var_list => global%get_var_list_ptr () allocate (theta (cmd%n_in)) do i = 1, cmd%n_in call expr%init_expr (cmd%expr(i)%ptr, var_list) call expr%evaluate () if (expr%is_known ()) then theta(i) = expr%get_real () else call msg_fatal ("beams theta: undefined value") end if call expr%final () end do call global%beam_structure%set_theta (theta) end subroutine cmd_beams_theta_execute subroutine cmd_beams_phi_execute (cmd, global) class(cmd_beams_phi_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list real(default), dimension(:), allocatable :: phi type(eval_tree_t) :: expr integer :: i var_list => global%get_var_list_ptr () allocate (phi (cmd%n_in)) do i = 1, cmd%n_in call expr%init_expr (cmd%expr(i)%ptr, var_list) call expr%evaluate () if (expr%is_known ()) then phi(i) = expr%get_real () else call msg_fatal ("beams phi: undefined value") end if call expr%final () end do call global%beam_structure%set_phi (phi) end subroutine cmd_beams_phi_execute @ %def cmd_beams_theta_execute @ %def cmd_beams_phi_execute @ \subsubsection{Cuts} Define a cut expression. We store the parse tree for the right-hand side instead of compiling it. Compilation is deferred to the process environment where the cut expression is used. <>= type, extends (command_t) :: cmd_cuts_t private type(parse_node_t), pointer :: pn_lexpr => null () contains <> end type cmd_cuts_t @ %def cmd_cuts_t @ Output. Do not print the parse tree, since this may get cluttered. Just a message that cuts have been defined. <>= procedure :: write => cmd_cuts_write <>= subroutine cmd_cuts_write (cmd, unit, indent) class(cmd_cuts_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "cuts: [defined]" end subroutine cmd_cuts_write @ %def cmd_cuts_write @ Compile. Simply store the parse (sub)tree. <>= procedure :: compile => cmd_cuts_compile <>= subroutine cmd_cuts_compile (cmd, global) class(cmd_cuts_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_lexpr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_cuts_compile @ %def cmd_cuts_compile @ Instead of evaluating the cut expression, link the parse tree to the global data set, such that it is compiled and executed in the appropriate process context. <>= procedure :: execute => cmd_cuts_execute <>= subroutine cmd_cuts_execute (cmd, global) class(cmd_cuts_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%cuts_lexpr => cmd%pn_lexpr end subroutine cmd_cuts_execute @ %def cmd_cuts_execute @ \subsubsection{General, Factorization and Renormalization Scales} Define a scale expression for either the renormalization or the factorization scale. We store the parse tree for the right-hand side instead of compiling it. Compilation is deferred to the process environment where the expression is used. <>= type, extends (command_t) :: cmd_scale_t private type(parse_node_t), pointer :: pn_expr => null () contains <> end type cmd_scale_t @ %def cmd_scale_t <>= type, extends (command_t) :: cmd_fac_scale_t private type(parse_node_t), pointer :: pn_expr => null () contains <> end type cmd_fac_scale_t @ %def cmd_fac_scale_t <>= type, extends (command_t) :: cmd_ren_scale_t private type(parse_node_t), pointer :: pn_expr => null () contains <> end type cmd_ren_scale_t @ %def cmd_ren_scale_t @ Output. Do not print the parse tree, since this may get cluttered. Just a message that scale, renormalization and factorization have been defined, respectively. <>= procedure :: write => cmd_scale_write <>= subroutine cmd_scale_write (cmd, unit, indent) class(cmd_scale_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "scale: [defined]" end subroutine cmd_scale_write @ %def cmd_scale_write @ <>= procedure :: write => cmd_fac_scale_write <>= subroutine cmd_fac_scale_write (cmd, unit, indent) class(cmd_fac_scale_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "factorization scale: [defined]" end subroutine cmd_fac_scale_write @ %def cmd_fac_scale_write @ <>= procedure :: write => cmd_ren_scale_write <>= subroutine cmd_ren_scale_write (cmd, unit, indent) class(cmd_ren_scale_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "renormalization scale: [defined]" end subroutine cmd_ren_scale_write @ %def cmd_ren_scale_write @ Compile. Simply store the parse (sub)tree. <>= procedure :: compile => cmd_scale_compile <>= subroutine cmd_scale_compile (cmd, global) class(cmd_scale_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_expr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_scale_compile @ %def cmd_scale_compile @ <>= procedure :: compile => cmd_fac_scale_compile <>= subroutine cmd_fac_scale_compile (cmd, global) class(cmd_fac_scale_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_expr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_fac_scale_compile @ %def cmd_fac_scale_compile @ <>= procedure :: compile => cmd_ren_scale_compile <>= subroutine cmd_ren_scale_compile (cmd, global) class(cmd_ren_scale_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_expr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_ren_scale_compile @ %def cmd_ren_scale_compile @ Instead of evaluating the scale expression, link the parse tree to the global data set, such that it is compiled and executed in the appropriate process context. <>= procedure :: execute => cmd_scale_execute <>= subroutine cmd_scale_execute (cmd, global) class(cmd_scale_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%scale_expr => cmd%pn_expr end subroutine cmd_scale_execute @ %def cmd_scale_execute @ <>= procedure :: execute => cmd_fac_scale_execute <>= subroutine cmd_fac_scale_execute (cmd, global) class(cmd_fac_scale_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%fac_scale_expr => cmd%pn_expr end subroutine cmd_fac_scale_execute @ %def cmd_fac_scale_execute @ <>= procedure :: execute => cmd_ren_scale_execute <>= subroutine cmd_ren_scale_execute (cmd, global) class(cmd_ren_scale_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%ren_scale_expr => cmd%pn_expr end subroutine cmd_ren_scale_execute @ %def cmd_ren_scale_execute @ \subsubsection{Weight} Define a weight expression. The weight is applied to a process to be integrated, event by event. We store the parse tree for the right-hand side instead of compiling it. Compilation is deferred to the process environment where the expression is used. <>= type, extends (command_t) :: cmd_weight_t private type(parse_node_t), pointer :: pn_expr => null () contains <> end type cmd_weight_t @ %def cmd_weight_t @ Output. Do not print the parse tree, since this may get cluttered. Just a message that scale, renormalization and factorization have been defined, respectively. <>= procedure :: write => cmd_weight_write <>= subroutine cmd_weight_write (cmd, unit, indent) class(cmd_weight_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "weight expression: [defined]" end subroutine cmd_weight_write @ %def cmd_weight_write @ Compile. Simply store the parse (sub)tree. <>= procedure :: compile => cmd_weight_compile <>= subroutine cmd_weight_compile (cmd, global) class(cmd_weight_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_expr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_weight_compile @ %def cmd_weight_compile @ Instead of evaluating the expression, link the parse tree to the global data set, such that it is compiled and executed in the appropriate process context. <>= procedure :: execute => cmd_weight_execute <>= subroutine cmd_weight_execute (cmd, global) class(cmd_weight_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%weight_expr => cmd%pn_expr end subroutine cmd_weight_execute @ %def cmd_weight_execute @ \subsubsection{Selection} Define a selection expression. This is to be applied upon simulation or event-file rescanning, event by event. We store the parse tree for the right-hand side instead of compiling it. Compilation is deferred to the environment where the expression is used. <>= type, extends (command_t) :: cmd_selection_t private type(parse_node_t), pointer :: pn_expr => null () contains <> end type cmd_selection_t @ %def cmd_selection_t @ Output. Do not print the parse tree, since this may get cluttered. Just a message that scale, renormalization and factorization have been defined, respectively. <>= procedure :: write => cmd_selection_write <>= subroutine cmd_selection_write (cmd, unit, indent) class(cmd_selection_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "selection expression: [defined]" end subroutine cmd_selection_write @ %def cmd_selection_write @ Compile. Simply store the parse (sub)tree. <>= procedure :: compile => cmd_selection_compile <>= subroutine cmd_selection_compile (cmd, global) class(cmd_selection_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_expr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_selection_compile @ %def cmd_selection_compile @ Instead of evaluating the expression, link the parse tree to the global data set, such that it is compiled and executed in the appropriate process context. <>= procedure :: execute => cmd_selection_execute <>= subroutine cmd_selection_execute (cmd, global) class(cmd_selection_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%selection_lexpr => cmd%pn_expr end subroutine cmd_selection_execute @ %def cmd_selection_execute @ \subsubsection{Reweight} Define a reweight expression. This is to be applied upon simulation or event-file rescanning, event by event. We store the parse tree for the right-hand side instead of compiling it. Compilation is deferred to the environment where the expression is used. <>= type, extends (command_t) :: cmd_reweight_t private type(parse_node_t), pointer :: pn_expr => null () contains <> end type cmd_reweight_t @ %def cmd_reweight_t @ Output. Do not print the parse tree, since this may get cluttered. Just a message that scale, renormalization and factorization have been defined, respectively. <>= procedure :: write => cmd_reweight_write <>= subroutine cmd_reweight_write (cmd, unit, indent) class(cmd_reweight_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "reweight expression: [defined]" end subroutine cmd_reweight_write @ %def cmd_reweight_write @ Compile. Simply store the parse (sub)tree. <>= procedure :: compile => cmd_reweight_compile <>= subroutine cmd_reweight_compile (cmd, global) class(cmd_reweight_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_expr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_reweight_compile @ %def cmd_reweight_compile @ Instead of evaluating the expression, link the parse tree to the global data set, such that it is compiled and executed in the appropriate process context. <>= procedure :: execute => cmd_reweight_execute <>= subroutine cmd_reweight_execute (cmd, global) class(cmd_reweight_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%reweight_expr => cmd%pn_expr end subroutine cmd_reweight_execute @ %def cmd_reweight_execute @ \subsubsection{Alternative Simulation Setups} Together with simulation, we can re-evaluate event weights in the context of alternative setups. The [[cmd_alt_setup_t]] object is designed to hold these setups, which are brace-enclosed command lists. Compilation is deferred to the simulation environment where the setup expression is used. <>= type, extends (command_t) :: cmd_alt_setup_t private type(parse_node_p), dimension(:), allocatable :: setup contains <> end type cmd_alt_setup_t @ %def cmd_alt_setup_t @ Output. Print just a message that the alternative setup list has been defined. <>= procedure :: write => cmd_alt_setup_write <>= subroutine cmd_alt_setup_write (cmd, unit, indent) class(cmd_alt_setup_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,I0,A)") "alt_setup: ", size (cmd%setup), " entries" end subroutine cmd_alt_setup_write @ %def cmd_alt_setup_write @ Compile. Store the parse sub-trees in an array. <>= procedure :: compile => cmd_alt_setup_compile <>= subroutine cmd_alt_setup_compile (cmd, global) class(cmd_alt_setup_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_list, pn_setup integer :: i pn_list => parse_node_get_sub_ptr (cmd%pn, 3) if (associated (pn_list)) then allocate (cmd%setup (parse_node_get_n_sub (pn_list))) i = 1 pn_setup => parse_node_get_sub_ptr (pn_list) do while (associated (pn_setup)) cmd%setup(i)%ptr => pn_setup i = i + 1 pn_setup => parse_node_get_next_ptr (pn_setup) end do else allocate (cmd%setup (0)) end if end subroutine cmd_alt_setup_compile @ %def cmd_alt_setup_compile @ Execute. Transfer the array of command lists to the global environment. <>= procedure :: execute => cmd_alt_setup_execute <>= subroutine cmd_alt_setup_execute (cmd, global) class(cmd_alt_setup_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (allocated (global%pn%alt_setup)) deallocate (global%pn%alt_setup) allocate (global%pn%alt_setup (size (cmd%setup))) global%pn%alt_setup = cmd%setup end subroutine cmd_alt_setup_execute @ %def cmd_alt_setup_execute @ \subsubsection{Integration} Integrate several processes, consecutively with identical parameters. <>= type, extends (command_t) :: cmd_integrate_t private integer :: n_proc = 0 type(string_t), dimension(:), allocatable :: process_id contains <> end type cmd_integrate_t @ %def cmd_integrate_t @ Output: we know the process IDs. <>= procedure :: write => cmd_integrate_write <>= subroutine cmd_integrate_write (cmd, unit, indent) class(cmd_integrate_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "integrate (" do i = 1, cmd%n_proc if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "(A)", advance="no") char (cmd%process_id(i)) end do write (u, "(A)") ")" end subroutine cmd_integrate_write @ %def cmd_integrate_write @ Compile. <>= procedure :: compile => cmd_integrate_compile <>= subroutine cmd_integrate_compile (cmd, global) class(cmd_integrate_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_proclist, pn_proc integer :: i pn_proclist => parse_node_get_sub_ptr (cmd%pn, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_proclist) call cmd%compile_options (global) cmd%n_proc = parse_node_get_n_sub (pn_proclist) allocate (cmd%process_id (cmd%n_proc)) pn_proc => parse_node_get_sub_ptr (pn_proclist) do i = 1, cmd%n_proc cmd%process_id(i) = parse_node_get_string (pn_proc) call global%process_stack%init_result_vars (cmd%process_id(i)) pn_proc => parse_node_get_next_ptr (pn_proc) end do end subroutine cmd_integrate_compile @ %def cmd_integrate_compile @ Command execution. Integrate the process(es) with the predefined number of passes, iterations and calls. For structure functions, cuts, weight and scale, use local definitions if present; by default, the local definitions are initialized with the global ones. The [[integrate]] procedure should take its input from the currently active local environment, but produce a process record in the stack of the global environment. Since the process acquires a snapshot of the variable list, so if the global list (or the local one) is deleted, this does no harm. This implies that later changes of the variable list do not affect the stored process. <>= procedure :: execute => cmd_integrate_execute <>= subroutine cmd_integrate_execute (cmd, global) class(cmd_integrate_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global integer :: i if (debug_on) call msg_debug (D_CORE, "cmd_integrate_execute") do i = 1, cmd%n_proc if (debug_on) call msg_debug (D_CORE, "cmd%process_id(i) ", cmd%process_id(i)) call integrate_process (cmd%process_id(i), cmd%local, global) call global%process_stack%fill_result_vars (cmd%process_id(i)) call global%process_stack%update_result_vars & (cmd%process_id(i), global%var_list) if (signal_is_pending ()) return end do end subroutine cmd_integrate_execute @ %def cmd_integrate_execute @ \subsubsection{Observables} Declare an observable. After the declaration, it can be used to record data, and at the end one can retrieve average and error. <>= type, extends (command_t) :: cmd_observable_t private type(string_t) :: id contains <> end type cmd_observable_t @ %def cmd_observable_t @ Output. We know the ID. <>= procedure :: write => cmd_observable_write <>= subroutine cmd_observable_write (cmd, unit, indent) class(cmd_observable_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A)") "observable: ", char (cmd%id) end subroutine cmd_observable_write @ %def cmd_observable_write @ Compile. Just record the observable ID. <>= procedure :: compile => cmd_observable_compile <>= subroutine cmd_observable_compile (cmd, global) class(cmd_observable_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_tag pn_tag => parse_node_get_sub_ptr (cmd%pn, 2) if (associated (pn_tag)) then cmd%pn_opt => parse_node_get_next_ptr (pn_tag) end if call cmd%compile_options (global) select case (char (parse_node_get_rule_key (pn_tag))) case ("analysis_id") cmd%id = parse_node_get_string (pn_tag) case default call msg_bug ("observable: name expression not implemented (yet)") end select end subroutine cmd_observable_compile @ %def cmd_observable_compile @ Command execution. This declares the observable and allocates it in the analysis store. <>= procedure :: execute => cmd_observable_execute <>= subroutine cmd_observable_execute (cmd, global) class(cmd_observable_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(graph_options_t) :: graph_options type(string_t) :: label, unit var_list => cmd%local%get_var_list_ptr () label = var_list%get_sval (var_str ("$obs_label")) unit = var_list%get_sval (var_str ("$obs_unit")) call graph_options_init (graph_options) call set_graph_options (graph_options, var_list) call analysis_init_observable (cmd%id, label, unit, graph_options) end subroutine cmd_observable_execute @ %def cmd_observable_execute @ \subsubsection{Histograms} Declare a histogram. At minimum, we have to set lower and upper bound and bin width. <>= type, extends (command_t) :: cmd_histogram_t private type(string_t) :: id type(parse_node_t), pointer :: pn_lower_bound => null () type(parse_node_t), pointer :: pn_upper_bound => null () type(parse_node_t), pointer :: pn_bin_width => null () contains <> end type cmd_histogram_t @ %def cmd_histogram_t @ Output. Just print the ID. <>= procedure :: write => cmd_histogram_write <>= subroutine cmd_histogram_write (cmd, unit, indent) class(cmd_histogram_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A)") "histogram: ", char (cmd%id) end subroutine cmd_histogram_write @ %def cmd_histogram_write @ Compile. Record the histogram ID and initialize lower, upper bound and bin width. <>= procedure :: compile => cmd_histogram_compile <>= subroutine cmd_histogram_compile (cmd, global) class(cmd_histogram_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_tag, pn_args, pn_arg1, pn_arg2, pn_arg3 character(*), parameter :: e_illegal_use = & "illegal usage of 'histogram': insufficient number of arguments" pn_tag => parse_node_get_sub_ptr (cmd%pn, 2) pn_args => parse_node_get_next_ptr (pn_tag) if (associated (pn_args)) then pn_arg1 => parse_node_get_sub_ptr (pn_args) if (.not. associated (pn_arg1)) call msg_fatal (e_illegal_use) pn_arg2 => parse_node_get_next_ptr (pn_arg1) if (.not. associated (pn_arg2)) call msg_fatal (e_illegal_use) pn_arg3 => parse_node_get_next_ptr (pn_arg2) cmd%pn_opt => parse_node_get_next_ptr (pn_args) end if call cmd%compile_options (global) select case (char (parse_node_get_rule_key (pn_tag))) case ("analysis_id") cmd%id = parse_node_get_string (pn_tag) case default call msg_bug ("histogram: name expression not implemented (yet)") end select cmd%pn_lower_bound => pn_arg1 cmd%pn_upper_bound => pn_arg2 cmd%pn_bin_width => pn_arg3 end subroutine cmd_histogram_compile @ %def cmd_histogram_compile @ Command execution. This declares the histogram and allocates it in the analysis store. <>= procedure :: execute => cmd_histogram_execute <>= subroutine cmd_histogram_execute (cmd, global) class(cmd_histogram_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list real(default) :: lower_bound, upper_bound, bin_width integer :: bin_number logical :: bin_width_is_used, normalize_bins type(string_t) :: obs_label, obs_unit type(graph_options_t) :: graph_options type(drawing_options_t) :: drawing_options var_list => cmd%local%get_var_list_ptr () lower_bound = eval_real (cmd%pn_lower_bound, var_list) upper_bound = eval_real (cmd%pn_upper_bound, var_list) if (associated (cmd%pn_bin_width)) then bin_width = eval_real (cmd%pn_bin_width, var_list) bin_width_is_used = .true. else if (var_list%is_known (var_str ("n_bins"))) then bin_number = & var_list%get_ival (var_str ("n_bins")) bin_width_is_used = .false. else call msg_error ("Cmd '" // char (cmd%id) // & "': neither bin width nor number is defined") end if normalize_bins = & var_list%get_lval (var_str ("?normalize_bins")) obs_label = & var_list%get_sval (var_str ("$obs_label")) obs_unit = & var_list%get_sval (var_str ("$obs_unit")) call graph_options_init (graph_options) call set_graph_options (graph_options, var_list) call drawing_options_init_histogram (drawing_options) call set_drawing_options (drawing_options, var_list) if (bin_width_is_used) then call analysis_init_histogram & (cmd%id, lower_bound, upper_bound, bin_width, & normalize_bins, & obs_label, obs_unit, & graph_options, drawing_options) else call analysis_init_histogram & (cmd%id, lower_bound, upper_bound, bin_number, & normalize_bins, & obs_label, obs_unit, & graph_options, drawing_options) end if end subroutine cmd_histogram_execute @ %def cmd_histogram_execute @ Set the graph options from a variable list. <>= subroutine set_graph_options (gro, var_list) type(graph_options_t), intent(inout) :: gro type(var_list_t), intent(in) :: var_list call graph_options_set (gro, title = & var_list%get_sval (var_str ("$title"))) call graph_options_set (gro, description = & var_list%get_sval (var_str ("$description"))) call graph_options_set (gro, x_label = & var_list%get_sval (var_str ("$x_label"))) call graph_options_set (gro, y_label = & var_list%get_sval (var_str ("$y_label"))) call graph_options_set (gro, width_mm = & var_list%get_ival (var_str ("graph_width_mm"))) call graph_options_set (gro, height_mm = & var_list%get_ival (var_str ("graph_height_mm"))) call graph_options_set (gro, x_log = & var_list%get_lval (var_str ("?x_log"))) call graph_options_set (gro, y_log = & var_list%get_lval (var_str ("?y_log"))) if (var_list%is_known (var_str ("x_min"))) & call graph_options_set (gro, x_min = & var_list%get_rval (var_str ("x_min"))) if (var_list%is_known (var_str ("x_max"))) & call graph_options_set (gro, x_max = & var_list%get_rval (var_str ("x_max"))) if (var_list%is_known (var_str ("y_min"))) & call graph_options_set (gro, y_min = & var_list%get_rval (var_str ("y_min"))) if (var_list%is_known (var_str ("y_max"))) & call graph_options_set (gro, y_max = & var_list%get_rval (var_str ("y_max"))) call graph_options_set (gro, gmlcode_bg = & var_list%get_sval (var_str ("$gmlcode_bg"))) call graph_options_set (gro, gmlcode_fg = & var_list%get_sval (var_str ("$gmlcode_fg"))) end subroutine set_graph_options @ %def set_graph_options @ Set the drawing options from a variable list. <>= subroutine set_drawing_options (dro, var_list) type(drawing_options_t), intent(inout) :: dro type(var_list_t), intent(in) :: var_list if (var_list%is_known (var_str ("?draw_histogram"))) then if (var_list%get_lval (var_str ("?draw_histogram"))) then call drawing_options_set (dro, with_hbars = .true.) else call drawing_options_set (dro, with_hbars = .false., & with_base = .false., fill = .false., piecewise = .false.) end if end if if (var_list%is_known (var_str ("?draw_base"))) then if (var_list%get_lval (var_str ("?draw_base"))) then call drawing_options_set (dro, with_base = .true.) else call drawing_options_set (dro, with_base = .false., fill = .false.) end if end if if (var_list%is_known (var_str ("?draw_piecewise"))) then if (var_list%get_lval (var_str ("?draw_piecewise"))) then call drawing_options_set (dro, piecewise = .true.) else call drawing_options_set (dro, piecewise = .false.) end if end if if (var_list%is_known (var_str ("?fill_curve"))) then if (var_list%get_lval (var_str ("?fill_curve"))) then call drawing_options_set (dro, fill = .true., with_base = .true.) else call drawing_options_set (dro, fill = .false.) end if end if if (var_list%is_known (var_str ("?draw_curve"))) then if (var_list%get_lval (var_str ("?draw_curve"))) then call drawing_options_set (dro, draw = .true.) else call drawing_options_set (dro, draw = .false.) end if end if if (var_list%is_known (var_str ("?draw_errors"))) then if (var_list%get_lval (var_str ("?draw_errors"))) then call drawing_options_set (dro, err = .true.) else call drawing_options_set (dro, err = .false.) end if end if if (var_list%is_known (var_str ("?draw_symbols"))) then if (var_list%get_lval (var_str ("?draw_symbols"))) then call drawing_options_set (dro, symbols = .true.) else call drawing_options_set (dro, symbols = .false.) end if end if if (var_list%is_known (var_str ("$fill_options"))) then call drawing_options_set (dro, fill_options = & var_list%get_sval (var_str ("$fill_options"))) end if if (var_list%is_known (var_str ("$draw_options"))) then call drawing_options_set (dro, draw_options = & var_list%get_sval (var_str ("$draw_options"))) end if if (var_list%is_known (var_str ("$err_options"))) then call drawing_options_set (dro, err_options = & var_list%get_sval (var_str ("$err_options"))) end if if (var_list%is_known (var_str ("$symbol"))) then call drawing_options_set (dro, symbol = & var_list%get_sval (var_str ("$symbol"))) end if if (var_list%is_known (var_str ("$gmlcode_bg"))) then call drawing_options_set (dro, gmlcode_bg = & var_list%get_sval (var_str ("$gmlcode_bg"))) end if if (var_list%is_known (var_str ("$gmlcode_fg"))) then call drawing_options_set (dro, gmlcode_fg = & var_list%get_sval (var_str ("$gmlcode_fg"))) end if end subroutine set_drawing_options @ %def set_drawing_options @ \subsubsection{Plots} Declare a plot. No mandatory arguments, just options. <>= type, extends (command_t) :: cmd_plot_t private type(string_t) :: id contains <> end type cmd_plot_t @ %def cmd_plot_t @ Output. Just print the ID. <>= procedure :: write => cmd_plot_write <>= subroutine cmd_plot_write (cmd, unit, indent) class(cmd_plot_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A)") "plot: ", char (cmd%id) end subroutine cmd_plot_write @ %def cmd_plot_write @ Compile. Record the plot ID and initialize lower, upper bound and bin width. <>= procedure :: compile => cmd_plot_compile <>= subroutine cmd_plot_compile (cmd, global) class(cmd_plot_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_tag pn_tag => parse_node_get_sub_ptr (cmd%pn, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_tag) call cmd%init (pn_tag, global) end subroutine cmd_plot_compile @ %def cmd_plot_compile @ This init routine is separated because it is reused below for graph initialization. <>= procedure :: init => cmd_plot_init <>= subroutine cmd_plot_init (plot, pn_tag, global) class(cmd_plot_t), intent(inout) :: plot type(parse_node_t), intent(in), pointer :: pn_tag type(rt_data_t), intent(inout), target :: global call plot%compile_options (global) select case (char (parse_node_get_rule_key (pn_tag))) case ("analysis_id") plot%id = parse_node_get_string (pn_tag) case default call msg_bug ("plot: name expression not implemented (yet)") end select end subroutine cmd_plot_init @ %def cmd_plot_init @ Command execution. This declares the plot and allocates it in the analysis store. <>= procedure :: execute => cmd_plot_execute <>= subroutine cmd_plot_execute (cmd, global) class(cmd_plot_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(graph_options_t) :: graph_options type(drawing_options_t) :: drawing_options var_list => cmd%local%get_var_list_ptr () call graph_options_init (graph_options) call set_graph_options (graph_options, var_list) call drawing_options_init_plot (drawing_options) call set_drawing_options (drawing_options, var_list) call analysis_init_plot (cmd%id, graph_options, drawing_options) end subroutine cmd_plot_execute @ %def cmd_plot_execute @ \subsubsection{Graphs} Declare a graph. The graph is defined in terms of its contents. Both the graph and its contents may carry options. The graph object contains its own ID as well as the IDs of its elements. For the elements, we reuse the [[cmd_plot_t]] defined above. <>= type, extends (command_t) :: cmd_graph_t private type(string_t) :: id integer :: n_elements = 0 type(cmd_plot_t), dimension(:), allocatable :: el type(string_t), dimension(:), allocatable :: element_id contains <> end type cmd_graph_t @ %def cmd_graph_t @ Output. Just print the ID. <>= procedure :: write => cmd_graph_write <>= subroutine cmd_graph_write (cmd, unit, indent) class(cmd_graph_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,A,A,I0,A)") "graph: ", char (cmd%id), & " (", cmd%n_elements, " entries)" end subroutine cmd_graph_write @ %def cmd_graph_write @ Compile. Record the graph ID and initialize lower, upper bound and bin width. For compiling the graph element syntax, we use part of the [[cmd_plot_t]] compiler. Note: currently, we do not respect options, therefore just IDs on the RHS. <>= procedure :: compile => cmd_graph_compile <>= subroutine cmd_graph_compile (cmd, global) class(cmd_graph_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_term, pn_tag, pn_def, pn_app integer :: i pn_term => parse_node_get_sub_ptr (cmd%pn, 2) pn_tag => parse_node_get_sub_ptr (pn_term) cmd%pn_opt => parse_node_get_next_ptr (pn_tag) call cmd%compile_options (global) select case (char (parse_node_get_rule_key (pn_tag))) case ("analysis_id") cmd%id = parse_node_get_string (pn_tag) case default call msg_bug ("graph: name expression not implemented (yet)") end select pn_def => parse_node_get_next_ptr (pn_term, 2) cmd%n_elements = parse_node_get_n_sub (pn_def) allocate (cmd%element_id (cmd%n_elements)) allocate (cmd%el (cmd%n_elements)) pn_term => parse_node_get_sub_ptr (pn_def) pn_tag => parse_node_get_sub_ptr (pn_term) cmd%el(1)%pn_opt => parse_node_get_next_ptr (pn_tag) call cmd%el(1)%init (pn_tag, global) cmd%element_id(1) = parse_node_get_string (pn_tag) pn_app => parse_node_get_next_ptr (pn_term) do i = 2, cmd%n_elements pn_term => parse_node_get_sub_ptr (pn_app, 2) pn_tag => parse_node_get_sub_ptr (pn_term) cmd%el(i)%pn_opt => parse_node_get_next_ptr (pn_tag) call cmd%el(i)%init (pn_tag, global) cmd%element_id(i) = parse_node_get_string (pn_tag) pn_app => parse_node_get_next_ptr (pn_app) end do end subroutine cmd_graph_compile @ %def cmd_graph_compile @ Command execution. This declares the graph, allocates it in the analysis store, and copies the graph elements. For the graph, we set graph and default drawing options. For the elements, we reset individual drawing options. This accesses internals of the contained elements of type [[cmd_plot_t]], see above. We might disentangle such an interdependency when this code is rewritten using proper type extension. <>= procedure :: execute => cmd_graph_execute <>= subroutine cmd_graph_execute (cmd, global) class(cmd_graph_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(graph_options_t) :: graph_options type(drawing_options_t) :: drawing_options integer :: i, type var_list => cmd%local%get_var_list_ptr () call graph_options_init (graph_options) call set_graph_options (graph_options, var_list) call analysis_init_graph (cmd%id, cmd%n_elements, graph_options) do i = 1, cmd%n_elements if (associated (cmd%el(i)%options)) then call cmd%el(i)%options%execute (cmd%el(i)%local) end if type = analysis_store_get_object_type (cmd%element_id(i)) select case (type) case (AN_HISTOGRAM) call drawing_options_init_histogram (drawing_options) case (AN_PLOT) call drawing_options_init_plot (drawing_options) end select call set_drawing_options (drawing_options, var_list) if (associated (cmd%el(i)%options)) then call set_drawing_options (drawing_options, cmd%el(i)%local%var_list) end if call analysis_fill_graph (cmd%id, i, cmd%element_id(i), drawing_options) end do end subroutine cmd_graph_execute @ %def cmd_graph_execute @ \subsubsection{Analysis} Hold the analysis ID either as a string or as an expression: <>= type :: analysis_id_t type(string_t) :: tag type(parse_node_t), pointer :: pn_sexpr => null () end type analysis_id_t @ %def analysis_id_t @ Define the analysis expression. We store the parse tree for the right-hand side instead of compiling it. Compilation is deferred to the process environment where the analysis expression is used. <>= type, extends (command_t) :: cmd_analysis_t private type(parse_node_t), pointer :: pn_lexpr => null () contains <> end type cmd_analysis_t @ %def cmd_analysis_t @ Output. Print just a message that analysis has been defined. <>= procedure :: write => cmd_analysis_write <>= subroutine cmd_analysis_write (cmd, unit, indent) class(cmd_analysis_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "analysis: [defined]" end subroutine cmd_analysis_write @ %def cmd_analysis_write @ Compile. Simply store the parse (sub)tree. <>= procedure :: compile => cmd_analysis_compile <>= subroutine cmd_analysis_compile (cmd, global) class(cmd_analysis_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%pn_lexpr => parse_node_get_sub_ptr (cmd%pn, 3) end subroutine cmd_analysis_compile @ %def cmd_analysis_compile @ Instead of evaluating the cut expression, link the parse tree to the global data set, such that it is compiled and executed in the appropriate process context. <>= procedure :: execute => cmd_analysis_execute <>= subroutine cmd_analysis_execute (cmd, global) class(cmd_analysis_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global global%pn%analysis_lexpr => cmd%pn_lexpr end subroutine cmd_analysis_execute @ %def cmd_analysis_execute @ \subsubsection{Write histograms and plots} The data type encapsulating the command: <>= type, extends (command_t) :: cmd_write_analysis_t private type(analysis_id_t), dimension(:), allocatable :: id type(string_t), dimension(:), allocatable :: tag contains <> end type cmd_write_analysis_t @ %def analysis_id_t @ %def cmd_write_analysis_t @ Output. Just the keyword. <>= procedure :: write => cmd_write_analysis_write <>= subroutine cmd_write_analysis_write (cmd, unit, indent) class(cmd_write_analysis_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "write_analysis" end subroutine cmd_write_analysis_write @ %def cmd_write_analysis_write @ Compile. <>= procedure :: compile => cmd_write_analysis_compile <>= subroutine cmd_write_analysis_compile (cmd, global) class(cmd_write_analysis_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_clause, pn_args, pn_id integer :: n, i pn_clause => parse_node_get_sub_ptr (cmd%pn) pn_args => parse_node_get_sub_ptr (pn_clause, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_clause) call cmd%compile_options (global) if (associated (pn_args)) then n = parse_node_get_n_sub (pn_args) allocate (cmd%id (n)) do i = 1, n pn_id => parse_node_get_sub_ptr (pn_args, i) if (char (parse_node_get_rule_key (pn_id)) == "analysis_id") then cmd%id(i)%tag = parse_node_get_string (pn_id) else cmd%id(i)%pn_sexpr => pn_id end if end do else allocate (cmd%id (0)) end if end subroutine cmd_write_analysis_compile @ %def cmd_write_analysis_compile @ The output format for real data values: <>= character(*), parameter, public :: & DEFAULT_ANALYSIS_FILENAME = "whizard_analysis.dat" character(len=1), dimension(2), parameter, public :: & FORBIDDEN_ENDINGS1 = [ "o", "a" ] character(len=2), dimension(6), parameter, public :: & FORBIDDEN_ENDINGS2 = [ "mp", "ps", "vg", "pg", "lo", "la" ] character(len=3), dimension(18), parameter, public :: & FORBIDDEN_ENDINGS3 = [ "aux", "dvi", "evt", "evx", "f03", "f90", & "f95", "log", "ltp", "mpx", "olc", "olp", "pdf", "phs", "sin", & "tex", "vg2", "vgx" ] @ %def DEFAULT_ANALYSIS_FILENAME @ %def FORBIDDEN_ENDINGS1 @ %def FORBIDDEN_ENDINGS2 @ %def FORBIDDEN_ENDINGS3 @ As this contains a lot of similar code to [[cmd_compile_analysis_execute]] we outsource the main code to a subroutine. <>= procedure :: execute => cmd_write_analysis_execute <>= subroutine cmd_write_analysis_execute (cmd, global) class(cmd_write_analysis_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list var_list => cmd%local%get_var_list_ptr () call write_analysis_wrap (var_list, global%out_files, & cmd%id, tag = cmd%tag) end subroutine cmd_write_analysis_execute @ %def cmd_write_analysis_execute @ If the [[data_file]] optional argument is present, this is called from [[cmd_compile_analysis_execute]], which needs the file name for further processing, and requires the default format. For the moment, parameters and macros for custom data processing are disabled. <>= subroutine write_analysis_wrap (var_list, out_files, id, tag, data_file) type(var_list_t), intent(inout), target :: var_list type(file_list_t), intent(inout), target :: out_files type(analysis_id_t), dimension(:), intent(in), target :: id type(string_t), dimension(:), allocatable, intent(out) :: tag type(string_t), intent(out), optional :: data_file type(string_t) :: defaultfile, file integer :: i logical :: keep_open type(string_t) :: extension logical :: one_file defaultfile = var_list%get_sval (var_str ("$out_file")) if (present (data_file)) then if (defaultfile == "" .or. defaultfile == ".") then defaultfile = DEFAULT_ANALYSIS_FILENAME else if (scan (".", defaultfile) > 0) then call split (defaultfile, extension, ".", back=.true.) if (any (lower_case (char(extension)) == FORBIDDEN_ENDINGS1) .or. & any (lower_case (char(extension)) == FORBIDDEN_ENDINGS2) .or. & any (lower_case (char(extension)) == FORBIDDEN_ENDINGS3)) & call msg_fatal ("The ending " // char(extension) // & " is internal and not allowed as data file.") if (extension /= "") then if (defaultfile /= "") then defaultfile = defaultfile // "." // extension else defaultfile = "whizard_analysis." // extension end if else defaultfile = defaultfile // ".dat" endif else defaultfile = defaultfile // ".dat" end if end if data_file = defaultfile end if one_file = defaultfile /= "" if (one_file) then file = defaultfile keep_open = file_list_is_open (out_files, file, & action = "write") if (keep_open) then if (present (data_file)) then call msg_fatal ("Compiling analysis: File '" & // char (data_file) & // "' can't be used, it is already open.") else call msg_message ("Appending analysis data to file '" & // char (file) // "'") end if else call file_list_open (out_files, file, & action = "write", status = "replace", position = "asis") call msg_message ("Writing analysis data to file '" & // char (file) // "'") end if end if call get_analysis_tags (tag, id, var_list) do i = 1, size (tag) call file_list_write_analysis & (out_files, file, tag(i)) end do if (one_file .and. .not. keep_open) then call file_list_close (out_files, file) end if contains subroutine get_analysis_tags (analysis_tag, id, var_list) type(string_t), dimension(:), intent(out), allocatable :: analysis_tag type(analysis_id_t), dimension(:), intent(in) :: id type(var_list_t), intent(in), target :: var_list if (size (id) /= 0) then allocate (analysis_tag (size (id))) do i = 1, size (id) if (associated (id(i)%pn_sexpr)) then analysis_tag(i) = eval_string (id(i)%pn_sexpr, var_list) else analysis_tag(i) = id(i)%tag end if end do else call analysis_store_get_ids (tag) end if end subroutine get_analysis_tags end subroutine write_analysis_wrap @ %def write_analysis_wrap \subsubsection{Compile analysis results} This command writes files in a form suitable for GAMELAN and executes the appropriate commands to compile them. The first part is identical to [[cmd_write_analysis]]. <>= type, extends (command_t) :: cmd_compile_analysis_t private type(analysis_id_t), dimension(:), allocatable :: id type(string_t), dimension(:), allocatable :: tag contains <> end type cmd_compile_analysis_t @ %def cmd_compile_analysis_t @ Output. Just the keyword. <>= procedure :: write => cmd_compile_analysis_write <>= subroutine cmd_compile_analysis_write (cmd, unit, indent) class(cmd_compile_analysis_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "compile_analysis" end subroutine cmd_compile_analysis_write @ %def cmd_compile_analysis_write @ Compile. <>= procedure :: compile => cmd_compile_analysis_compile <>= subroutine cmd_compile_analysis_compile (cmd, global) class(cmd_compile_analysis_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_clause, pn_args, pn_id integer :: n, i pn_clause => parse_node_get_sub_ptr (cmd%pn) pn_args => parse_node_get_sub_ptr (pn_clause, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_clause) call cmd%compile_options (global) if (associated (pn_args)) then n = parse_node_get_n_sub (pn_args) allocate (cmd%id (n)) do i = 1, n pn_id => parse_node_get_sub_ptr (pn_args, i) if (char (parse_node_get_rule_key (pn_id)) == "analysis_id") then cmd%id(i)%tag = parse_node_get_string (pn_id) else cmd%id(i)%pn_sexpr => pn_id end if end do else allocate (cmd%id (0)) end if end subroutine cmd_compile_analysis_compile @ %def cmd_compile_analysis_compile @ First write the analysis data to file, then write a GAMELAN driver and produce MetaPost and \TeX\ output. <>= procedure :: execute => cmd_compile_analysis_execute <>= subroutine cmd_compile_analysis_execute (cmd, global) class(cmd_compile_analysis_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(string_t) :: file, basename, extension, driver_file, & makefile integer :: u_driver, u_makefile logical :: has_gmlcode, only_file var_list => cmd%local%get_var_list_ptr () call write_analysis_wrap (var_list, & global%out_files, cmd%id, tag = cmd%tag, & data_file = file) basename = file if (scan (".", basename) > 0) then call split (basename, extension, ".", back=.true.) else extension = "" end if driver_file = basename // ".tex" makefile = basename // "_ana.makefile" u_driver = free_unit () open (unit=u_driver, file=char(driver_file), & action="write", status="replace") if (allocated (cmd%tag)) then call analysis_write_driver (file, cmd%tag, unit=u_driver) has_gmlcode = analysis_has_plots (cmd%tag) else call analysis_write_driver (file, unit=u_driver) has_gmlcode = analysis_has_plots () end if close (u_driver) u_makefile = free_unit () open (unit=u_makefile, file=char(makefile), & action="write", status="replace") call analysis_write_makefile (basename, u_makefile, & has_gmlcode, global%os_data) close (u_makefile) call msg_message ("Compiling analysis results display in '" & // char (driver_file) // "'") call msg_message ("Providing analysis steering makefile '" & // char (makefile) // "'") only_file = global%var_list%get_lval & (var_str ("?analysis_file_only")) if (.not. only_file) call analysis_compile_tex & (basename, has_gmlcode, global%os_data) end subroutine cmd_compile_analysis_execute @ %def cmd_compile_analysis_execute @ \subsection{User-controlled output to data files} \subsubsection{Open file (output)} Open a file for output. <>= type, extends (command_t) :: cmd_open_out_t private type(parse_node_t), pointer :: file_expr => null () contains <> end type cmd_open_out_t @ %def cmd_open_out @ Finalizer for the embedded eval tree. <>= subroutine cmd_open_out_final (object) class(cmd_open_out_t), intent(inout) :: object end subroutine cmd_open_out_final @ %def cmd_open_out_final @ Output (trivial here). <>= procedure :: write => cmd_open_out_write <>= subroutine cmd_open_out_write (cmd, unit, indent) class(cmd_open_out_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "open_out: " end subroutine cmd_open_out_write @ %def cmd_open_out_write @ Compile: create an eval tree for the filename expression. <>= procedure :: compile => cmd_open_out_compile <>= subroutine cmd_open_out_compile (cmd, global) class(cmd_open_out_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global cmd%file_expr => parse_node_get_sub_ptr (cmd%pn, 2) if (associated (cmd%file_expr)) then cmd%pn_opt => parse_node_get_next_ptr (cmd%file_expr) end if call cmd%compile_options (global) end subroutine cmd_open_out_compile @ %def cmd_open_out_compile @ Execute: append the file to the global list of open files. <>= procedure :: execute => cmd_open_out_execute <>= subroutine cmd_open_out_execute (cmd, global) class(cmd_open_out_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(eval_tree_t) :: file_expr type(string_t) :: file var_list => cmd%local%get_var_list_ptr () call file_expr%init_sexpr (cmd%file_expr, var_list) call file_expr%evaluate () if (file_expr%is_known ()) then file = file_expr%get_string () call file_list_open (global%out_files, file, & action = "write", status = "replace", position = "asis") else call msg_fatal ("open_out: file name argument evaluates to unknown") end if call file_expr%final () end subroutine cmd_open_out_execute @ %def cmd_open_out_execute \subsubsection{Open file (output)} Close an output file. Except for the [[execute]] method, everything is analogous to the open command, so we can just inherit. <>= type, extends (cmd_open_out_t) :: cmd_close_out_t private contains <> end type cmd_close_out_t @ %def cmd_close_out @ Execute: remove the file from the global list of output files. <>= procedure :: execute => cmd_close_out_execute <>= subroutine cmd_close_out_execute (cmd, global) class(cmd_close_out_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(eval_tree_t) :: file_expr type(string_t) :: file var_list => cmd%local%var_list call file_expr%init_sexpr (cmd%file_expr, var_list) call file_expr%evaluate () if (file_expr%is_known ()) then file = file_expr%get_string () call file_list_close (global%out_files, file) else call msg_fatal ("close_out: file name argument evaluates to unknown") end if call file_expr%final () end subroutine cmd_close_out_execute @ %def cmd_close_out_execute @ \subsection{Print custom-formatted values} <>= type, extends (command_t) :: cmd_printf_t private type(parse_node_t), pointer :: sexpr => null () type(parse_node_t), pointer :: sprintf_fun => null () type(parse_node_t), pointer :: sprintf_clause => null () type(parse_node_t), pointer :: sprintf => null () contains <> end type cmd_printf_t @ %def cmd_printf_t @ Finalize. <>= procedure :: final => cmd_printf_final <>= subroutine cmd_printf_final (cmd) class(cmd_printf_t), intent(inout) :: cmd call parse_node_final (cmd%sexpr, recursive = .false.) deallocate (cmd%sexpr) call parse_node_final (cmd%sprintf_fun, recursive = .false.) deallocate (cmd%sprintf_fun) call parse_node_final (cmd%sprintf_clause, recursive = .false.) deallocate (cmd%sprintf_clause) call parse_node_final (cmd%sprintf, recursive = .false.) deallocate (cmd%sprintf) end subroutine cmd_printf_final @ %def cmd_printf_final @ Output. Do not print the parse tree, since this may get cluttered. Just a message that cuts have been defined. <>= procedure :: write => cmd_printf_write <>= subroutine cmd_printf_write (cmd, unit, indent) class(cmd_printf_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "printf:" end subroutine cmd_printf_write @ %def cmd_printf_write @ Compile. We create a fake parse node (subtree) with a [[sprintf]] command with identical arguments which can then be handled by the corresponding evaluation procedure. <>= procedure :: compile => cmd_printf_compile <>= subroutine cmd_printf_compile (cmd, global) class(cmd_printf_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_cmd, pn_clause, pn_args, pn_format pn_cmd => parse_node_get_sub_ptr (cmd%pn) pn_clause => parse_node_get_sub_ptr (pn_cmd) pn_format => parse_node_get_sub_ptr (pn_clause, 2) pn_args => parse_node_get_next_ptr (pn_clause) cmd%pn_opt => parse_node_get_next_ptr (pn_cmd) call cmd%compile_options (global) allocate (cmd%sexpr) call parse_node_create_branch (cmd%sexpr, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("sexpr"))) allocate (cmd%sprintf_fun) call parse_node_create_branch (cmd%sprintf_fun, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("sprintf_fun"))) allocate (cmd%sprintf_clause) call parse_node_create_branch (cmd%sprintf_clause, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("sprintf_clause"))) allocate (cmd%sprintf) call parse_node_create_key (cmd%sprintf, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("sprintf"))) call parse_node_append_sub (cmd%sprintf_clause, cmd%sprintf) call parse_node_append_sub (cmd%sprintf_clause, pn_format) call parse_node_freeze_branch (cmd%sprintf_clause) call parse_node_append_sub (cmd%sprintf_fun, cmd%sprintf_clause) if (associated (pn_args)) then call parse_node_append_sub (cmd%sprintf_fun, pn_args) end if call parse_node_freeze_branch (cmd%sprintf_fun) call parse_node_append_sub (cmd%sexpr, cmd%sprintf_fun) call parse_node_freeze_branch (cmd%sexpr) end subroutine cmd_printf_compile @ %def cmd_printf_compile @ Execute. Evaluate the string (pretending this is a [[sprintf]] expression) and print it. <>= procedure :: execute => cmd_printf_execute <>= subroutine cmd_printf_execute (cmd, global) class(cmd_printf_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(string_t) :: string, file type(eval_tree_t) :: sprintf_expr logical :: advance var_list => cmd%local%get_var_list_ptr () advance = var_list%get_lval (& var_str ("?out_advance")) file = var_list%get_sval (& var_str ("$out_file")) call sprintf_expr%init_sexpr (cmd%sexpr, var_list) call sprintf_expr%evaluate () if (sprintf_expr%is_known ()) then string = sprintf_expr%get_string () if (len (file) == 0) then call msg_result (char (string)) else call file_list_write (global%out_files, file, string, advance) end if end if end subroutine cmd_printf_execute @ %def cmd_printf_execute @ \subsubsection{Record data} The expression syntax already contains a [[record]] keyword; this evaluates to a logical which is always true, but it has the side-effect of recording data into analysis objects. Here we define a command as an interface to this construct. <>= type, extends (command_t) :: cmd_record_t private type(parse_node_t), pointer :: pn_lexpr => null () contains <> end type cmd_record_t @ %def cmd_record_t @ Output. With the compile hack below, there is nothing of interest to print here. <>= procedure :: write => cmd_record_write <>= subroutine cmd_record_write (cmd, unit, indent) class(cmd_record_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)") "record" end subroutine cmd_record_write @ %def cmd_record_write @ Compile. This is a hack which transforms the [[record]] command into a [[record]] expression, which we handle in the [[expressions]] module. <>= procedure :: compile => cmd_record_compile <>= subroutine cmd_record_compile (cmd, global) class(cmd_record_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_lexpr, pn_lsinglet, pn_lterm, pn_record call parse_node_create_branch (pn_lexpr, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("lexpr"))) call parse_node_create_branch (pn_lsinglet, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("lsinglet"))) call parse_node_append_sub (pn_lexpr, pn_lsinglet) call parse_node_create_branch (pn_lterm, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("lterm"))) call parse_node_append_sub (pn_lsinglet, pn_lterm) pn_record => parse_node_get_sub_ptr (cmd%pn) call parse_node_append_sub (pn_lterm, pn_record) cmd%pn_lexpr => pn_lexpr end subroutine cmd_record_compile @ %def cmd_record_compile @ Command execution. Again, transfer this to the embedded expression and just forget the logical result. <>= procedure :: execute => cmd_record_execute <>= subroutine cmd_record_execute (cmd, global) class(cmd_record_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list logical :: lval var_list => global%get_var_list_ptr () lval = eval_log (cmd%pn_lexpr, var_list) end subroutine cmd_record_execute @ %def cmd_record_execute @ \subsubsection{Unstable particles} Mark a particle as unstable. For each unstable particle, we store a number of decay channels and compute their respective BRs. <>= type, extends (command_t) :: cmd_unstable_t private integer :: n_proc = 0 type(string_t), dimension(:), allocatable :: process_id type(parse_node_t), pointer :: pn_prt_in => null () contains <> end type cmd_unstable_t @ %def cmd_unstable_t @ Output: we know the process IDs. <>= procedure :: write => cmd_unstable_write <>= subroutine cmd_unstable_write (cmd, unit, indent) class(cmd_unstable_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,1x,I0,1x,A)", advance="no") & "unstable:", 1, "(" do i = 1, cmd%n_proc if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "(A)", advance="no") char (cmd%process_id(i)) end do write (u, "(A)") ")" end subroutine cmd_unstable_write @ %def cmd_unstable_write @ Compile. Initiate an eval tree for the decaying particle and determine the decay channel process IDs. <>= procedure :: compile => cmd_unstable_compile <>= subroutine cmd_unstable_compile (cmd, global) class(cmd_unstable_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_list, pn_proc integer :: i cmd%pn_prt_in => parse_node_get_sub_ptr (cmd%pn, 2) pn_list => parse_node_get_next_ptr (cmd%pn_prt_in) if (associated (pn_list)) then select case (char (parse_node_get_rule_key (pn_list))) case ("unstable_arg") cmd%n_proc = parse_node_get_n_sub (pn_list) cmd%pn_opt => parse_node_get_next_ptr (pn_list) case default cmd%n_proc = 0 cmd%pn_opt => pn_list pn_list => null () end select end if call cmd%compile_options (global) if (associated (pn_list)) then allocate (cmd%process_id (cmd%n_proc)) pn_proc => parse_node_get_sub_ptr (pn_list) do i = 1, cmd%n_proc cmd%process_id(i) = parse_node_get_string (pn_proc) call cmd%local%process_stack%init_result_vars (cmd%process_id(i)) pn_proc => parse_node_get_next_ptr (pn_proc) end do else allocate (cmd%process_id (0)) end if end subroutine cmd_unstable_compile @ %def cmd_unstable_compile @ Command execution. Evaluate the decaying particle and mark the decays in the current model object. <>= procedure :: execute => cmd_unstable_execute <>= subroutine cmd_unstable_execute (cmd, global) class(cmd_unstable_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list logical :: auto_decays, auto_decays_radiative integer :: auto_decays_multiplicity logical :: isotropic_decay, diagonal_decay, polarized_decay integer :: decay_helicity type(pdg_array_t) :: pa_in integer :: pdg_in type(string_t) :: libname_cur, libname_dec type(string_t), dimension(:), allocatable :: auto_id, tmp_id integer :: n_proc_user integer :: i, u_tmp character(80) :: buffer var_list => cmd%local%get_var_list_ptr () auto_decays = & var_list%get_lval (var_str ("?auto_decays")) if (auto_decays) then auto_decays_multiplicity = & var_list%get_ival (var_str ("auto_decays_multiplicity")) auto_decays_radiative = & var_list%get_lval (var_str ("?auto_decays_radiative")) end if isotropic_decay = & var_list%get_lval (var_str ("?isotropic_decay")) if (isotropic_decay) then diagonal_decay = .false. polarized_decay = .false. else diagonal_decay = & var_list%get_lval (var_str ("?diagonal_decay")) if (diagonal_decay) then polarized_decay = .false. else polarized_decay = & var_list%is_known (var_str ("decay_helicity")) if (polarized_decay) then decay_helicity = var_list%get_ival (var_str ("decay_helicity")) end if end if end if pa_in = eval_pdg_array (cmd%pn_prt_in, var_list) if (pdg_array_get_length (pa_in) /= 1) & call msg_fatal ("Unstable: decaying particle must be unique") pdg_in = pdg_array_get (pa_in, 1) n_proc_user = cmd%n_proc if (auto_decays) then call create_auto_decays (pdg_in, & auto_decays_multiplicity, auto_decays_radiative, & libname_dec, auto_id, cmd%local) allocate (tmp_id (cmd%n_proc + size (auto_id))) tmp_id(:cmd%n_proc) = cmd%process_id tmp_id(cmd%n_proc+1:) = auto_id call move_alloc (from = tmp_id, to = cmd%process_id) cmd%n_proc = size (cmd%process_id) end if libname_cur = cmd%local%prclib%get_name () do i = 1, cmd%n_proc if (i == n_proc_user + 1) then call cmd%local%update_prclib & (cmd%local%prclib_stack%get_library_ptr (libname_dec)) end if if (.not. global%process_stack%exists (cmd%process_id(i))) then call var_list%set_log & (var_str ("?decay_rest_frame"), .false., is_known = .true.) call integrate_process (cmd%process_id(i), cmd%local, global) call global%process_stack%fill_result_vars (cmd%process_id(i)) end if end do call cmd%local%update_prclib & (cmd%local%prclib_stack%get_library_ptr (libname_cur)) if (cmd%n_proc > 0) then if (polarized_decay) then call global%modify_particle (pdg_in, stable = .false., & decay = cmd%process_id, & isotropic_decay = .false., & diagonal_decay = .false., & decay_helicity = decay_helicity, & polarized = .false.) else call global%modify_particle (pdg_in, stable = .false., & decay = cmd%process_id, & isotropic_decay = isotropic_decay, & diagonal_decay = diagonal_decay, & polarized = .false.) end if u_tmp = free_unit () open (u_tmp, status = "scratch", action = "readwrite") call show_unstable (global, pdg_in, u_tmp) rewind (u_tmp) do read (u_tmp, "(A)", end = 1) buffer write (msg_buffer, "(A)") trim (buffer) call msg_message () end do 1 continue close (u_tmp) else call err_unstable (global, pdg_in) end if end subroutine cmd_unstable_execute @ %def cmd_unstable_execute @ Show data for the current unstable particle. This is called both by the [[unstable]] and by the [[show]] command. To determine decay branching rations, we look at the decay process IDs and inspect the corresponding [[integral()]] result variables. <>= subroutine show_unstable (global, pdg, u) type(rt_data_t), intent(in), target :: global integer, intent(in) :: pdg, u type(flavor_t) :: flv type(string_t), dimension(:), allocatable :: decay real(default), dimension(:), allocatable :: br real(default) :: width type(process_t), pointer :: process type(process_component_def_t), pointer :: prc_def type(string_t), dimension(:), allocatable :: prt_out, prt_out_str integer :: i, j logical :: opened call flv%init (pdg, global%model) call flv%get_decays (decay) if (.not. allocated (decay)) return allocate (prt_out_str (size (decay))) allocate (br (size (decay))) do i = 1, size (br) process => global%process_stack%get_process_ptr (decay(i)) prc_def => process%get_component_def_ptr (1) call prc_def%get_prt_out (prt_out) prt_out_str(i) = prt_out(1) do j = 2, size (prt_out) prt_out_str(i) = prt_out_str(i) // ", " // prt_out(j) end do br(i) = global%get_rval ("integral(" // decay(i) // ")") end do if (all (br >= 0)) then if (any (br > 0)) then width = sum (br) br = br / sum (br) write (u, "(A)") "Unstable particle " & // char (flv%get_name ()) & // ": computed branching ratios:" do i = 1, size (br) write (u, "(2x,A,':'," // FMT_14 // ",3x,A)") & char (decay(i)), br(i), char (prt_out_str(i)) end do write (u, "(2x,'Total width ='," // FMT_14 // ",' GeV (computed)')") width write (u, "(2x,' ='," // FMT_14 // ",' GeV (preset)')") & flv%get_width () if (flv%decays_isotropically ()) then write (u, "(2x,A)") "Decay options: isotropic" else if (flv%decays_diagonal ()) then write (u, "(2x,A)") "Decay options: & &projection on diagonal helicity states" else if (flv%has_decay_helicity ()) then write (u, "(2x,A,1x,I0)") "Decay options: projection onto helicity =", & flv%get_decay_helicity () else write (u, "(2x,A)") "Decay options: helicity treated exactly" end if else inquire (unit = u, opened = opened) if (opened .and. .not. mask_fatal_errors) close (u) call msg_fatal ("Unstable particle " & // char (flv%get_name ()) & // ": partial width vanishes for all decay channels") end if else inquire (unit = u, opened = opened) if (opened .and. .not. mask_fatal_errors) close (u) call msg_fatal ("Unstable particle " & // char (flv%get_name ()) & // ": partial width is negative") end if end subroutine show_unstable @ %def show_unstable @ If no decays have been found, issue a non-fatal error. <>= subroutine err_unstable (global, pdg) type(rt_data_t), intent(in), target :: global integer, intent(in) :: pdg type(flavor_t) :: flv call flv%init (pdg, global%model) call msg_error ("Unstable: no allowed decays found for particle " & // char (flv%get_name ()) // ", keeping as stable") end subroutine err_unstable @ %def err_unstable @ Auto decays: create process IDs and make up process configurations, using the PDG codes generated by the [[ds_table]] make method. We allocate and use a self-contained process library that contains only the decay processes of the current particle. When done, we revert the global library pointer to the original library but return the name of the new one. The new library becomes part of the global library stack and can thus be referred to at any time. <>= subroutine create_auto_decays & (pdg_in, mult, rad, libname_dec, process_id, global) integer, intent(in) :: pdg_in integer, intent(in) :: mult logical, intent(in) :: rad type(string_t), intent(out) :: libname_dec type(string_t), dimension(:), allocatable, intent(out) :: process_id type(rt_data_t), intent(inout) :: global type(prclib_entry_t), pointer :: lib_entry type(process_library_t), pointer :: lib type(ds_table_t) :: ds_table type(split_constraints_t) :: constraints type(pdg_array_t), dimension(:), allocatable :: pa_out character(80) :: buffer character :: p_or_a type(string_t) :: process_string, libname_cur type(flavor_t) :: flv_in, flv_out type(string_t) :: prt_in type(string_t), dimension(:), allocatable :: prt_out type(process_configuration_t) :: prc_config integer :: i, j, k call flv_in%init (pdg_in, global%model) if (rad) then call constraints%init (2) else call constraints%init (3) call constraints%set (3, constrain_radiation ()) end if call constraints%set (1, constrain_n_tot (mult)) call constraints%set (2, & constrain_mass_sum (flv_in%get_mass (), margin = 0._default)) call ds_table%make (global%model, pdg_in, constraints) prt_in = flv_in%get_name () if (pdg_in > 0) then p_or_a = "p" else p_or_a = "a" end if if (ds_table%get_length () == 0) then call msg_warning ("Auto-decays: Particle " // char (prt_in) // ": " & // "no decays found") libname_dec = "" allocate (process_id (0)) else call msg_message ("Creating decay process library for particle " & // char (prt_in)) libname_cur = global%prclib%get_name () write (buffer, "(A,A,I0)") "_d", p_or_a, abs (pdg_in) libname_dec = libname_cur // trim (buffer) lib => global%prclib_stack%get_library_ptr (libname_dec) if (.not. (associated (lib))) then allocate (lib_entry) call lib_entry%init (libname_dec) lib => lib_entry%process_library_t call global%add_prclib (lib_entry) else call global%update_prclib (lib) end if allocate (process_id (ds_table%get_length ())) do i = 1, size (process_id) write (buffer, "(A,'_',A,I0,'_',I0)") & "decay", p_or_a, abs (pdg_in), i process_id(i) = trim (buffer) process_string = process_id(i) // ": " // prt_in // " =>" call ds_table%get_pdg_out (i, pa_out) allocate (prt_out (size (pa_out))) do j = 1, size (pa_out) do k = 1, pa_out(j)%get_length () call flv_out%init (pa_out(j)%get (k), global%model) if (k == 1) then prt_out(j) = flv_out%get_name () else prt_out(j) = prt_out(j) // ":" // flv_out%get_name () end if end do process_string = process_string // " " // prt_out(j) end do call msg_message (char (process_string)) call prc_config%init (process_id(i), 1, 1, & global%model, global%var_list, & nlo_process = global%nlo_fixed_order) call prc_config%setup_component (1, new_prt_spec ([prt_in]), & new_prt_spec (prt_out), global%model, global%var_list) call prc_config%record (global) deallocate (prt_out) deallocate (pa_out) end do lib => global%prclib_stack%get_library_ptr (libname_cur) call global%update_prclib (lib) end if call ds_table%final () end subroutine create_auto_decays @ %def create_auto_decays @ \subsubsection{(Stable particles} Revert the unstable declaration for a list of particles. <>= type, extends (command_t) :: cmd_stable_t private type(parse_node_p), dimension(:), allocatable :: pn_pdg contains <> end type cmd_stable_t @ %def cmd_stable_t @ Output: we know only the number of particles. <>= procedure :: write => cmd_stable_write <>= subroutine cmd_stable_write (cmd, unit, indent) class(cmd_stable_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,1x,I0)") "stable:", size (cmd%pn_pdg) end subroutine cmd_stable_write @ %def cmd_stable_write @ Compile. Assign parse nodes for the particle IDs. <>= procedure :: compile => cmd_stable_compile <>= subroutine cmd_stable_compile (cmd, global) class(cmd_stable_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_list, pn_prt integer :: n, i pn_list => parse_node_get_sub_ptr (cmd%pn, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_list) call cmd%compile_options (global) n = parse_node_get_n_sub (pn_list) allocate (cmd%pn_pdg (n)) pn_prt => parse_node_get_sub_ptr (pn_list) i = 1 do while (associated (pn_prt)) cmd%pn_pdg(i)%ptr => pn_prt pn_prt => parse_node_get_next_ptr (pn_prt) i = i + 1 end do end subroutine cmd_stable_compile @ %def cmd_stable_compile @ Execute: apply the modifications to the current model. <>= procedure :: execute => cmd_stable_execute <>= subroutine cmd_stable_execute (cmd, global) class(cmd_stable_t), intent(inout) :: cmd type(rt_data_t), target, intent(inout) :: global type(var_list_t), pointer :: var_list type(pdg_array_t) :: pa integer :: pdg type(flavor_t) :: flv integer :: i var_list => cmd%local%get_var_list_ptr () do i = 1, size (cmd%pn_pdg) pa = eval_pdg_array (cmd%pn_pdg(i)%ptr, var_list) if (pdg_array_get_length (pa) /= 1) & call msg_fatal ("Stable: listed particles must be unique") pdg = pdg_array_get (pa, 1) call global%modify_particle (pdg, stable = .true., & isotropic_decay = .false., & diagonal_decay = .false., & polarized = .false.) call flv%init (pdg, cmd%local%model) call msg_message ("Particle " & // char (flv%get_name ()) & // " declared as stable") end do end subroutine cmd_stable_execute @ %def cmd_stable_execute @ \subsubsection{Polarized particles} These commands mark particles as (un)polarized, to be applied in subsequent simulation passes. Since this is technically the same as the [[stable]] command, we take a shortcut and make this an extension, just overriding methods. <>= type, extends (cmd_stable_t) :: cmd_polarized_t contains <> end type cmd_polarized_t type, extends (cmd_stable_t) :: cmd_unpolarized_t contains <> end type cmd_unpolarized_t @ %def cmd_polarized_t cmd_unpolarized_t @ Output: we know only the number of particles. <>= procedure :: write => cmd_polarized_write <>= procedure :: write => cmd_unpolarized_write <>= subroutine cmd_polarized_write (cmd, unit, indent) class(cmd_polarized_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,1x,I0)") "polarized:", size (cmd%pn_pdg) end subroutine cmd_polarized_write subroutine cmd_unpolarized_write (cmd, unit, indent) class(cmd_unpolarized_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,1x,I0)") "unpolarized:", size (cmd%pn_pdg) end subroutine cmd_unpolarized_write @ %def cmd_polarized_write @ %def cmd_unpolarized_write @ Compile: accounted for by the base command. Execute: apply the modifications to the current model. <>= procedure :: execute => cmd_polarized_execute <>= procedure :: execute => cmd_unpolarized_execute <>= subroutine cmd_polarized_execute (cmd, global) class(cmd_polarized_t), intent(inout) :: cmd type(rt_data_t), target, intent(inout) :: global type(var_list_t), pointer :: var_list type(pdg_array_t) :: pa integer :: pdg type(flavor_t) :: flv integer :: i var_list => cmd%local%get_var_list_ptr () do i = 1, size (cmd%pn_pdg) pa = eval_pdg_array (cmd%pn_pdg(i)%ptr, var_list) if (pdg_array_get_length (pa) /= 1) & call msg_fatal ("Polarized: listed particles must be unique") pdg = pdg_array_get (pa, 1) call global%modify_particle (pdg, polarized = .true., & stable = .true., & isotropic_decay = .false., & diagonal_decay = .false.) call flv%init (pdg, cmd%local%model) call msg_message ("Particle " & // char (flv%get_name ()) & // " declared as polarized") end do end subroutine cmd_polarized_execute subroutine cmd_unpolarized_execute (cmd, global) class(cmd_unpolarized_t), intent(inout) :: cmd type(rt_data_t), target, intent(inout) :: global type(var_list_t), pointer :: var_list type(pdg_array_t) :: pa integer :: pdg type(flavor_t) :: flv integer :: i var_list => cmd%local%get_var_list_ptr () do i = 1, size (cmd%pn_pdg) pa = eval_pdg_array (cmd%pn_pdg(i)%ptr, var_list) if (pdg_array_get_length (pa) /= 1) & call msg_fatal ("Unpolarized: listed particles must be unique") pdg = pdg_array_get (pa, 1) call global%modify_particle (pdg, polarized = .false., & stable = .true., & isotropic_decay = .false., & diagonal_decay = .false.) call flv%init (pdg, cmd%local%model) call msg_message ("Particle " & // char (flv%get_name ()) & // " declared as unpolarized") end do end subroutine cmd_unpolarized_execute @ %def cmd_polarized_execute @ %def cmd_unpolarized_execute @ \subsubsection{Parameters: formats for event-sample output} Specify all event formats that are to be used for output files in the subsequent simulation run. (The raw format is on by default and can be turned off here.) <>= type, extends (command_t) :: cmd_sample_format_t private type(string_t), dimension(:), allocatable :: format contains <> end type cmd_sample_format_t @ %def cmd_sample_format_t @ Output: here, everything is known. <>= procedure :: write => cmd_sample_format_write <>= subroutine cmd_sample_format_write (cmd, unit, indent) class(cmd_sample_format_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "sample_format = " do i = 1, size (cmd%format) if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "(A)", advance="no") char (cmd%format(i)) end do write (u, "(A)") end subroutine cmd_sample_format_write @ %def cmd_sample_format_write @ Compile. Initialize evaluation trees. <>= procedure :: compile => cmd_sample_format_compile <>= subroutine cmd_sample_format_compile (cmd, global) class(cmd_sample_format_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg type(parse_node_t), pointer :: pn_format integer :: i, n_format pn_arg => parse_node_get_sub_ptr (cmd%pn, 3) if (associated (pn_arg)) then n_format = parse_node_get_n_sub (pn_arg) allocate (cmd%format (n_format)) pn_format => parse_node_get_sub_ptr (pn_arg) i = 0 do while (associated (pn_format)) i = i + 1 cmd%format(i) = parse_node_get_string (pn_format) pn_format => parse_node_get_next_ptr (pn_format) end do else allocate (cmd%format (0)) end if end subroutine cmd_sample_format_compile @ %def cmd_sample_format_compile @ Execute. Transfer the list of format specifications to the corresponding array in the runtime data set. <>= procedure :: execute => cmd_sample_format_execute <>= subroutine cmd_sample_format_execute (cmd, global) class(cmd_sample_format_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (allocated (global%sample_fmt)) deallocate (global%sample_fmt) allocate (global%sample_fmt (size (cmd%format)), source = cmd%format) end subroutine cmd_sample_format_execute @ %def cmd_sample_format_execute @ \subsubsection{The simulate command} This is the actual SINDARIN command. <>= type, extends (command_t) :: cmd_simulate_t ! not private anymore as required by the whizard-c-interface integer :: n_proc = 0 type(string_t), dimension(:), allocatable :: process_id contains <> end type cmd_simulate_t @ %def cmd_simulate_t @ Output: we know the process IDs. <>= procedure :: write => cmd_simulate_write <>= subroutine cmd_simulate_write (cmd, unit, indent) class(cmd_simulate_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "simulate (" do i = 1, cmd%n_proc if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "(A)", advance="no") char (cmd%process_id(i)) end do write (u, "(A)") ")" end subroutine cmd_simulate_write @ %def cmd_simulate_write @ Compile. In contrast to WHIZARD 1 the confusing option to give the number of unweighted events for weighted events as if unweighting were to take place has been abandoned. (We both use [[n_events]] for weighted and unweighted events, the variable [[n_calls]] from WHIZARD 1 has been discarded. <>= procedure :: compile => cmd_simulate_compile <>= subroutine cmd_simulate_compile (cmd, global) class(cmd_simulate_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_proclist, pn_proc integer :: i pn_proclist => parse_node_get_sub_ptr (cmd%pn, 2) cmd%pn_opt => parse_node_get_next_ptr (pn_proclist) call cmd%compile_options (global) cmd%n_proc = parse_node_get_n_sub (pn_proclist) allocate (cmd%process_id (cmd%n_proc)) pn_proc => parse_node_get_sub_ptr (pn_proclist) do i = 1, cmd%n_proc cmd%process_id(i) = parse_node_get_string (pn_proc) call global%process_stack%init_result_vars (cmd%process_id(i)) pn_proc => parse_node_get_next_ptr (pn_proc) end do end subroutine cmd_simulate_compile @ %def cmd_simulate_compile @ Execute command: Simulate events. This is done via a [[simulation_t]] object and its associated methods. Signal handling: the [[generate]] method may exit abnormally if there is a pending signal. The current logic ensures that the [[es_array]] output channels are closed before the [[execute]] routine returns. The program will terminate then in [[command_list_execute]]. <>= procedure :: execute => cmd_simulate_execute <>= subroutine cmd_simulate_execute (cmd, global) class(cmd_simulate_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(rt_data_t), dimension(:), allocatable, target :: alt_env integer :: n_events type(simulation_t), target :: sim type(event_stream_array_t) :: es_array integer :: i, checkpoint, callback var_list => cmd%local%var_list if (cmd%local%nlo_fixed_order) then call check_nlo_options (cmd%local) end if if (allocated (cmd%local%pn%alt_setup)) then allocate (alt_env (size (cmd%local%pn%alt_setup))) do i = 1, size (alt_env) call build_alt_setup (alt_env(i), cmd%local, & cmd%local%pn%alt_setup(i)%ptr) end do call sim%init (cmd%process_id, .true., .true., cmd%local, global, & alt_env) else call sim%init (cmd%process_id, .true., .true., cmd%local, global) end if if (signal_is_pending ()) return if (sim%is_valid ()) then call sim%init_process_selector () call sim%setup_openmp () call sim%compute_n_events (n_events) call sim%set_n_events_requested (n_events) call sim%activate_extra_logging () call sim%prepare_event_streams (es_array) if (es_array%is_valid ()) then call sim%generate (es_array) else call sim%generate () end if call es_array%final () if (allocated (alt_env)) then do i = 1, size (alt_env) call alt_env(i)%local_final () end do end if end if call sim%final () end subroutine cmd_simulate_execute @ %def cmd_simulate_execute @ Build an alternative setup: the parse tree is stored in the global environment. We create a temporary command list to compile and execute this; the result is an alternative local environment [[alt_env]] which we can hand over to the [[simulate]] command. <>= recursive subroutine build_alt_setup (alt_env, global, pn) type(rt_data_t), intent(inout), target :: alt_env type(rt_data_t), intent(inout), target :: global type(parse_node_t), intent(in), target :: pn type(command_list_t), allocatable :: alt_options allocate (alt_options) call alt_env%local_init (global) call alt_env%activate () call alt_options%compile (pn, alt_env) call alt_options%execute (alt_env) call alt_env%deactivate (global, keep_local = .true.) call alt_options%final () end subroutine build_alt_setup @ %def build_alt_setup @ \subsubsection{The rescan command} This is the actual SINDARIN command. <>= type, extends (command_t) :: cmd_rescan_t ! private type(parse_node_t), pointer :: pn_filename => null () integer :: n_proc = 0 type(string_t), dimension(:), allocatable :: process_id contains <> end type cmd_rescan_t @ %def cmd_rescan_t @ Output: we know the process IDs. <>= procedure :: write => cmd_rescan_write <>= subroutine cmd_rescan_write (cmd, unit, indent) class(cmd_rescan_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "rescan (" do i = 1, cmd%n_proc if (i > 1) write (u, "(A,1x)", advance="no") "," write (u, "(A)", advance="no") char (cmd%process_id(i)) end do write (u, "(A)") ")" end subroutine cmd_rescan_write @ %def cmd_rescan_write @ Compile. The command takes a suffix argument, namely the file name of requested event file. <>= procedure :: compile => cmd_rescan_compile <>= subroutine cmd_rescan_compile (cmd, global) class(cmd_rescan_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_filename, pn_proclist, pn_proc integer :: i pn_filename => parse_node_get_sub_ptr (cmd%pn, 2) pn_proclist => parse_node_get_next_ptr (pn_filename) cmd%pn_opt => parse_node_get_next_ptr (pn_proclist) call cmd%compile_options (global) cmd%pn_filename => pn_filename cmd%n_proc = parse_node_get_n_sub (pn_proclist) allocate (cmd%process_id (cmd%n_proc)) pn_proc => parse_node_get_sub_ptr (pn_proclist) do i = 1, cmd%n_proc cmd%process_id(i) = parse_node_get_string (pn_proc) pn_proc => parse_node_get_next_ptr (pn_proc) end do end subroutine cmd_rescan_compile @ %def cmd_rescan_compile @ Execute command: Rescan events. This is done via a [[simulation_t]] object and its associated methods. <>= procedure :: execute => cmd_rescan_execute <>= subroutine cmd_rescan_execute (cmd, global) class(cmd_rescan_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(rt_data_t), dimension(:), allocatable, target :: alt_env type(string_t) :: sample, sample_suffix logical :: exist, write_raw, update_event, update_sqme type(simulation_t), target :: sim type(event_sample_data_t) :: input_data, data type(string_t) :: input_sample integer :: n_fmt type(string_t), dimension(:), allocatable :: sample_fmt type(string_t) :: input_format, input_ext, input_file type(string_t) :: lhef_extension, extension_hepmc, extension_lcio type(event_stream_array_t) :: es_array integer :: i, n_events <> var_list => cmd%local%var_list if (allocated (cmd%local%pn%alt_setup)) then allocate (alt_env (size (cmd%local%pn%alt_setup))) do i = 1, size (alt_env) call build_alt_setup (alt_env(i), cmd%local, & cmd%local%pn%alt_setup(i)%ptr) end do call sim%init (cmd%process_id, .false., .false., cmd%local, global, & alt_env) else call sim%init (cmd%process_id, .false., .false., cmd%local, global) end if call sim%compute_n_events (n_events) input_sample = eval_string (cmd%pn_filename, var_list) input_format = var_list%get_sval (& var_str ("$rescan_input_format")) sample_suffix = "" <> sample = var_list%get_sval (var_str ("$sample")) if (sample == "") then sample = sim%get_default_sample_name () // sample_suffix else sample = var_list%get_sval (var_str ("$sample")) // sample_suffix end if write_raw = var_list%get_lval (var_str ("?write_raw")) if (allocated (cmd%local%sample_fmt)) then n_fmt = size (cmd%local%sample_fmt) else n_fmt = 0 end if if (write_raw) then if (sample == input_sample) then call msg_error ("Rescan: ?write_raw = true: " & // "suppressing raw event output (filename clashes with input)") allocate (sample_fmt (n_fmt)) if (n_fmt > 0) sample_fmt = cmd%local%sample_fmt else allocate (sample_fmt (n_fmt + 1)) if (n_fmt > 0) sample_fmt(:n_fmt) = cmd%local%sample_fmt sample_fmt(n_fmt+1) = var_str ("raw") end if else allocate (sample_fmt (n_fmt)) if (n_fmt > 0) sample_fmt = cmd%local%sample_fmt end if update_event = & var_list%get_lval (var_str ("?update_event")) update_sqme = & var_list%get_lval (var_str ("?update_sqme")) if (update_event .or. update_sqme) then call msg_message ("Recalculating observables") if (update_sqme) then call msg_message ("Recalculating squared matrix elements") end if end if lhef_extension = & var_list%get_sval (var_str ("$lhef_extension")) extension_hepmc = & var_list%get_sval (var_str ("$extension_hepmc")) extension_lcio = & var_list%get_sval (var_str ("$extension_lcio")) select case (char (input_format)) case ("raw"); input_ext = "evx" call cmd%local%set_log & (var_str ("?recover_beams"), .false., is_known=.true.) case ("lhef"); input_ext = lhef_extension case ("hepmc"); input_ext = extension_hepmc case ("lcio"); input_ext = extension_lcio case default call msg_fatal ("rescan: input sample format '" // char (input_format) & // "' not supported") end select input_file = input_sample // "." // input_ext inquire (file = char (input_file), exist = exist) if (exist) then input_data = sim%get_data (alt = .false.) input_data%n_evt = n_events data = sim%get_data () data%n_evt = n_events input_data%md5sum_cfg = "" call es_array%init (sample, & sample_fmt, cmd%local, data, & input = input_format, input_sample = input_sample, & input_data = input_data, & allow_switch = .false.) call sim%rescan (n_events, es_array, global = cmd%local) call es_array%final () else call msg_fatal ("Rescan: event file '" & // char (input_file) // "' not found") end if if (allocated (alt_env)) then do i = 1, size (alt_env) call alt_env(i)%local_final () end do end if call sim%final () end subroutine cmd_rescan_execute @ %def cmd_rescan_execute @ MPI: Append rank id to sample name. <>= <>= logical :: mpi_logging integer :: rank, n_size <>= <>= call mpi_get_comm_id (n_size, rank) if (n_size > 1) then sample_suffix = var_str ("_") // str (rank) end if mpi_logging = (("vamp2" == char (var_list%get_sval (var_str ("$integration_method"))) & & .and. (n_size > 1)) & & .or. var_list%get_lval (var_str ("?mpi_logging"))) call mpi_set_logging (mpi_logging) @ \subsubsection{Parameters: number of iterations} Specify number of iterations and number of calls for one integration pass. <>= type, extends (command_t) :: cmd_iterations_t private integer :: n_pass = 0 type(parse_node_p), dimension(:), allocatable :: pn_expr_n_it type(parse_node_p), dimension(:), allocatable :: pn_expr_n_calls type(parse_node_p), dimension(:), allocatable :: pn_sexpr_adapt contains <> end type cmd_iterations_t @ %def cmd_iterations_t @ Output. Display the number of passes, which is known after compilation. <>= procedure :: write => cmd_iterations_write <>= subroutine cmd_iterations_write (cmd, unit, indent) class(cmd_iterations_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) select case (cmd%n_pass) case (0) write (u, "(1x,A)") "iterations: [empty]" case (1) write (u, "(1x,A,I0,A)") "iterations: ", cmd%n_pass, " pass" case default write (u, "(1x,A,I0,A)") "iterations: ", cmd%n_pass, " passes" end select end subroutine cmd_iterations_write @ %def cmd_iterations_write @ Compile. Initialize evaluation trees. <>= procedure :: compile => cmd_iterations_compile <>= subroutine cmd_iterations_compile (cmd, global) class(cmd_iterations_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_n_it, pn_n_calls, pn_adapt type(parse_node_t), pointer :: pn_it_spec, pn_calls_spec, pn_adapt_spec integer :: i pn_arg => parse_node_get_sub_ptr (cmd%pn, 3) if (associated (pn_arg)) then cmd%n_pass = parse_node_get_n_sub (pn_arg) allocate (cmd%pn_expr_n_it (cmd%n_pass)) allocate (cmd%pn_expr_n_calls (cmd%n_pass)) allocate (cmd%pn_sexpr_adapt (cmd%n_pass)) pn_it_spec => parse_node_get_sub_ptr (pn_arg) i = 1 do while (associated (pn_it_spec)) pn_n_it => parse_node_get_sub_ptr (pn_it_spec) pn_calls_spec => parse_node_get_next_ptr (pn_n_it) pn_n_calls => parse_node_get_sub_ptr (pn_calls_spec, 2) pn_adapt_spec => parse_node_get_next_ptr (pn_calls_spec) if (associated (pn_adapt_spec)) then pn_adapt => parse_node_get_sub_ptr (pn_adapt_spec, 2) else pn_adapt => null () end if cmd%pn_expr_n_it(i)%ptr => pn_n_it cmd%pn_expr_n_calls(i)%ptr => pn_n_calls cmd%pn_sexpr_adapt(i)%ptr => pn_adapt i = i + 1 pn_it_spec => parse_node_get_next_ptr (pn_it_spec) end do else allocate (cmd%pn_expr_n_it (0)) allocate (cmd%pn_expr_n_calls (0)) end if end subroutine cmd_iterations_compile @ %def cmd_iterations_compile @ Execute. Evaluate the trees and transfer the results to the iteration list in the runtime data set. <>= procedure :: execute => cmd_iterations_execute <>= subroutine cmd_iterations_execute (cmd, global) class(cmd_iterations_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list integer, dimension(cmd%n_pass) :: n_it, n_calls logical, dimension(cmd%n_pass) :: custom_adapt type(string_t), dimension(cmd%n_pass) :: adapt_code integer :: i var_list => global%get_var_list_ptr () do i = 1, cmd%n_pass n_it(i) = eval_int (cmd%pn_expr_n_it(i)%ptr, var_list) n_calls(i) = & eval_int (cmd%pn_expr_n_calls(i)%ptr, var_list) if (associated (cmd%pn_sexpr_adapt(i)%ptr)) then adapt_code(i) = & eval_string (cmd%pn_sexpr_adapt(i)%ptr, & var_list, is_known = custom_adapt(i)) else custom_adapt(i) = .false. end if end do call global%it_list%init (n_it, n_calls, custom_adapt, adapt_code) end subroutine cmd_iterations_execute @ %def cmd_iterations_execute @ \subsubsection{Range expressions} We need a special type for storing and evaluating range expressions. <>= integer, parameter :: STEP_NONE = 0 integer, parameter :: STEP_ADD = 1 integer, parameter :: STEP_SUB = 2 integer, parameter :: STEP_MUL = 3 integer, parameter :: STEP_DIV = 4 integer, parameter :: STEP_COMP_ADD = 11 integer, parameter :: STEP_COMP_MUL = 13 @ There is an abstract base type and two implementations: scan over integers and scan over reals. <>= type, abstract :: range_t type(parse_node_t), pointer :: pn_expr => null () type(parse_node_t), pointer :: pn_term => null () type(parse_node_t), pointer :: pn_factor => null () type(parse_node_t), pointer :: pn_value => null () type(parse_node_t), pointer :: pn_literal => null () type(parse_node_t), pointer :: pn_beg => null () type(parse_node_t), pointer :: pn_end => null () type(parse_node_t), pointer :: pn_step => null () type(eval_tree_t) :: expr_beg type(eval_tree_t) :: expr_end type(eval_tree_t) :: expr_step integer :: step_mode = 0 integer :: n_step = 0 contains <> end type range_t @ %def range_t @ These are the implementations: <>= type, extends (range_t) :: range_int_t integer :: i_beg = 0 integer :: i_end = 0 integer :: i_step = 0 contains <> end type range_int_t type, extends (range_t) :: range_real_t real(default) :: r_beg = 0 real(default) :: r_end = 0 real(default) :: r_step = 0 real(default) :: lr_beg = 0 real(default) :: lr_end = 0 real(default) :: lr_step = 0 contains <> end type range_real_t @ %def range_int_t range_real_t @ Finalize the allocated dummy node. The other nodes are just pointers. <>= procedure :: final => range_final <>= subroutine range_final (object) class(range_t), intent(inout) :: object if (associated (object%pn_expr)) then call parse_node_final (object%pn_expr, recursive = .false.) call parse_node_final (object%pn_term, recursive = .false.) call parse_node_final (object%pn_factor, recursive = .false.) call parse_node_final (object%pn_value, recursive = .false.) call parse_node_final (object%pn_literal, recursive = .false.) deallocate (object%pn_expr) deallocate (object%pn_term) deallocate (object%pn_factor) deallocate (object%pn_value) deallocate (object%pn_literal) end if end subroutine range_final @ %def range_final @ Output. <>= procedure (range_write), deferred :: write procedure :: base_write => range_write <>= procedure :: write => range_int_write <>= procedure :: write => range_real_write <>= subroutine range_write (object, unit) class(range_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) write (u, "(1x,A)") "Range specification:" if (associated (object%pn_expr)) then write (u, "(1x,A)") "Dummy value:" call parse_node_write_rec (object%pn_expr, u) end if if (associated (object%pn_beg)) then write (u, "(1x,A)") "Initial value:" call parse_node_write_rec (object%pn_beg, u) call object%expr_beg%write (u) if (associated (object%pn_end)) then write (u, "(1x,A)") "Final value:" call parse_node_write_rec (object%pn_end, u) call object%expr_end%write (u) if (associated (object%pn_step)) then write (u, "(1x,A)") "Step value:" call parse_node_write_rec (object%pn_step, u) select case (object%step_mode) case (STEP_ADD); write (u, "(1x,A)") "Step mode: +" case (STEP_SUB); write (u, "(1x,A)") "Step mode: -" case (STEP_MUL); write (u, "(1x,A)") "Step mode: *" case (STEP_DIV); write (u, "(1x,A)") "Step mode: /" case (STEP_COMP_ADD); write (u, "(1x,A)") "Division mode: +" case (STEP_COMP_MUL); write (u, "(1x,A)") "Division mode: *" end select end if end if else write (u, "(1x,A)") "Expressions: [undefined]" end if end subroutine range_write subroutine range_int_write (object, unit) class(range_int_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) call object%base_write (unit) write (u, "(1x,A)") "Range parameters:" write (u, "(3x,A,I0)") "i_beg = ", object%i_beg write (u, "(3x,A,I0)") "i_end = ", object%i_end write (u, "(3x,A,I0)") "i_step = ", object%i_step write (u, "(3x,A,I0)") "n_step = ", object%n_step end subroutine range_int_write subroutine range_real_write (object, unit) class(range_real_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) call object%base_write (unit) write (u, "(1x,A)") "Range parameters:" write (u, "(3x,A," // FMT_19 // ")") "r_beg = ", object%r_beg write (u, "(3x,A," // FMT_19 // ")") "r_end = ", object%r_end write (u, "(3x,A," // FMT_19 // ")") "r_step = ", object%r_end write (u, "(3x,A,I0)") "n_step = ", object%n_step end subroutine range_real_write @ %def range_write @ Initialize, given a range expression parse node. This is common to the implementations. <>= procedure :: init => range_init <>= subroutine range_init (range, pn) class(range_t), intent(out) :: range type(parse_node_t), intent(in), target :: pn type(parse_node_t), pointer :: pn_spec, pn_end, pn_step_spec, pn_op select case (char (parse_node_get_rule_key (pn))) case ("expr") case ("range_expr") range%pn_beg => parse_node_get_sub_ptr (pn) pn_spec => parse_node_get_next_ptr (range%pn_beg) if (associated (pn_spec)) then pn_end => parse_node_get_sub_ptr (pn_spec, 2) range%pn_end => pn_end pn_step_spec => parse_node_get_next_ptr (pn_end) if (associated (pn_step_spec)) then pn_op => parse_node_get_sub_ptr (pn_step_spec) range%pn_step => parse_node_get_next_ptr (pn_op) select case (char (parse_node_get_rule_key (pn_op))) case ("/+"); range%step_mode = STEP_ADD case ("/-"); range%step_mode = STEP_SUB case ("/*"); range%step_mode = STEP_MUL case ("//"); range%step_mode = STEP_DIV case ("/+/"); range%step_mode = STEP_COMP_ADD case ("/*/"); range%step_mode = STEP_COMP_MUL case default call range%write () call msg_bug ("Range: step mode not implemented") end select else range%step_mode = STEP_ADD end if else range%step_mode = STEP_NONE end if call range%create_value_node () case default call msg_bug ("range expression: node type '" & // char (parse_node_get_rule_key (pn)) & // "' not implemented") end select end subroutine range_init @ %def range_init @ This method manually creates a parse node (actually, a cascade of parse nodes) that hold a constant value as a literal. The idea is that this node is inserted as the right-hand side of a fake variable assignment, which is prepended to each scan iteration. Before the variable assignment is compiled and executed, we can manually reset the value of the literal and thus pretend that the loop variable is assigned this value. <>= procedure :: create_value_node => range_create_value_node <>= subroutine range_create_value_node (range) class(range_t), intent(inout) :: range allocate (range%pn_literal) allocate (range%pn_value) select type (range) type is (range_int_t) call parse_node_create_value (range%pn_literal, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("integer_literal")),& ival = 0) call parse_node_create_branch (range%pn_value, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("integer_value"))) type is (range_real_t) call parse_node_create_value (range%pn_literal, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("real_literal")),& rval = 0._default) call parse_node_create_branch (range%pn_value, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("real_value"))) class default call msg_bug ("range: create value node: type not implemented") end select call parse_node_append_sub (range%pn_value, range%pn_literal) call parse_node_freeze_branch (range%pn_value) allocate (range%pn_factor) call parse_node_create_branch (range%pn_factor, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("factor"))) call parse_node_append_sub (range%pn_factor, range%pn_value) call parse_node_freeze_branch (range%pn_factor) allocate (range%pn_term) call parse_node_create_branch (range%pn_term, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("term"))) call parse_node_append_sub (range%pn_term, range%pn_factor) call parse_node_freeze_branch (range%pn_term) allocate (range%pn_expr) call parse_node_create_branch (range%pn_expr, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("expr"))) call parse_node_append_sub (range%pn_expr, range%pn_term) call parse_node_freeze_branch (range%pn_expr) end subroutine range_create_value_node @ %def range_create_value_node @ Compile, given an environment. <>= procedure :: compile => range_compile <>= subroutine range_compile (range, global) class(range_t), intent(inout) :: range type(rt_data_t), intent(in), target :: global type(var_list_t), pointer :: var_list var_list => global%get_var_list_ptr () if (associated (range%pn_beg)) then call range%expr_beg%init_expr (range%pn_beg, var_list) if (associated (range%pn_end)) then call range%expr_end%init_expr (range%pn_end, var_list) if (associated (range%pn_step)) then call range%expr_step%init_expr (range%pn_step, var_list) end if end if end if end subroutine range_compile @ %def range_compile @ Evaluate: compute the actual bounds and parameters that determine the values that we can iterate. This is implementation-specific. <>= procedure (range_evaluate), deferred :: evaluate <>= abstract interface subroutine range_evaluate (range) import class(range_t), intent(inout) :: range end subroutine range_evaluate end interface @ %def range_evaluate @ The version for an integer variable. If the step is subtractive, we invert the sign and treat it as an additive step. For a multiplicative step, the step must be greater than one, and the initial and final values must be of same sign and strictly ordered. Analogously for a division step. <>= procedure :: evaluate => range_int_evaluate <>= subroutine range_int_evaluate (range) class(range_int_t), intent(inout) :: range integer :: ival if (associated (range%pn_beg)) then call range%expr_beg%evaluate () if (range%expr_beg%is_known ()) then range%i_beg = range%expr_beg%get_int () else call range%write () call msg_fatal & ("Range expression: initial value evaluates to unknown") end if if (associated (range%pn_end)) then call range%expr_end%evaluate () if (range%expr_end%is_known ()) then range%i_end = range%expr_end%get_int () if (associated (range%pn_step)) then call range%expr_step%evaluate () if (range%expr_step%is_known ()) then range%i_step = range%expr_step%get_int () select case (range%step_mode) case (STEP_SUB); range%i_step = - range%i_step end select else call range%write () call msg_fatal & ("Range expression: step value evaluates to unknown") end if else range%i_step = 1 end if else call range%write () call msg_fatal & ("Range expression: final value evaluates to unknown") end if else range%i_end = range%i_beg range%i_step = 1 end if select case (range%step_mode) case (STEP_NONE) range%n_step = 1 case (STEP_ADD, STEP_SUB) if (range%i_step /= 0) then if (range%i_beg == range%i_end) then range%n_step = 1 else if (sign (1, range%i_end - range%i_beg) & == sign (1, range%i_step)) then range%n_step = (range%i_end - range%i_beg) / range%i_step + 1 else range%n_step = 0 end if else call msg_fatal ("range evaluation (add): step value is zero") end if case (STEP_MUL) if (range%i_step > 1) then if (range%i_beg == range%i_end) then range%n_step = 1 else if (range%i_beg == 0) then call msg_fatal ("range evaluation (mul): initial value is zero") else if (sign (1, range%i_beg) == sign (1, range%i_end) & .and. abs (range%i_beg) < abs (range%i_end)) then range%n_step = 0 ival = range%i_beg do while (abs (ival) <= abs (range%i_end)) range%n_step = range%n_step + 1 ival = ival * range%i_step end do else range%n_step = 0 end if else call msg_fatal & ("range evaluation (mult): step value is one or less") end if case (STEP_DIV) if (range%i_step > 1) then if (range%i_beg == range%i_end) then range%n_step = 1 else if (sign (1, range%i_beg) == sign (1, range%i_end) & .and. abs (range%i_beg) > abs (range%i_end)) then range%n_step = 0 ival = range%i_beg do while (abs (ival) >= abs (range%i_end)) range%n_step = range%n_step + 1 if (ival == 0) exit ival = ival / range%i_step end do else range%n_step = 0 end if else call msg_fatal & ("range evaluation (div): step value is one or less") end if case (STEP_COMP_ADD) call msg_fatal ("range evaluation: & &step mode /+/ not allowed for integer variable") case (STEP_COMP_MUL) call msg_fatal ("range evaluation: & &step mode /*/ not allowed for integer variable") case default call range%write () call msg_bug ("range evaluation: step mode not implemented") end select end if end subroutine range_int_evaluate @ %def range_int_evaluate @ The version for a real variable. <>= procedure :: evaluate => range_real_evaluate <>= subroutine range_real_evaluate (range) class(range_real_t), intent(inout) :: range if (associated (range%pn_beg)) then call range%expr_beg%evaluate () if (range%expr_beg%is_known ()) then range%r_beg = range%expr_beg%get_real () else call range%write () call msg_fatal & ("Range expression: initial value evaluates to unknown") end if if (associated (range%pn_end)) then call range%expr_end%evaluate () if (range%expr_end%is_known ()) then range%r_end = range%expr_end%get_real () if (associated (range%pn_step)) then if (range%expr_step%is_known ()) then select case (range%step_mode) case (STEP_ADD, STEP_SUB, STEP_MUL, STEP_DIV) call range%expr_step%evaluate () range%r_step = range%expr_step%get_real () select case (range%step_mode) case (STEP_SUB); range%r_step = - range%r_step end select case (STEP_COMP_ADD, STEP_COMP_MUL) range%n_step = & max (range%expr_step%get_int (), 0) end select else call range%write () call msg_fatal & ("Range expression: step value evaluates to unknown") end if else call range%write () call msg_fatal & ("Range expression (real): step value must be provided") end if else call range%write () call msg_fatal & ("Range expression: final value evaluates to unknown") end if else range%r_end = range%r_beg range%r_step = 1 end if select case (range%step_mode) case (STEP_NONE) range%n_step = 1 case (STEP_ADD, STEP_SUB) if (range%r_step /= 0) then if (sign (1._default, range%r_end - range%r_beg) & == sign (1._default, range%r_step)) then range%n_step = & nint ((range%r_end - range%r_beg) / range%r_step + 1) else range%n_step = 0 end if else call msg_fatal ("range evaluation (add): step value is zero") end if case (STEP_MUL) if (range%r_step > 1) then if (range%r_beg == 0 .or. range%r_end == 0) then call msg_fatal ("range evaluation (mul): bound is zero") else if (sign (1._default, range%r_beg) & == sign (1._default, range%r_end) & .and. abs (range%r_beg) <= abs (range%r_end)) then range%lr_beg = log (abs (range%r_beg)) range%lr_end = log (abs (range%r_end)) range%lr_step = log (range%r_step) range%n_step = nint & (abs ((range%lr_end - range%lr_beg) / range%lr_step) + 1) else range%n_step = 0 end if else call msg_fatal & ("range evaluation (mult): step value is one or less") end if case (STEP_DIV) if (range%r_step > 1) then if (range%r_beg == 0 .or. range%r_end == 0) then call msg_fatal ("range evaluation (div): bound is zero") else if (sign (1._default, range%r_beg) & == sign (1._default, range%r_end) & .and. abs (range%r_beg) >= abs (range%r_end)) then range%lr_beg = log (abs (range%r_beg)) range%lr_end = log (abs (range%r_end)) range%lr_step = -log (range%r_step) range%n_step = nint & (abs ((range%lr_end - range%lr_beg) / range%lr_step) + 1) else range%n_step = 0 end if else call msg_fatal & ("range evaluation (mult): step value is one or less") end if case (STEP_COMP_ADD) ! Number of steps already known case (STEP_COMP_MUL) ! Number of steps already known if (range%r_beg == 0 .or. range%r_end == 0) then call msg_fatal ("range evaluation (mul): bound is zero") else if (sign (1._default, range%r_beg) & == sign (1._default, range%r_end)) then range%lr_beg = log (abs (range%r_beg)) range%lr_end = log (abs (range%r_end)) else range%n_step = 0 end if case default call range%write () call msg_bug ("range evaluation: step mode not implemented") end select end if end subroutine range_real_evaluate @ %def range_real_evaluate @ Return the number of iterations: <>= procedure :: get_n_iterations => range_get_n_iterations <>= function range_get_n_iterations (range) result (n) class(range_t), intent(in) :: range integer :: n n = range%n_step end function range_get_n_iterations @ %def range_get_n_iterations @ Compute the value for iteration [[i]] and store it in the embedded token. <>= procedure (range_set_value), deferred :: set_value <>= abstract interface subroutine range_set_value (range, i) import class(range_t), intent(inout) :: range integer, intent(in) :: i end subroutine range_set_value end interface @ %def range_set_value @ In the integer case, we compute the value directly for additive step. For multiplicative step, we perform a loop in the same way as above, where the number of iteration was determined. <>= procedure :: set_value => range_int_set_value <>= subroutine range_int_set_value (range, i) class(range_int_t), intent(inout) :: range integer, intent(in) :: i integer :: k, ival select case (range%step_mode) case (STEP_NONE) ival = range%i_beg case (STEP_ADD, STEP_SUB) ival = range%i_beg + (i - 1) * range%i_step case (STEP_MUL) ival = range%i_beg do k = 1, i - 1 ival = ival * range%i_step end do case (STEP_DIV) ival = range%i_beg do k = 1, i - 1 ival = ival / range%i_step end do case default call range%write () call msg_bug ("range iteration: step mode not implemented") end select call parse_node_set_value (range%pn_literal, ival = ival) end subroutine range_int_set_value @ %def range_int_set_value @ In the integer case, we compute the value directly for additive step. For multiplicative step, we perform a loop in the same way as above, where the number of iteration was determined. <>= procedure :: set_value => range_real_set_value <>= subroutine range_real_set_value (range, i) class(range_real_t), intent(inout) :: range integer, intent(in) :: i real(default) :: rval, x select case (range%step_mode) case (STEP_NONE) rval = range%r_beg case (STEP_ADD, STEP_SUB, STEP_COMP_ADD) if (range%n_step > 1) then x = real (i - 1, default) / (range%n_step - 1) else x = 1._default / 2 end if rval = x * range%r_end + (1 - x) * range%r_beg case (STEP_MUL, STEP_DIV, STEP_COMP_MUL) if (range%n_step > 1) then x = real (i - 1, default) / (range%n_step - 1) else x = 1._default / 2 end if rval = sign & (exp (x * range%lr_end + (1 - x) * range%lr_beg), range%r_beg) case default call range%write () call msg_bug ("range iteration: step mode not implemented") end select call parse_node_set_value (range%pn_literal, rval = rval) end subroutine range_real_set_value @ %def range_real_set_value @ \subsubsection{Scan over parameters and other objects} The scan command allocates a new parse node for the variable assignment (the lhs). The rhs of this parse node is assigned from the available rhs expressions in the scan list, one at a time, so the compiled parse node can be prepended to the scan body. <>= type, extends (command_t) :: cmd_scan_t private type(string_t) :: name integer :: n_values = 0 type(parse_node_p), dimension(:), allocatable :: scan_cmd class(range_t), dimension(:), allocatable :: range contains <> end type cmd_scan_t @ %def cmd_scan_t @ Finalizer. The auxiliary parse nodes that we have constructed have to be treated carefully: the embedded pointers all point to persistent objects somewhere else and should not be finalized, so we should not call the finalizer recursively. <>= procedure :: final => cmd_scan_final <>= recursive subroutine cmd_scan_final (cmd) class(cmd_scan_t), intent(inout) :: cmd type(parse_node_t), pointer :: pn_var_single, pn_decl_single type(string_t) :: key integer :: i if (allocated (cmd%scan_cmd)) then do i = 1, size (cmd%scan_cmd) pn_var_single => parse_node_get_sub_ptr (cmd%scan_cmd(i)%ptr) key = parse_node_get_rule_key (pn_var_single) select case (char (key)) case ("scan_string_decl", "scan_log_decl") pn_decl_single => parse_node_get_sub_ptr (pn_var_single, 2) call parse_node_final (pn_decl_single, recursive=.false.) deallocate (pn_decl_single) end select call parse_node_final (pn_var_single, recursive=.false.) deallocate (pn_var_single) end do deallocate (cmd%scan_cmd) end if if (allocated (cmd%range)) then do i = 1, size (cmd%range) call cmd%range(i)%final () end do end if end subroutine cmd_scan_final @ %def cmd_scan_final @ Output. <>= procedure :: write => cmd_scan_write <>= subroutine cmd_scan_write (cmd, unit, indent) class(cmd_scan_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,1x,A,1x,'(',I0,')')") "scan:", char (cmd%name), & cmd%n_values end subroutine cmd_scan_write @ %def cmd_scan_write @ Compile the scan command. We construct a new parse node that implements the variable assignment for a single element on the rhs, instead of the whole list that we get from the original parse tree. By simply copying the node, we copy all pointers and inherit the targets from the original. During execution, we should replace the rhs by the stored rhs pointers (the list elements), one by one, then (re)compile the redefined node. <>= procedure :: compile => cmd_scan_compile <>= recursive subroutine cmd_scan_compile (cmd, global) class(cmd_scan_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list type(parse_node_t), pointer :: pn_var, pn_body, pn_body_first type(parse_node_t), pointer :: pn_decl, pn_name type(parse_node_t), pointer :: pn_arg, pn_scan_cmd, pn_rhs type(parse_node_t), pointer :: pn_decl_single, pn_var_single type(syntax_rule_t), pointer :: var_rule_decl, var_rule type(string_t) :: key integer :: var_type integer :: i if (debug_on) call msg_debug (D_CORE, "cmd_scan_compile") if (debug_active (D_CORE)) call parse_node_write_rec (cmd%pn) pn_var => parse_node_get_sub_ptr (cmd%pn, 2) pn_body => parse_node_get_next_ptr (pn_var) if (associated (pn_body)) then pn_body_first => parse_node_get_sub_ptr (pn_body) else pn_body_first => null () end if key = parse_node_get_rule_key (pn_var) select case (char (key)) case ("scan_num") pn_name => parse_node_get_sub_ptr (pn_var) cmd%name = parse_node_get_string (pn_name) var_rule => syntax_get_rule_ptr (syntax_cmd_list, var_str ("cmd_num")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_int") pn_name => parse_node_get_sub_ptr (pn_var, 2) cmd%name = parse_node_get_string (pn_name) var_rule => syntax_get_rule_ptr (syntax_cmd_list, var_str ("cmd_int")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_real") pn_name => parse_node_get_sub_ptr (pn_var, 2) cmd%name = parse_node_get_string (pn_name) var_rule => syntax_get_rule_ptr (syntax_cmd_list, var_str ("cmd_real")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_complex") pn_name => parse_node_get_sub_ptr (pn_var, 2) cmd%name = parse_node_get_string (pn_name) var_rule => syntax_get_rule_ptr (syntax_cmd_list, var_str("cmd_complex")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_alias") pn_name => parse_node_get_sub_ptr (pn_var, 2) cmd%name = parse_node_get_string (pn_name) var_rule => syntax_get_rule_ptr (syntax_cmd_list, var_str ("cmd_alias")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_string_decl") pn_decl => parse_node_get_sub_ptr (pn_var, 2) pn_name => parse_node_get_sub_ptr (pn_decl, 2) cmd%name = parse_node_get_string (pn_name) var_rule_decl => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_string")) var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_string_decl")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_log_decl") pn_decl => parse_node_get_sub_ptr (pn_var, 2) pn_name => parse_node_get_sub_ptr (pn_decl, 2) cmd%name = parse_node_get_string (pn_name) var_rule_decl => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_log")) var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_log_decl")) pn_arg => parse_node_get_next_ptr (pn_name, 2) case ("scan_cuts") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_cuts")) cmd%name = "cuts" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_weight") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_weight")) cmd%name = "weight" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_scale") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_scale")) cmd%name = "scale" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_ren_scale") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_ren_scale")) cmd%name = "renormalization_scale" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_fac_scale") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_fac_scale")) cmd%name = "factorization_scale" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_selection") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_selection")) cmd%name = "selection" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_reweight") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_reweight")) cmd%name = "reweight" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_analysis") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_analysis")) cmd%name = "analysis" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_model") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_model")) cmd%name = "model" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case ("scan_library") var_rule => syntax_get_rule_ptr (syntax_cmd_list, & var_str ("cmd_library")) cmd%name = "library" pn_arg => parse_node_get_sub_ptr (pn_var, 3) case default call msg_bug ("scan: case '" // char (key) // "' not implemented") end select if (associated (pn_arg)) then cmd%n_values = parse_node_get_n_sub (pn_arg) end if var_list => global%get_var_list_ptr () allocate (cmd%scan_cmd (cmd%n_values)) select case (char (key)) case ("scan_num") var_type = & var_list%get_type (cmd%name) select case (var_type) case (V_INT) allocate (range_int_t :: cmd%range (cmd%n_values)) case (V_REAL) allocate (range_real_t :: cmd%range (cmd%n_values)) case (V_CMPLX) call msg_fatal ("scan over complex variable not implemented") case (V_NONE) call msg_fatal ("scan: variable '" // char (cmd%name) //"' undefined") case default call msg_bug ("scan: impossible variable type") end select case ("scan_int") allocate (range_int_t :: cmd%range (cmd%n_values)) case ("scan_real") allocate (range_real_t :: cmd%range (cmd%n_values)) case ("scan_complex") call msg_fatal ("scan over complex variable not implemented") end select i = 1 if (associated (pn_arg)) then pn_rhs => parse_node_get_sub_ptr (pn_arg) else pn_rhs => null () end if do while (associated (pn_rhs)) allocate (pn_scan_cmd) call parse_node_create_branch (pn_scan_cmd, & syntax_get_rule_ptr (syntax_cmd_list, var_str ("command_list"))) allocate (pn_var_single) pn_var_single = pn_var call parse_node_replace_rule (pn_var_single, var_rule) select case (char (key)) case ("scan_num", "scan_int", "scan_real", & "scan_complex", "scan_alias", & "scan_cuts", "scan_weight", & "scan_scale", "scan_ren_scale", "scan_fac_scale", & "scan_selection", "scan_reweight", "scan_analysis", & "scan_model", "scan_library") if (allocated (cmd%range)) then call cmd%range(i)%init (pn_rhs) call parse_node_replace_last_sub & (pn_var_single, cmd%range(i)%pn_expr) else call parse_node_replace_last_sub (pn_var_single, pn_rhs) end if case ("scan_string_decl", "scan_log_decl") allocate (pn_decl_single) pn_decl_single = pn_decl call parse_node_replace_rule (pn_decl_single, var_rule_decl) call parse_node_replace_last_sub (pn_decl_single, pn_rhs) call parse_node_freeze_branch (pn_decl_single) call parse_node_replace_last_sub (pn_var_single, pn_decl_single) case default call msg_bug ("scan: case '" // char (key) & // "' broken") end select call parse_node_freeze_branch (pn_var_single) call parse_node_append_sub (pn_scan_cmd, pn_var_single) call parse_node_append_sub (pn_scan_cmd, pn_body_first) call parse_node_freeze_branch (pn_scan_cmd) cmd%scan_cmd(i)%ptr => pn_scan_cmd i = i + 1 pn_rhs => parse_node_get_next_ptr (pn_rhs) end do if (debug_active (D_CORE)) then do i = 1, cmd%n_values print *, "scan command ", i call parse_node_write_rec (cmd%scan_cmd(i)%ptr) if (allocated (cmd%range)) call cmd%range(i)%write () end do print *, "original" call parse_node_write_rec (cmd%pn) end if end subroutine cmd_scan_compile @ %def cmd_scan_compile @ Execute the loop for all values in the step list. We use the parse trees with single variable assignment that we have stored, to iteratively create a local environment, execute the stored commands, and destroy it again. When we encounter a range object, we execute the commands for each value that this object provides. Computing this value has the side effect of modifying the rhs of the variable assignment that heads the local command list, directly in the local parse tree. <>= procedure :: execute => cmd_scan_execute <>= recursive subroutine cmd_scan_execute (cmd, global) class(cmd_scan_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(rt_data_t), allocatable :: local integer :: i, j do i = 1, cmd%n_values if (allocated (cmd%range)) then call cmd%range(i)%compile (global) call cmd%range(i)%evaluate () do j = 1, cmd%range(i)%get_n_iterations () call cmd%range(i)%set_value (j) allocate (local) call build_alt_setup (local, global, cmd%scan_cmd(i)%ptr) call local%local_final () deallocate (local) end do else allocate (local) call build_alt_setup (local, global, cmd%scan_cmd(i)%ptr) call local%local_final () deallocate (local) end if end do end subroutine cmd_scan_execute @ %def cmd_scan_execute @ \subsubsection{Conditionals} Conditionals are implemented as a list that is compiled and evaluated recursively; this allows for a straightforward representation of [[else if]] constructs. A [[cmd_if_t]] object can hold either an [[else_if]] clause which is another object of this type, or an [[else_body]], but not both. If- or else-bodies are no scoping units, so all data remain global and no copy-in copy-out is needed. <>= type, extends (command_t) :: cmd_if_t private type(parse_node_t), pointer :: pn_if_lexpr => null () type(command_list_t), pointer :: if_body => null () type(cmd_if_t), dimension(:), pointer :: elsif_cmd => null () type(command_list_t), pointer :: else_body => null () contains <> end type cmd_if_t @ %def cmd_if_t @ Finalizer. There are no local options, therefore we can simply override the default finalizer. <>= procedure :: final => cmd_if_final <>= recursive subroutine cmd_if_final (cmd) class(cmd_if_t), intent(inout) :: cmd integer :: i if (associated (cmd%if_body)) then call command_list_final (cmd%if_body) deallocate (cmd%if_body) end if if (associated (cmd%elsif_cmd)) then do i = 1, size (cmd%elsif_cmd) call cmd_if_final (cmd%elsif_cmd(i)) end do deallocate (cmd%elsif_cmd) end if if (associated (cmd%else_body)) then call command_list_final (cmd%else_body) deallocate (cmd%else_body) end if end subroutine cmd_if_final @ %def cmd_if_final @ Output. Recursively write the command lists. <>= procedure :: write => cmd_if_write <>= subroutine cmd_if_write (cmd, unit, indent) class(cmd_if_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, ind, i u = given_output_unit (unit); if (u < 0) return ind = 0; if (present (indent)) ind = indent call write_indent (u, indent) write (u, "(A)") "if then" if (associated (cmd%if_body)) then call cmd%if_body%write (unit, ind + 1) end if if (associated (cmd%elsif_cmd)) then do i = 1, size (cmd%elsif_cmd) call write_indent (u, indent) write (u, "(A)") "elsif then" if (associated (cmd%elsif_cmd(i)%if_body)) then call cmd%elsif_cmd(i)%if_body%write (unit, ind + 1) end if end do end if if (associated (cmd%else_body)) then call write_indent (u, indent) write (u, "(A)") "else" call cmd%else_body%write (unit, ind + 1) end if end subroutine cmd_if_write @ %def cmd_if_write @ Compile the conditional. <>= procedure :: compile => cmd_if_compile <>= recursive subroutine cmd_if_compile (cmd, global) class(cmd_if_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_lexpr, pn_body type(parse_node_t), pointer :: pn_elsif_clauses, pn_cmd_elsif type(parse_node_t), pointer :: pn_else_clause, pn_cmd_else integer :: i, n_elsif pn_lexpr => parse_node_get_sub_ptr (cmd%pn, 2) cmd%pn_if_lexpr => pn_lexpr pn_body => parse_node_get_next_ptr (pn_lexpr, 2) select case (char (parse_node_get_rule_key (pn_body))) case ("command_list") allocate (cmd%if_body) call cmd%if_body%compile (pn_body, global) pn_elsif_clauses => parse_node_get_next_ptr (pn_body) case default pn_elsif_clauses => pn_body end select select case (char (parse_node_get_rule_key (pn_elsif_clauses))) case ("elsif_clauses") n_elsif = parse_node_get_n_sub (pn_elsif_clauses) allocate (cmd%elsif_cmd (n_elsif)) pn_cmd_elsif => parse_node_get_sub_ptr (pn_elsif_clauses) do i = 1, n_elsif pn_lexpr => parse_node_get_sub_ptr (pn_cmd_elsif, 2) cmd%elsif_cmd(i)%pn_if_lexpr => pn_lexpr pn_body => parse_node_get_next_ptr (pn_lexpr, 2) if (associated (pn_body)) then allocate (cmd%elsif_cmd(i)%if_body) call cmd%elsif_cmd(i)%if_body%compile (pn_body, global) end if pn_cmd_elsif => parse_node_get_next_ptr (pn_cmd_elsif) end do pn_else_clause => parse_node_get_next_ptr (pn_elsif_clauses) case default pn_else_clause => pn_elsif_clauses end select select case (char (parse_node_get_rule_key (pn_else_clause))) case ("else_clause") pn_cmd_else => parse_node_get_sub_ptr (pn_else_clause) pn_body => parse_node_get_sub_ptr (pn_cmd_else, 2) if (associated (pn_body)) then allocate (cmd%else_body) call cmd%else_body%compile (pn_body, global) end if end select end subroutine cmd_if_compile @ %def global @ (Recursively) execute the condition. Context remains global in all cases. <>= procedure :: execute => cmd_if_execute <>= recursive subroutine cmd_if_execute (cmd, global) class(cmd_if_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list logical :: lval, is_known integer :: i var_list => global%get_var_list_ptr () lval = eval_log (cmd%pn_if_lexpr, var_list, is_known=is_known) if (is_known) then if (lval) then if (associated (cmd%if_body)) then call cmd%if_body%execute (global) end if return end if else call error_undecided () return end if if (associated (cmd%elsif_cmd)) then SCAN_ELSIF: do i = 1, size (cmd%elsif_cmd) lval = eval_log (cmd%elsif_cmd(i)%pn_if_lexpr, var_list, & is_known=is_known) if (is_known) then if (lval) then if (associated (cmd%elsif_cmd(i)%if_body)) then call cmd%elsif_cmd(i)%if_body%execute (global) end if return end if else call error_undecided () return end if end do SCAN_ELSIF end if if (associated (cmd%else_body)) then call cmd%else_body%execute (global) end if contains subroutine error_undecided () call msg_error ("Undefined result of cmditional expression: " & // "neither branch will be executed") end subroutine error_undecided end subroutine cmd_if_execute @ %def cmd_if_execute @ \subsubsection{Include another command-list file} The include command allocates a local parse tree. This must not be deleted before the command object itself is deleted, since pointers may point to subobjects of it. <>= type, extends (command_t) :: cmd_include_t private type(string_t) :: file type(command_list_t), pointer :: command_list => null () type(parse_tree_t) :: parse_tree contains <> end type cmd_include_t @ %def cmd_include_t @ Finalizer: delete the command list. No options, so we can simply override the default finalizer. <>= procedure :: final => cmd_include_final <>= subroutine cmd_include_final (cmd) class(cmd_include_t), intent(inout) :: cmd call parse_tree_final (cmd%parse_tree) if (associated (cmd%command_list)) then call cmd%command_list%final () deallocate (cmd%command_list) end if end subroutine cmd_include_final @ %def cmd_include_final @ Write: display the command list as-is, if allocated. <>= procedure :: write => cmd_include_write <>= subroutine cmd_include_write (cmd, unit, indent) class(cmd_include_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, ind u = given_output_unit (unit) ind = 0; if (present (indent)) ind = indent call write_indent (u, indent) write (u, "(A,A,A,A)") "include ", '"', char (cmd%file), '"' if (associated (cmd%command_list)) then call cmd%command_list%write (u, ind + 1) end if end subroutine cmd_include_write @ %def cmd_include_write @ Compile file contents: First parse the file, then immediately compile its contents. Use the global data set. <>= procedure :: compile => cmd_include_compile <>= subroutine cmd_include_compile (cmd, global) class(cmd_include_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_file type(string_t) :: file logical :: exist integer :: u type(stream_t), target :: stream type(lexer_t) :: lexer pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) pn_file => parse_node_get_sub_ptr (pn_arg) file = parse_node_get_string (pn_file) inquire (file=char(file), exist=exist) if (exist) then cmd%file = file else cmd%file = global%os_data%whizard_cutspath // "/" // file inquire (file=char(cmd%file), exist=exist) if (.not. exist) then call msg_error ("Include file '" // char (file) // "' not found") return end if end if u = free_unit () call lexer_init_cmd_list (lexer, global%lexer) call stream_init (stream, char (cmd%file)) call lexer_assign_stream (lexer, stream) call parse_tree_init (cmd%parse_tree, syntax_cmd_list, lexer) call stream_final (stream) call lexer_final (lexer) close (u) allocate (cmd%command_list) call cmd%command_list%compile (cmd%parse_tree%get_root_ptr (), & global) end subroutine cmd_include_compile @ %def cmd_include_compile @ Execute file contents in the global context. <>= procedure :: execute => cmd_include_execute <>= subroutine cmd_include_execute (cmd, global) class(cmd_include_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global if (associated (cmd%command_list)) then call msg_message & ("Including Sindarin from '" // char (cmd%file) // "'") call cmd%command_list%execute (global) call msg_message & ("End of included '" // char (cmd%file) // "'") end if end subroutine cmd_include_execute @ %def cmd_include_execute @ \subsubsection{Export values} This command exports the current values of variables or other objects to the surrounding scope. By default, a scope enclosed by braces keeps all objects local to it. The [[export]] command exports the values that are generated within the scope to the corresponding object in the outer scope. The allowed set of exportable objects is, in principle, the same as the set of objects that the [[show]] command supports. This includes some convenience abbreviations. TODO: The initial implementation inherits syntax from [[show]], but supports only the [[results]] pseudo-object. The results (i.e., the process stack) is appended to the outer process stack instead of being discarded. The behavior of the [[export]] command for other object kinds is to be defined on a case-by-case basis. It may involve replacing the outer value or, instead, doing some sort of appending or reduction. <>= type, extends (command_t) :: cmd_export_t private type(string_t), dimension(:), allocatable :: name contains <> end type cmd_export_t @ %def cmd_export_t @ Output: list the object names, not values. <>= procedure :: write => cmd_export_write <>= subroutine cmd_export_write (cmd, unit, indent) class(cmd_export_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u, i u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A)", advance="no") "export: " if (allocated (cmd%name)) then do i = 1, size (cmd%name) write (u, "(1x,A)", advance="no") char (cmd%name(i)) end do write (u, *) else write (u, "(5x,A)") "[undefined]" end if end subroutine cmd_export_write @ %def cmd_export_write @ Compile. Allocate an array which is filled with the names of the variables to export. <>= procedure :: compile => cmd_export_compile <>= subroutine cmd_export_compile (cmd, global) class(cmd_export_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg, pn_var, pn_prefix, pn_name type(string_t) :: key integer :: i, n_args pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) if (associated (pn_arg)) then select case (char (parse_node_get_rule_key (pn_arg))) case ("show_arg") cmd%pn_opt => parse_node_get_next_ptr (pn_arg) case default cmd%pn_opt => pn_arg pn_arg => null () end select end if call cmd%compile_options (global) if (associated (pn_arg)) then n_args = parse_node_get_n_sub (pn_arg) allocate (cmd%name (n_args)) pn_var => parse_node_get_sub_ptr (pn_arg) i = 0 do while (associated (pn_var)) i = i + 1 select case (char (parse_node_get_rule_key (pn_var))) case ("model", "library", "beams", "iterations", & "cuts", "weight", "int", "real", "complex", & "scale", "factorization_scale", "renormalization_scale", & "selection", "reweight", "analysis", "pdg", & "stable", "unstable", "polarized", "unpolarized", & "results", "expect", "intrinsic", "string", "logical") cmd%name(i) = parse_node_get_key (pn_var) case ("result_var") pn_prefix => parse_node_get_sub_ptr (pn_var) pn_name => parse_node_get_next_ptr (pn_prefix) if (associated (pn_name)) then cmd%name(i) = parse_node_get_key (pn_prefix) & // "(" // parse_node_get_string (pn_name) // ")" else cmd%name(i) = parse_node_get_key (pn_prefix) end if case ("log_var", "string_var", "alias_var") pn_prefix => parse_node_get_sub_ptr (pn_var) pn_name => parse_node_get_next_ptr (pn_prefix) key = parse_node_get_key (pn_prefix) if (associated (pn_name)) then select case (char (parse_node_get_rule_key (pn_name))) case ("var_name") select case (char (key)) case ("?", "$") ! $ sign cmd%name(i) = key // parse_node_get_string (pn_name) case ("alias") cmd%name(i) = parse_node_get_string (pn_name) end select case default call parse_node_mismatch & ("var_name", pn_name) end select else cmd%name(i) = key end if case default cmd%name(i) = parse_node_get_string (pn_var) end select !!! restriction imposed by current lack of implementation select case (char (parse_node_get_rule_key (pn_var))) case ("results") case default call msg_fatal ("export: object (type) '" & // char (parse_node_get_rule_key (pn_var)) & // "' not supported yet") end select pn_var => parse_node_get_next_ptr (pn_var) end do else allocate (cmd%name (0)) end if end subroutine cmd_export_compile @ %def cmd_export_compile @ Execute. Scan the list of objects to export. <>= procedure :: execute => cmd_export_execute <>= subroutine cmd_export_execute (cmd, global) class(cmd_export_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global call global%append_exports (cmd%name) end subroutine cmd_export_execute @ %def cmd_export_execute @ \subsubsection{Quit command execution} The code is the return code of the whole program if it is terminated by this command. <>= type, extends (command_t) :: cmd_quit_t private logical :: has_code = .false. type(parse_node_t), pointer :: pn_code_expr => null () contains <> end type cmd_quit_t @ %def cmd_quit_t @ Output. <>= procedure :: write => cmd_quit_write <>= subroutine cmd_quit_write (cmd, unit, indent) class(cmd_quit_t), intent(in) :: cmd integer, intent(in), optional :: unit, indent integer :: u u = given_output_unit (unit); if (u < 0) return call write_indent (u, indent) write (u, "(1x,A,L1)") "quit: has_code = ", cmd%has_code end subroutine cmd_quit_write @ %def cmd_quit_write @ Compile: allocate a [[quit]] object which serves as a placeholder. <>= procedure :: compile => cmd_quit_compile <>= subroutine cmd_quit_compile (cmd, global) class(cmd_quit_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_arg pn_arg => parse_node_get_sub_ptr (cmd%pn, 2) if (associated (pn_arg)) then cmd%pn_code_expr => parse_node_get_sub_ptr (pn_arg) cmd%has_code = .true. end if end subroutine cmd_quit_compile @ %def cmd_quit_compile @ Execute: The quit command does not execute anything, it just stops command execution. This is achieved by setting quit flag and quit code in the global variable list. However, the return code, if present, is an expression which has to be evaluated. <>= procedure :: execute => cmd_quit_execute <>= subroutine cmd_quit_execute (cmd, global) class(cmd_quit_t), intent(inout) :: cmd type(rt_data_t), intent(inout), target :: global type(var_list_t), pointer :: var_list logical :: is_known var_list => global%get_var_list_ptr () if (cmd%has_code) then global%quit_code = eval_int (cmd%pn_code_expr, var_list, & is_known=is_known) if (.not. is_known) then call msg_error ("Undefined return code of quit/exit command") end if end if global%quit = .true. end subroutine cmd_quit_execute @ %def cmd_quit_execute @ \subsection{The command list} The command list holds a list of commands and relevant global data. <>= public :: command_list_t <>= type :: command_list_t ! not private anymore as required by the whizard-c-interface class(command_t), pointer :: first => null () class(command_t), pointer :: last => null () contains <> end type command_list_t @ %def command_list_t @ Output. <>= procedure :: write => command_list_write <>= recursive subroutine command_list_write (cmd_list, unit, indent) class(command_list_t), intent(in) :: cmd_list integer, intent(in), optional :: unit, indent class(command_t), pointer :: cmd cmd => cmd_list%first do while (associated (cmd)) call cmd%write (unit, indent) cmd => cmd%next end do end subroutine command_list_write @ %def command_list_write @ Append a new command to the list and free the original pointer. <>= procedure :: append => command_list_append <>= subroutine command_list_append (cmd_list, command) class(command_list_t), intent(inout) :: cmd_list class(command_t), intent(inout), pointer :: command if (associated (cmd_list%last)) then cmd_list%last%next => command else cmd_list%first => command end if cmd_list%last => command command => null () end subroutine command_list_append @ %def command_list_append @ Finalize. <>= procedure :: final => command_list_final <>= recursive subroutine command_list_final (cmd_list) class(command_list_t), intent(inout) :: cmd_list class(command_t), pointer :: command do while (associated (cmd_list%first)) command => cmd_list%first cmd_list%first => cmd_list%first%next call command%final () deallocate (command) end do cmd_list%last => null () end subroutine command_list_final @ %def command_list_final @ \subsection{Compiling the parse tree} Transform a parse tree into a command list. Initialization is assumed to be done. After each command, we set a breakpoint. <>= procedure :: compile => command_list_compile <>= recursive subroutine command_list_compile (cmd_list, pn, global) class(command_list_t), intent(inout), target :: cmd_list type(parse_node_t), intent(in), target :: pn type(rt_data_t), intent(inout), target :: global type(parse_node_t), pointer :: pn_cmd class(command_t), pointer :: command integer :: i pn_cmd => parse_node_get_sub_ptr (pn) do i = 1, parse_node_get_n_sub (pn) call dispatch_command (command, pn_cmd) call command%compile (global) call cmd_list%append (command) call terminate_now_if_signal () pn_cmd => parse_node_get_next_ptr (pn_cmd) end do end subroutine command_list_compile @ %def command_list_compile @ \subsection{Executing the command list} Before executing a command we should execute its options (if any). After that, reset the options, i.e., remove temporary effects from the global state. Also here, after each command we set a breakpoint. <>= procedure :: execute => command_list_execute <>= recursive subroutine command_list_execute (cmd_list, global) class(command_list_t), intent(in) :: cmd_list type(rt_data_t), intent(inout), target :: global class(command_t), pointer :: command command => cmd_list%first COMMAND_COND: do while (associated (command)) call command%execute_options (global) call command%execute (global) call command%reset_options (global) call terminate_now_if_signal () if (global%quit) exit COMMAND_COND command => command%next end do COMMAND_COND end subroutine command_list_execute @ %def command_list_execute @ \subsection{Command list syntax} <>= public :: syntax_cmd_list <>= type(syntax_t), target, save :: syntax_cmd_list @ %def syntax_cmd_list <>= public :: syntax_cmd_list_init <>= subroutine syntax_cmd_list_init () type(ifile_t) :: ifile call define_cmd_list_syntax (ifile) call syntax_init (syntax_cmd_list, ifile) call ifile_final (ifile) end subroutine syntax_cmd_list_init @ %def syntax_cmd_list_init <>= public :: syntax_cmd_list_final <>= subroutine syntax_cmd_list_final () call syntax_final (syntax_cmd_list) end subroutine syntax_cmd_list_final @ %def syntax_cmd_list_final <>= public :: syntax_cmd_list_write <>= subroutine syntax_cmd_list_write (unit) integer, intent(in), optional :: unit call syntax_write (syntax_cmd_list, unit) end subroutine syntax_cmd_list_write @ %def syntax_cmd_list_write <>= subroutine define_cmd_list_syntax (ifile) type(ifile_t), intent(inout) :: ifile call ifile_append (ifile, "SEQ command_list = command*") call ifile_append (ifile, "ALT command = " & // "cmd_model | cmd_library | cmd_iterations | cmd_sample_format | " & // "cmd_var | cmd_slha | " & // "cmd_show | cmd_clear | " & // "cmd_expect | " & // "cmd_cuts | cmd_scale | cmd_fac_scale | cmd_ren_scale | " & // "cmd_weight | cmd_selection | cmd_reweight | " & // "cmd_beams | cmd_beams_pol_density | cmd_beams_pol_fraction | " & // "cmd_beams_momentum | cmd_beams_theta | cmd_beams_phi | " & // "cmd_integrate | " & // "cmd_observable | cmd_histogram | cmd_plot | cmd_graph | " & // "cmd_record | " & // "cmd_analysis | cmd_alt_setup | " & // "cmd_unstable | cmd_stable | cmd_simulate | cmd_rescan | " & // "cmd_process | cmd_compile | cmd_exec | " & // "cmd_scan | cmd_if | cmd_include | cmd_quit | " & // "cmd_export | " & // "cmd_polarized | cmd_unpolarized | " & // "cmd_open_out | cmd_close_out | cmd_printf | " & // "cmd_write_analysis | cmd_compile_analysis | cmd_nlo | cmd_components") call ifile_append (ifile, "GRO options = '{' local_command_list '}'") call ifile_append (ifile, "SEQ local_command_list = local_command*") call ifile_append (ifile, "ALT local_command = " & // "cmd_model | cmd_library | cmd_iterations | cmd_sample_format | " & // "cmd_var | cmd_slha | " & // "cmd_show | " & // "cmd_expect | " & // "cmd_cuts | cmd_scale | cmd_fac_scale | cmd_ren_scale | " & // "cmd_weight | cmd_selection | cmd_reweight | " & // "cmd_beams | cmd_beams_pol_density | cmd_beams_pol_fraction | " & // "cmd_beams_momentum | cmd_beams_theta | cmd_beams_phi | " & // "cmd_observable | cmd_histogram | cmd_plot | cmd_graph | " & // "cmd_clear | cmd_record | " & // "cmd_analysis | cmd_alt_setup | " & // "cmd_open_out | cmd_close_out | cmd_printf | " & // "cmd_write_analysis | cmd_compile_analysis | cmd_nlo | cmd_components") call ifile_append (ifile, "SEQ cmd_model = model '=' model_name model_arg?") call ifile_append (ifile, "KEY model") call ifile_append (ifile, "ALT model_name = model_id | string_literal") call ifile_append (ifile, "IDE model_id") call ifile_append (ifile, "ARG model_arg = ( model_scheme? )") call ifile_append (ifile, "ALT model_scheme = " & // "ufo_spec | scheme_id | string_literal") call ifile_append (ifile, "SEQ ufo_spec = ufo ufo_arg?") call ifile_append (ifile, "KEY ufo") call ifile_append (ifile, "ARG ufo_arg = ( string_literal )") call ifile_append (ifile, "IDE scheme_id") call ifile_append (ifile, "SEQ cmd_library = library '=' lib_name") call ifile_append (ifile, "KEY library") call ifile_append (ifile, "ALT lib_name = lib_id | string_literal") call ifile_append (ifile, "IDE lib_id") call ifile_append (ifile, "ALT cmd_var = " & // "cmd_log_decl | cmd_log | " & // "cmd_int | cmd_real | cmd_complex | cmd_num | " & // "cmd_string_decl | cmd_string | cmd_alias | " & // "cmd_result") call ifile_append (ifile, "SEQ cmd_log_decl = logical cmd_log") call ifile_append (ifile, "SEQ cmd_log = '?' var_name '=' lexpr") call ifile_append (ifile, "SEQ cmd_int = int var_name '=' expr") call ifile_append (ifile, "SEQ cmd_real = real var_name '=' expr") call ifile_append (ifile, "SEQ cmd_complex = complex var_name '=' expr") call ifile_append (ifile, "SEQ cmd_num = var_name '=' expr") call ifile_append (ifile, "SEQ cmd_string_decl = string cmd_string") call ifile_append (ifile, "SEQ cmd_string = " & // "'$' var_name '=' sexpr") ! $ call ifile_append (ifile, "SEQ cmd_alias = alias var_name '=' cexpr") call ifile_append (ifile, "SEQ cmd_result = result '=' expr") call ifile_append (ifile, "SEQ cmd_slha = slha_action slha_arg options?") call ifile_append (ifile, "ALT slha_action = " & // "read_slha | write_slha") call ifile_append (ifile, "KEY read_slha") call ifile_append (ifile, "KEY write_slha") call ifile_append (ifile, "ARG slha_arg = ( string_literal )") call ifile_append (ifile, "SEQ cmd_show = show show_arg options?") call ifile_append (ifile, "KEY show") call ifile_append (ifile, "ARG show_arg = ( showable* )") call ifile_append (ifile, "ALT showable = " & // "model | library | beams | iterations | " & // "cuts | weight | logical | string | pdg | " & // "scale | factorization_scale | renormalization_scale | " & // "selection | reweight | analysis | " & // "stable | unstable | polarized | unpolarized | " & // "expect | intrinsic | int | real | complex | " & // "alias_var | string | results | result_var | " & // "log_var | string_var | var_name") call ifile_append (ifile, "KEY results") call ifile_append (ifile, "KEY intrinsic") call ifile_append (ifile, "SEQ alias_var = alias var_name") call ifile_append (ifile, "SEQ result_var = result_key result_arg?") call ifile_append (ifile, "SEQ log_var = '?' var_name") call ifile_append (ifile, "SEQ string_var = '$' var_name") ! $ call ifile_append (ifile, "SEQ cmd_clear = clear clear_arg options?") call ifile_append (ifile, "KEY clear") call ifile_append (ifile, "ARG clear_arg = ( clearable* )") call ifile_append (ifile, "ALT clearable = " & // "beams | iterations | " & // "cuts | weight | " & // "scale | factorization_scale | renormalization_scale | " & // "selection | reweight | analysis | " & // "unstable | polarized | " & // "expect | " & // "log_var | string_var | var_name") call ifile_append (ifile, "SEQ cmd_expect = expect expect_arg options?") call ifile_append (ifile, "KEY expect") call ifile_append (ifile, "ARG expect_arg = ( lexpr )") call ifile_append (ifile, "SEQ cmd_cuts = cuts '=' lexpr") call ifile_append (ifile, "SEQ cmd_scale = scale '=' expr") call ifile_append (ifile, "SEQ cmd_fac_scale = " & // "factorization_scale '=' expr") call ifile_append (ifile, "SEQ cmd_ren_scale = " & // "renormalization_scale '=' expr") call ifile_append (ifile, "SEQ cmd_weight = weight '=' expr") call ifile_append (ifile, "SEQ cmd_selection = selection '=' lexpr") call ifile_append (ifile, "SEQ cmd_reweight = reweight '=' expr") call ifile_append (ifile, "KEY cuts") call ifile_append (ifile, "KEY scale") call ifile_append (ifile, "KEY factorization_scale") call ifile_append (ifile, "KEY renormalization_scale") call ifile_append (ifile, "KEY weight") call ifile_append (ifile, "KEY selection") call ifile_append (ifile, "KEY reweight") call ifile_append (ifile, "SEQ cmd_process = process process_id '=' " & // "process_prt '=>' prt_state_list options?") call ifile_append (ifile, "KEY process") call ifile_append (ifile, "KEY '=>'") call ifile_append (ifile, "LIS process_prt = cexpr+") call ifile_append (ifile, "LIS prt_state_list = prt_state_sum+") call ifile_append (ifile, "SEQ prt_state_sum = " & // "prt_state prt_state_addition*") call ifile_append (ifile, "SEQ prt_state_addition = '+' prt_state") call ifile_append (ifile, "ALT prt_state = grouped_prt_state_list | cexpr") call ifile_append (ifile, "GRO grouped_prt_state_list = " & // "( prt_state_list )") call ifile_append (ifile, "SEQ cmd_compile = compile_cmd options?") call ifile_append (ifile, "SEQ compile_cmd = compile_clause compile_arg?") call ifile_append (ifile, "SEQ compile_clause = compile exec_name_spec?") call ifile_append (ifile, "KEY compile") call ifile_append (ifile, "SEQ exec_name_spec = as exec_name") call ifile_append (ifile, "KEY as") call ifile_append (ifile, "ALT exec_name = exec_id | string_literal") call ifile_append (ifile, "IDE exec_id") call ifile_append (ifile, "ARG compile_arg = ( lib_name* )") call ifile_append (ifile, "SEQ cmd_exec = exec exec_arg") call ifile_append (ifile, "KEY exec") call ifile_append (ifile, "ARG exec_arg = ( sexpr )") call ifile_append (ifile, "SEQ cmd_beams = beams '=' beam_def") call ifile_append (ifile, "KEY beams") call ifile_append (ifile, "SEQ beam_def = beam_spec strfun_seq*") call ifile_append (ifile, "SEQ beam_spec = beam_list") call ifile_append (ifile, "LIS beam_list = cexpr, cexpr?") call ifile_append (ifile, "SEQ cmd_beams_pol_density = " & // "beams_pol_density '=' beams_pol_spec") call ifile_append (ifile, "KEY beams_pol_density") call ifile_append (ifile, "LIS beams_pol_spec = smatrix, smatrix?") call ifile_append (ifile, "SEQ smatrix = '@' smatrix_arg") ! call ifile_append (ifile, "KEY '@'") !!! Key already exists call ifile_append (ifile, "ARG smatrix_arg = ( sentry* )") call ifile_append (ifile, "SEQ sentry = expr extra_sentry*") call ifile_append (ifile, "SEQ extra_sentry = ':' expr") call ifile_append (ifile, "SEQ cmd_beams_pol_fraction = " & // "beams_pol_fraction '=' beams_par_spec") call ifile_append (ifile, "KEY beams_pol_fraction") call ifile_append (ifile, "SEQ cmd_beams_momentum = " & // "beams_momentum '=' beams_par_spec") call ifile_append (ifile, "KEY beams_momentum") call ifile_append (ifile, "SEQ cmd_beams_theta = " & // "beams_theta '=' beams_par_spec") call ifile_append (ifile, "KEY beams_theta") call ifile_append (ifile, "SEQ cmd_beams_phi = " & // "beams_phi '=' beams_par_spec") call ifile_append (ifile, "KEY beams_phi") call ifile_append (ifile, "LIS beams_par_spec = expr, expr?") call ifile_append (ifile, "SEQ strfun_seq = '=>' strfun_pair") call ifile_append (ifile, "LIS strfun_pair = strfun_def, strfun_def?") call ifile_append (ifile, "SEQ strfun_def = strfun_id") call ifile_append (ifile, "ALT strfun_id = " & // "none | lhapdf | lhapdf_photon | pdf_builtin | pdf_builtin_photon | " & // "isr | epa | ewa | circe1 | circe2 | energy_scan | " & // "gaussian | beam_events") call ifile_append (ifile, "KEY none") call ifile_append (ifile, "KEY lhapdf") call ifile_append (ifile, "KEY lhapdf_photon") call ifile_append (ifile, "KEY pdf_builtin") call ifile_append (ifile, "KEY pdf_builtin_photon") call ifile_append (ifile, "KEY isr") call ifile_append (ifile, "KEY epa") call ifile_append (ifile, "KEY ewa") call ifile_append (ifile, "KEY circe1") call ifile_append (ifile, "KEY circe2") call ifile_append (ifile, "KEY energy_scan") call ifile_append (ifile, "KEY gaussian") call ifile_append (ifile, "KEY beam_events") call ifile_append (ifile, "SEQ cmd_integrate = " & // "integrate proc_arg options?") call ifile_append (ifile, "KEY integrate") call ifile_append (ifile, "ARG proc_arg = ( proc_id* )") call ifile_append (ifile, "IDE proc_id") call ifile_append (ifile, "SEQ cmd_iterations = " & // "iterations '=' iterations_list") call ifile_append (ifile, "KEY iterations") call ifile_append (ifile, "LIS iterations_list = iterations_spec+") call ifile_append (ifile, "ALT iterations_spec = it_spec") call ifile_append (ifile, "SEQ it_spec = expr calls_spec adapt_spec?") call ifile_append (ifile, "SEQ calls_spec = ':' expr") call ifile_append (ifile, "SEQ adapt_spec = ':' sexpr") call ifile_append (ifile, "SEQ cmd_components = " & // "active '=' component_list") call ifile_append (ifile, "KEY active") call ifile_append (ifile, "LIS component_list = sexpr+") call ifile_append (ifile, "SEQ cmd_sample_format = " & // "sample_format '=' event_format_list") call ifile_append (ifile, "KEY sample_format") call ifile_append (ifile, "LIS event_format_list = event_format+") call ifile_append (ifile, "IDE event_format") call ifile_append (ifile, "SEQ cmd_observable = " & // "observable analysis_tag options?") call ifile_append (ifile, "KEY observable") call ifile_append (ifile, "SEQ cmd_histogram = " & // "histogram analysis_tag histogram_arg " & // "options?") call ifile_append (ifile, "KEY histogram") call ifile_append (ifile, "ARG histogram_arg = (expr, expr, expr?)") call ifile_append (ifile, "SEQ cmd_plot = plot analysis_tag options?") call ifile_append (ifile, "KEY plot") call ifile_append (ifile, "SEQ cmd_graph = graph graph_term '=' graph_def") call ifile_append (ifile, "KEY graph") call ifile_append (ifile, "SEQ graph_term = analysis_tag options?") call ifile_append (ifile, "SEQ graph_def = graph_term graph_append*") call ifile_append (ifile, "SEQ graph_append = '&' graph_term") call ifile_append (ifile, "SEQ cmd_analysis = analysis '=' lexpr") call ifile_append (ifile, "KEY analysis") call ifile_append (ifile, "SEQ cmd_alt_setup = " & // "alt_setup '=' option_list_expr") call ifile_append (ifile, "KEY alt_setup") call ifile_append (ifile, "ALT option_list_expr = " & // "grouped_option_list | option_list") call ifile_append (ifile, "GRO grouped_option_list = ( option_list_expr )") call ifile_append (ifile, "LIS option_list = options+") call ifile_append (ifile, "SEQ cmd_open_out = open_out open_arg options?") call ifile_append (ifile, "SEQ cmd_close_out = close_out open_arg options?") call ifile_append (ifile, "KEY open_out") call ifile_append (ifile, "KEY close_out") call ifile_append (ifile, "ARG open_arg = (sexpr)") call ifile_append (ifile, "SEQ cmd_printf = printf_cmd options?") call ifile_append (ifile, "SEQ printf_cmd = printf_clause sprintf_args?") call ifile_append (ifile, "SEQ printf_clause = printf sexpr") call ifile_append (ifile, "KEY printf") call ifile_append (ifile, "SEQ cmd_record = record_cmd") call ifile_append (ifile, "SEQ cmd_unstable = " & // "unstable cexpr unstable_arg options?") call ifile_append (ifile, "KEY unstable") call ifile_append (ifile, "ARG unstable_arg = ( proc_id* )") call ifile_append (ifile, "SEQ cmd_stable = stable stable_list options?") call ifile_append (ifile, "KEY stable") call ifile_append (ifile, "LIS stable_list = cexpr+") call ifile_append (ifile, "KEY polarized") call ifile_append (ifile, "SEQ cmd_polarized = polarized polarized_list options?") call ifile_append (ifile, "LIS polarized_list = cexpr+") call ifile_append (ifile, "KEY unpolarized") call ifile_append (ifile, "SEQ cmd_unpolarized = unpolarized unpolarized_list options?") call ifile_append (ifile, "LIS unpolarized_list = cexpr+") call ifile_append (ifile, "SEQ cmd_simulate = " & // "simulate proc_arg options?") call ifile_append (ifile, "KEY simulate") call ifile_append (ifile, "SEQ cmd_rescan = " & // "rescan sexpr proc_arg options?") call ifile_append (ifile, "KEY rescan") call ifile_append (ifile, "SEQ cmd_scan = scan scan_var scan_body?") call ifile_append (ifile, "KEY scan") call ifile_append (ifile, "ALT scan_var = " & // "scan_log_decl | scan_log | " & // "scan_int | scan_real | scan_complex | scan_num | " & // "scan_string_decl | scan_string | scan_alias | " & // "scan_cuts | scan_weight | " & // "scan_scale | scan_ren_scale | scan_fac_scale | " & // "scan_selection | scan_reweight | scan_analysis | " & // "scan_model | scan_library") call ifile_append (ifile, "SEQ scan_log_decl = logical scan_log") call ifile_append (ifile, "SEQ scan_log = '?' var_name '=' scan_log_arg") call ifile_append (ifile, "ARG scan_log_arg = ( lexpr* )") call ifile_append (ifile, "SEQ scan_int = int var_name '=' scan_num_arg") call ifile_append (ifile, "SEQ scan_real = real var_name '=' scan_num_arg") call ifile_append (ifile, "SEQ scan_complex = " & // "complex var_name '=' scan_num_arg") call ifile_append (ifile, "SEQ scan_num = var_name '=' scan_num_arg") call ifile_append (ifile, "ARG scan_num_arg = ( range* )") call ifile_append (ifile, "ALT range = grouped_range | range_expr") call ifile_append (ifile, "GRO grouped_range = ( range_expr )") call ifile_append (ifile, "SEQ range_expr = expr range_spec?") call ifile_append (ifile, "SEQ range_spec = '=>' expr step_spec?") call ifile_append (ifile, "SEQ step_spec = step_op expr") call ifile_append (ifile, "ALT step_op = " & // "'/+' | '/-' | '/*' | '//' | '/+/' | '/*/'") call ifile_append (ifile, "KEY '/+'") call ifile_append (ifile, "KEY '/-'") call ifile_append (ifile, "KEY '/*'") call ifile_append (ifile, "KEY '//'") call ifile_append (ifile, "KEY '/+/'") call ifile_append (ifile, "KEY '/*/'") call ifile_append (ifile, "SEQ scan_string_decl = string scan_string") call ifile_append (ifile, "SEQ scan_string = " & // "'$' var_name '=' scan_string_arg") call ifile_append (ifile, "ARG scan_string_arg = ( sexpr* )") call ifile_append (ifile, "SEQ scan_alias = " & // "alias var_name '=' scan_alias_arg") call ifile_append (ifile, "ARG scan_alias_arg = ( cexpr* )") call ifile_append (ifile, "SEQ scan_cuts = cuts '=' scan_lexpr_arg") call ifile_append (ifile, "ARG scan_lexpr_arg = ( lexpr* )") call ifile_append (ifile, "SEQ scan_scale = scale '=' scan_expr_arg") call ifile_append (ifile, "ARG scan_expr_arg = ( expr* )") call ifile_append (ifile, "SEQ scan_fac_scale = " & // "factorization_scale '=' scan_expr_arg") call ifile_append (ifile, "SEQ scan_ren_scale = " & // "renormalization_scale '=' scan_expr_arg") call ifile_append (ifile, "SEQ scan_weight = weight '=' scan_expr_arg") call ifile_append (ifile, "SEQ scan_selection = selection '=' scan_lexpr_arg") call ifile_append (ifile, "SEQ scan_reweight = reweight '=' scan_expr_arg") call ifile_append (ifile, "SEQ scan_analysis = analysis '=' scan_lexpr_arg") call ifile_append (ifile, "SEQ scan_model = model '=' scan_model_arg") call ifile_append (ifile, "ARG scan_model_arg = ( model_name* )") call ifile_append (ifile, "SEQ scan_library = library '=' scan_library_arg") call ifile_append (ifile, "ARG scan_library_arg = ( lib_name* )") call ifile_append (ifile, "GRO scan_body = '{' command_list '}'") call ifile_append (ifile, "SEQ cmd_if = " & // "if lexpr then command_list elsif_clauses else_clause endif") call ifile_append (ifile, "SEQ elsif_clauses = cmd_elsif*") call ifile_append (ifile, "SEQ cmd_elsif = elsif lexpr then command_list") call ifile_append (ifile, "SEQ else_clause = cmd_else?") call ifile_append (ifile, "SEQ cmd_else = else command_list") call ifile_append (ifile, "SEQ cmd_include = include include_arg") call ifile_append (ifile, "KEY include") call ifile_append (ifile, "ARG include_arg = ( string_literal )") call ifile_append (ifile, "SEQ cmd_quit = quit_cmd quit_arg?") call ifile_append (ifile, "ALT quit_cmd = quit | exit") call ifile_append (ifile, "KEY quit") call ifile_append (ifile, "KEY exit") call ifile_append (ifile, "ARG quit_arg = ( expr )") call ifile_append (ifile, "SEQ cmd_export = export show_arg options?") call ifile_append (ifile, "KEY export") call ifile_append (ifile, "SEQ cmd_write_analysis = " & // "write_analysis_clause options?") call ifile_append (ifile, "SEQ cmd_compile_analysis = " & // "compile_analysis_clause options?") call ifile_append (ifile, "SEQ write_analysis_clause = " & // "write_analysis write_analysis_arg?") call ifile_append (ifile, "SEQ compile_analysis_clause = " & // "compile_analysis write_analysis_arg?") call ifile_append (ifile, "KEY write_analysis") call ifile_append (ifile, "KEY compile_analysis") call ifile_append (ifile, "ARG write_analysis_arg = ( analysis_tag* )") call ifile_append (ifile, "SEQ cmd_nlo = " & // "nlo_calculation '=' nlo_calculation_list") call ifile_append (ifile, "KEY nlo_calculation") call ifile_append (ifile, "LIS nlo_calculation_list = nlo_comp+") call ifile_append (ifile, "ALT nlo_comp = " // & "full | born | real | virtual | dglap | subtraction | " // & "mismatch | GKS") call ifile_append (ifile, "KEY full") call ifile_append (ifile, "KEY born") call ifile_append (ifile, "KEY virtual") call ifile_append (ifile, "KEY dglap") call ifile_append (ifile, "KEY subtraction") call ifile_append (ifile, "KEY mismatch") call ifile_append (ifile, "KEY GKS") call define_expr_syntax (ifile, particles=.true., analysis=.true.) end subroutine define_cmd_list_syntax @ %def define_cmd_list_syntax <>= public :: lexer_init_cmd_list <>= subroutine lexer_init_cmd_list (lexer, parent_lexer) type(lexer_t), intent(out) :: lexer type(lexer_t), intent(in), optional, target :: parent_lexer call lexer_init (lexer, & comment_chars = "#!", & quote_chars = '"', & quote_match = '"', & single_chars = "()[]{},;:&%?$@", & special_class = [ "+-*/^", "<>=~ " ] , & keyword_list = syntax_get_keyword_list_ptr (syntax_cmd_list), & parent = parent_lexer) end subroutine lexer_init_cmd_list @ %def lexer_init_cmd_list @ \subsection{Unit Tests} Test module, followed by the corresponding implementation module. <<[[commands_ut.f90]]>>= <> module commands_ut use unit_tests use system_dependencies, only: MPOST_AVAILABLE use commands_uti <> <> contains <> end module commands_ut @ %def commands_ut @ <<[[commands_uti.f90]]>>= <> module commands_uti <> use kinds, only: i64 <> use io_units use ifiles use parser use interactions, only: reset_interaction_counter use prclib_stacks use analysis use variables, only: var_list_t use models use slha_interface use rt_data use event_base, only: generic_event_t, event_callback_t use commands <> <> <> contains <> <> end module commands_uti @ %def commands_uti @ API: driver for the unit tests below. <>= public :: commands_test <>= subroutine commands_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine commands_test @ %def commands_test @ \subsubsection{Prepare Sindarin code} This routine parses an internal file, prints the parse tree, and returns a parse node to the root. We use the routine in the tests below. <>= public :: parse_ifile <>= subroutine parse_ifile (ifile, pn_root, u) use ifiles use lexers use parser use commands type(ifile_t), intent(in) :: ifile type(parse_node_t), pointer, intent(out) :: pn_root integer, intent(in), optional :: u type(stream_t), target :: stream type(lexer_t), target :: lexer type(parse_tree_t) :: parse_tree call lexer_init_cmd_list (lexer) call stream_init (stream, ifile) call lexer_assign_stream (lexer, stream) call parse_tree_init (parse_tree, syntax_cmd_list, lexer) if (present (u)) call parse_tree_write (parse_tree, u) pn_root => parse_tree%get_root_ptr () call stream_final (stream) call lexer_final (lexer) end subroutine parse_ifile @ %def parse_ifile @ \subsubsection{Empty command list} Compile and execute an empty command list. Should do nothing but test the integrity of the workflow. <>= call test (commands_1, "commands_1", & "empty command list", & u, results) <>= public :: commands_1 <>= subroutine commands_1 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_1" write (u, "(A)") "* Purpose: compile and execute empty command list" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Parse empty file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" if (associated (pn_root)) then call command_list%compile (pn_root, global) end if write (u, "(A)") write (u, "(A)") "* Execute command list" call global%activate () call command_list%execute (global) call global%deactivate () write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call syntax_cmd_list_final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_1" end subroutine commands_1 @ %def commands_1 @ \subsubsection{Read model} Execute a [[model]] assignment. <>= call test (commands_2, "commands_2", & "model", & u, results) <>= public :: commands_2 <>= subroutine commands_2 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_2" write (u, "(A)") "* Purpose: set model" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_write (ifile, u) write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_2" end subroutine commands_2 @ %def commands_2 @ \subsubsection{Declare Process} Read a model, then declare a process. The process library is allocated explicitly. For the process definition, We take the default ([[omega]]) method. Since we do not compile, \oMega\ is not actually called. <>= call test (commands_3, "commands_3", & "process declaration", & u, results) <>= public :: commands_3 <>= subroutine commands_3 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_3" write (u, "(A)") "* Purpose: define process" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%var_list%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) allocate (lib) call lib%init (var_str ("lib_cmd3")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process t3 = s, s => s, s') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%prclib_stack%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_3" end subroutine commands_3 @ %def commands_3 @ \subsubsection{Compile Process} Read a model, then declare a process and compile the library. The process library is allocated explicitly. For the process definition, We take the default ([[unit_test]]) method. There is no external code, so compilation of the library is merely a formal status change. <>= call test (commands_4, "commands_4", & "compilation", & u, results) <>= public :: commands_4 <>= subroutine commands_4 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_4" write (u, "(A)") "* Purpose: define process and compile library" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) allocate (lib) call lib%init (var_str ("lib_cmd4")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process t4 = s, s => s, s') call ifile_append (ifile, 'compile ("lib_cmd4")') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%prclib_stack%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_4" end subroutine commands_4 @ %def commands_4 @ \subsubsection{Integrate Process} Read a model, then declare a process, compile the library, and integrate over phase space. We take the default ([[unit_test]]) method and use the simplest methods of phase-space parameterization and integration. <>= call test (commands_5, "commands_5", & "integration", & u, results) <>= public :: commands_5 <>= subroutine commands_5 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_5" write (u, "(A)") "* Purpose: define process, iterations, and integrate" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) call global%var_list%set_int (var_str ("seed"), 0, is_known=.true.) allocate (lib) call lib%init (var_str ("lib_cmd5")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process t5 = s, s => s, s') call ifile_append (ifile, 'compile') call ifile_append (ifile, 'iterations = 1:1000') call ifile_append (ifile, 'integrate (t5)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call reset_interaction_counter () call command_list%execute (global) call global%it_list%write (u) write (u, "(A)") call global%process_stack%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_5" end subroutine commands_5 @ %def commands_5 @ \subsubsection{Variables} Set intrinsic and user-defined variables. <>= call test (commands_6, "commands_6", & "variables", & u, results) <>= public :: commands_6 <>= subroutine commands_6 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_6" write (u, "(A)") "* Purpose: define and set variables" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () call global%write_vars (u, [ & var_str ("$run_id"), & var_str ("?unweighted"), & var_str ("sqrts")]) write (u, "(A)") write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$run_id = "run1"') call ifile_append (ifile, '?unweighted = false') call ifile_append (ifile, 'sqrts = 1000') call ifile_append (ifile, 'int j = 10') call ifile_append (ifile, 'real x = 1000.') call ifile_append (ifile, 'complex z = 5') call ifile_append (ifile, 'string $text = "abcd"') call ifile_append (ifile, 'logical ?flag = true') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_vars (u, [ & var_str ("$run_id"), & var_str ("?unweighted"), & var_str ("sqrts"), & var_str ("j"), & var_str ("x"), & var_str ("z"), & var_str ("$text"), & var_str ("?flag")]) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call syntax_cmd_list_final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_6" end subroutine commands_6 @ %def commands_6 @ \subsubsection{Process library} Open process libraries explicitly. <>= call test (commands_7, "commands_7", & "process library", & u, results) <>= public :: commands_7 <>= subroutine commands_7 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_7" write (u, "(A)") "* Purpose: declare process libraries" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () call global%var_list%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) global%os_data%fc = "Fortran-compiler" global%os_data%fcflags = "Fortran-flags" global%os_data%fclibs = "Fortran-libs" write (u, "(A)") write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'library = "lib_cmd7_1"') call ifile_append (ifile, 'library = "lib_cmd7_2"') call ifile_append (ifile, 'library = "lib_cmd7_1"') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_libraries (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call syntax_cmd_list_final () call global%final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_7" end subroutine commands_7 @ %def commands_7 @ \subsubsection{Generate events} Read a model, then declare a process, compile the library, and generate weighted events. We take the default ([[unit_test]]) method and use the simplest methods of phase-space parameterization and integration. <>= call test (commands_8, "commands_8", & "event generation", & u, results) <>= public :: commands_8 <>= subroutine commands_8 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_8" write (u, "(A)") "* Purpose: define process, integrate, generate events" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) allocate (lib) call lib%init (var_str ("lib_cmd8")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process commands_8_p = s, s => s, s') call ifile_append (ifile, 'compile') call ifile_append (ifile, 'iterations = 1:1000') call ifile_append (ifile, 'integrate (commands_8_p)') call ifile_append (ifile, '?unweighted = false') call ifile_append (ifile, 'n_events = 3') call ifile_append (ifile, '?read_raw = false') call ifile_append (ifile, 'simulate (commands_8_p)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" call command_list%execute (global) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_8" end subroutine commands_8 @ %def commands_8 @ \subsubsection{Define cuts} Declare a cut expression. <>= call test (commands_9, "commands_9", & "cuts", & u, results) <>= public :: commands_9 <>= subroutine commands_9 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(string_t), dimension(0) :: no_vars write (u, "(A)") "* Test output: commands_9" write (u, "(A)") "* Purpose: define cuts" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'cuts = all Pt > 0 [particle]') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write (u, vars = no_vars) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_9" end subroutine commands_9 @ %def commands_9 @ \subsubsection{Beams} Define beam setup. <>= call test (commands_10, "commands_10", & "beams", & u, results) <>= public :: commands_10 <>= subroutine commands_10 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_10" write (u, "(A)") "* Purpose: define beams" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = QCD') call ifile_append (ifile, 'sqrts = 1000') call ifile_append (ifile, 'beams = p, p') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_beams (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_10" end subroutine commands_10 @ %def commands_10 @ \subsubsection{Structure functions} Define beam setup with structure functions <>= call test (commands_11, "commands_11", & "structure functions", & u, results) <>= public :: commands_11 <>= subroutine commands_11 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_11" write (u, "(A)") "* Purpose: define beams with structure functions" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = QCD') call ifile_append (ifile, 'sqrts = 1100') call ifile_append (ifile, 'beams = p, p => lhapdf => pdf_builtin, isr') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_beams (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_11" end subroutine commands_11 @ %def commands_11 @ \subsubsection{Rescan events} Read a model, then declare a process, compile the library, and generate weighted events. We take the default ([[unit_test]]) method and use the simplest methods of phase-space parameterization and integration. Then, rescan the generated event sample. <>= call test (commands_12, "commands_12", & "event rescanning", & u, results) <>= public :: commands_12 <>= subroutine commands_12 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_12" write (u, "(A)") "* Purpose: generate events and rescan" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%var_list%append_log (& var_str ("?rebuild_phase_space"), .false., & intrinsic=.true.) call global%var_list%append_log (& var_str ("?rebuild_grids"), .false., & intrinsic=.true.) call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) allocate (lib) call lib%init (var_str ("lib_cmd12")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process commands_12_p = s, s => s, s') call ifile_append (ifile, 'compile') call ifile_append (ifile, 'iterations = 1:1000') call ifile_append (ifile, 'integrate (commands_12_p)') call ifile_append (ifile, '?unweighted = false') call ifile_append (ifile, 'n_events = 3') call ifile_append (ifile, '?read_raw = false') call ifile_append (ifile, 'simulate (commands_12_p)') call ifile_append (ifile, '?write_raw = false') call ifile_append (ifile, 'rescan "commands_12_p" (commands_12_p)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" call command_list%execute (global) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_12" end subroutine commands_12 @ %def commands_12 @ \subsubsection{Event Files} Set output formats for event files. <>= call test (commands_13, "commands_13", & "event output formats", & u, results) <>= public :: commands_13 <>= subroutine commands_13 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib logical :: exist write (u, "(A)") "* Test output: commands_13" write (u, "(A)") "* Purpose: generate events and rescan" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) allocate (lib) call lib%init (var_str ("lib_cmd13")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process commands_13_p = s, s => s, s') call ifile_append (ifile, 'compile') call ifile_append (ifile, 'iterations = 1:1000') call ifile_append (ifile, 'integrate (commands_13_p)') call ifile_append (ifile, '?unweighted = false') call ifile_append (ifile, 'n_events = 1') call ifile_append (ifile, '?read_raw = false') call ifile_append (ifile, 'sample_format = weight_stream') call ifile_append (ifile, 'simulate (commands_13_p)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" call command_list%execute (global) write (u, "(A)") write (u, "(A)") "* Verify output files" write (u, "(A)") inquire (file = "commands_13_p.evx", exist = exist) if (exist) write (u, "(1x,A)") "raw" inquire (file = "commands_13_p.weights.dat", exist = exist) if (exist) write (u, "(1x,A)") "weight_stream" write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_13" end subroutine commands_13 @ %def commands_13 @ \subsubsection{Compile Empty Libraries} (This is a regression test:) Declare two empty libraries and compile them. <>= call test (commands_14, "commands_14", & "empty libraries", & u, results) <>= public :: commands_14 <>= subroutine commands_14 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_14" write (u, "(A)") "* Purpose: define and compile empty libraries" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_model_file_init () call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'library = "lib1"') call ifile_append (ifile, 'library = "lib2"') call ifile_append (ifile, 'compile ()') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%prclib_stack%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_14" end subroutine commands_14 @ %def commands_14 @ \subsubsection{Compile Process} Read a model, then declare a process and compile the library. The process library is allocated explicitly. For the process definition, We take the default ([[unit_test]]) method. There is no external code, so compilation of the library is merely a formal status change. <>= call test (commands_15, "commands_15", & "compilation", & u, results) <>= public :: commands_15 <>= subroutine commands_15 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_15" write (u, "(A)") "* Purpose: define process and compile library" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) allocate (lib) call lib%init (var_str ("lib_cmd15")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process t15 = s, s => s, s') call ifile_append (ifile, 'iterations = 1:1000') call ifile_append (ifile, 'integrate (t15)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%prclib_stack%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_15" end subroutine commands_15 @ %def commands_15 @ \subsubsection{Observable} Declare an observable, fill it and display. <>= call test (commands_16, "commands_16", & "observables", & u, results) <>= public :: commands_16 <>= subroutine commands_16 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_16" write (u, "(A)") "* Purpose: declare an observable" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$obs_label = "foo"') call ifile_append (ifile, '$obs_unit = "cm"') call ifile_append (ifile, '$title = "Observable foo"') call ifile_append (ifile, '$description = "This is observable foo"') call ifile_append (ifile, 'observable foo') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Record two data items" write (u, "(A)") call analysis_record_data (var_str ("foo"), 1._default) call analysis_record_data (var_str ("foo"), 3._default) write (u, "(A)") "* Display analysis store" write (u, "(A)") call analysis_write (u, verbose=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_16" end subroutine commands_16 @ %def commands_16 @ \subsubsection{Histogram} Declare a histogram, fill it and display. <>= call test (commands_17, "commands_17", & "histograms", & u, results) <>= public :: commands_17 <>= subroutine commands_17 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(string_t), dimension(3) :: name integer :: i write (u, "(A)") "* Test output: commands_17" write (u, "(A)") "* Purpose: declare histograms" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$obs_label = "foo"') call ifile_append (ifile, '$obs_unit = "cm"') call ifile_append (ifile, '$title = "Histogram foo"') call ifile_append (ifile, '$description = "This is histogram foo"') call ifile_append (ifile, 'histogram foo (0,5,1)') call ifile_append (ifile, '$title = "Histogram bar"') call ifile_append (ifile, '$description = "This is histogram bar"') call ifile_append (ifile, 'n_bins = 2') call ifile_append (ifile, 'histogram bar (0,5)') call ifile_append (ifile, '$title = "Histogram gee"') call ifile_append (ifile, '$description = "This is histogram gee"') call ifile_append (ifile, '?normalize_bins = true') call ifile_append (ifile, 'histogram gee (0,5)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Record two data items" write (u, "(A)") name(1) = "foo" name(2) = "bar" name(3) = "gee" do i = 1, 3 call analysis_record_data (name(i), 0.1_default, & weight = 0.25_default) call analysis_record_data (name(i), 3.1_default) call analysis_record_data (name(i), 4.1_default, & excess = 0.5_default) call analysis_record_data (name(i), 7.1_default) end do write (u, "(A)") "* Display analysis store" write (u, "(A)") call analysis_write (u, verbose=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_17" end subroutine commands_17 @ %def commands_17 @ \subsubsection{Plot} Declare a plot, fill it and display contents. <>= call test (commands_18, "commands_18", & "plots", & u, results) <>= public :: commands_18 <>= subroutine commands_18 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_18" write (u, "(A)") "* Purpose: declare a plot" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$obs_label = "foo"') call ifile_append (ifile, '$obs_unit = "cm"') call ifile_append (ifile, '$title = "Plot foo"') call ifile_append (ifile, '$description = "This is plot foo"') call ifile_append (ifile, '$x_label = "x axis"') call ifile_append (ifile, '$y_label = "y axis"') call ifile_append (ifile, '?x_log = false') call ifile_append (ifile, '?y_log = true') call ifile_append (ifile, 'x_min = -1') call ifile_append (ifile, 'x_max = 1') call ifile_append (ifile, 'y_min = 0.1') call ifile_append (ifile, 'y_max = 1000') call ifile_append (ifile, 'plot foo') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Record two data items" write (u, "(A)") call analysis_record_data (var_str ("foo"), 0._default, 20._default, & xerr = 0.25_default) call analysis_record_data (var_str ("foo"), 0.5_default, 0.2_default, & yerr = 0.07_default) call analysis_record_data (var_str ("foo"), 3._default, 2._default) write (u, "(A)") "* Display analysis store" write (u, "(A)") call analysis_write (u, verbose=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_18" end subroutine commands_18 @ %def commands_18 @ \subsubsection{Graph} Combine two (empty) plots to a graph. <>= call test (commands_19, "commands_19", & "graphs", & u, results) <>= public :: commands_19 <>= subroutine commands_19 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_19" write (u, "(A)") "* Purpose: combine two plots to a graph" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'plot a') call ifile_append (ifile, 'plot b') call ifile_append (ifile, '$title = "Graph foo"') call ifile_append (ifile, '$description = "This is graph foo"') call ifile_append (ifile, 'graph foo = a & b') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Display analysis object" write (u, "(A)") call analysis_write (var_str ("foo"), u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_19" end subroutine commands_19 @ %def commands_19 @ \subsubsection{Record Data} Record data in previously allocated analysis objects. <>= call test (commands_20, "commands_20", & "record data", & u, results) <>= public :: commands_20 <>= subroutine commands_20 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_20" write (u, "(A)") "* Purpose: record data" write (u, "(A)") write (u, "(A)") "* Initialization: create observable, histogram, plot" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () call analysis_init_observable (var_str ("o")) call analysis_init_histogram (var_str ("h"), 0._default, 1._default, 3, & normalize_bins = .false.) call analysis_init_plot (var_str ("p")) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'record o (1.234)') call ifile_append (ifile, 'record h (0.5)') call ifile_append (ifile, 'record p (1, 2)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Display analysis object" write (u, "(A)") call analysis_write (u, verbose = .true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_20" end subroutine commands_20 @ %def commands_20 @ \subsubsection{Analysis} Declare an analysis expression and use it to fill an observable during event generation. <>= call test (commands_21, "commands_21", & "analysis expression", & u, results) <>= public :: commands_21 <>= subroutine commands_21 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_21" write (u, "(A)") "* Purpose: create and use analysis expression" write (u, "(A)") write (u, "(A)") "* Initialization: create observable" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) allocate (lib) call lib%init (var_str ("lib_cmd8")) call global%add_prclib (lib) call analysis_init_observable (var_str ("m")) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process commands_21_p = s, s => s, s') call ifile_append (ifile, 'compile') call ifile_append (ifile, 'iterations = 1:100') call ifile_append (ifile, 'integrate (commands_21_p)') call ifile_append (ifile, '?unweighted = true') call ifile_append (ifile, 'n_events = 3') call ifile_append (ifile, '?read_raw = false') call ifile_append (ifile, 'observable m') call ifile_append (ifile, 'analysis = record m (eval M [s])') call ifile_append (ifile, 'simulate (commands_21_p)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Display analysis object" write (u, "(A)") call analysis_write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_21" end subroutine commands_21 @ %def commands_21 @ \subsubsection{Write Analysis} Write accumulated analysis data to file. <>= call test (commands_22, "commands_22", & "write analysis", & u, results) <>= public :: commands_22 <>= subroutine commands_22 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root integer :: u_file, iostat logical :: exist character(80) :: buffer write (u, "(A)") "* Test output: commands_22" write (u, "(A)") "* Purpose: write analysis data" write (u, "(A)") write (u, "(A)") "* Initialization: create observable" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () call analysis_init_observable (var_str ("m")) call analysis_record_data (var_str ("m"), 125._default) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$out_file = "commands_22.dat"') call ifile_append (ifile, 'write_analysis') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Display analysis data" write (u, "(A)") inquire (file = "commands_22.dat", exist = exist) if (.not. exist) then write (u, "(A)") "ERROR: File commands_22.dat not found" return end if u_file = free_unit () open (u_file, file = "commands_22.dat", & action = "read", status = "old") do read (u_file, "(A)", iostat = iostat) buffer if (iostat /= 0) exit write (u, "(A)") trim (buffer) end do close (u_file) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_22" end subroutine commands_22 @ %def commands_22 @ \subsubsection{Compile Analysis} Write accumulated analysis data to file and compile. <>= if (MPOST_AVAILABLE) then call test (commands_23, "commands_23", & "compile analysis", & u, results) end if <>= public :: commands_23 <>= subroutine commands_23 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root integer :: u_file, iostat character(256) :: buffer logical :: exist type(graph_options_t) :: graph_options write (u, "(A)") "* Test output: commands_23" write (u, "(A)") "* Purpose: write and compile analysis data" write (u, "(A)") write (u, "(A)") "* Initialization: create and fill histogram" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () call graph_options_init (graph_options) call graph_options_set (graph_options, & title = var_str ("Histogram for test: commands 23"), & description = var_str ("This is a test."), & width_mm = 125, height_mm = 85) call analysis_init_histogram (var_str ("h"), & 0._default, 10._default, 2._default, .false., & graph_options = graph_options) call analysis_record_data (var_str ("h"), 1._default) call analysis_record_data (var_str ("h"), 1._default) call analysis_record_data (var_str ("h"), 1._default) call analysis_record_data (var_str ("h"), 1._default) call analysis_record_data (var_str ("h"), 3._default) call analysis_record_data (var_str ("h"), 3._default) call analysis_record_data (var_str ("h"), 3._default) call analysis_record_data (var_str ("h"), 5._default) call analysis_record_data (var_str ("h"), 7._default) call analysis_record_data (var_str ("h"), 7._default) call analysis_record_data (var_str ("h"), 7._default) call analysis_record_data (var_str ("h"), 7._default) call analysis_record_data (var_str ("h"), 9._default) call analysis_record_data (var_str ("h"), 9._default) call analysis_record_data (var_str ("h"), 9._default) call analysis_record_data (var_str ("h"), 9._default) call analysis_record_data (var_str ("h"), 9._default) call analysis_record_data (var_str ("h"), 9._default) call analysis_record_data (var_str ("h"), 9._default) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$out_file = "commands_23.dat"') call ifile_append (ifile, 'compile_analysis') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Delete Postscript output" write (u, "(A)") inquire (file = "commands_23.ps", exist = exist) if (exist) then u_file = free_unit () open (u_file, file = "commands_23.ps", action = "write", status = "old") close (u_file, status = "delete") end if inquire (file = "commands_23.ps", exist = exist) write (u, "(1x,A,L1)") "Postcript output exists = ", exist write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* TeX file" write (u, "(A)") inquire (file = "commands_23.tex", exist = exist) if (.not. exist) then write (u, "(A)") "ERROR: File commands_23.tex not found" return end if u_file = free_unit () open (u_file, file = "commands_23.tex", & action = "read", status = "old") do read (u_file, "(A)", iostat = iostat) buffer if (iostat /= 0) exit write (u, "(A)") trim (buffer) end do close (u_file) write (u, *) inquire (file = "commands_23.ps", exist = exist) write (u, "(1x,A,L1)") "Postcript output exists = ", exist write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_23" end subroutine commands_23 @ %def commands_23 @ \subsubsection{Histogram} Declare a histogram, fill it and display. <>= call test (commands_24, "commands_24", & "drawing options", & u, results) <>= public :: commands_24 <>= subroutine commands_24 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_24" write (u, "(A)") "* Purpose: check graph and drawing options" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, '$title = "Title"') call ifile_append (ifile, '$description = "Description"') call ifile_append (ifile, '$x_label = "X Label"') call ifile_append (ifile, '$y_label = "Y Label"') call ifile_append (ifile, 'graph_width_mm = 111') call ifile_append (ifile, 'graph_height_mm = 222') call ifile_append (ifile, 'x_min = -11') call ifile_append (ifile, 'x_max = 22') call ifile_append (ifile, 'y_min = -33') call ifile_append (ifile, 'y_max = 44') call ifile_append (ifile, '$gmlcode_bg = "GML Code BG"') call ifile_append (ifile, '$gmlcode_fg = "GML Code FG"') call ifile_append (ifile, '$fill_options = "Fill Options"') call ifile_append (ifile, '$draw_options = "Draw Options"') call ifile_append (ifile, '$err_options = "Error Options"') call ifile_append (ifile, '$symbol = "Symbol"') call ifile_append (ifile, 'histogram foo (0,1)') call ifile_append (ifile, 'plot bar') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Display analysis store" write (u, "(A)") call analysis_write (u, verbose=.true.) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_24" end subroutine commands_24 @ %def commands_24 @ \subsubsection{Local Environment} Declare a local environment. <>= call test (commands_25, "commands_25", & "local process environment", & u, results) <>= public :: commands_25 <>= subroutine commands_25 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_25" write (u, "(A)") "* Purpose: declare local environment for process" write (u, "(A)") call syntax_model_file_init () call syntax_cmd_list_init () call global%global_init () call global%var_list%set_log (var_str ("?omega_openmp"), & .false., is_known = .true.) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'library = "commands_25_lib"') call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process commands_25_p1 = g, g => g, g & &{ model = "QCD" }') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_libraries (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_25" end subroutine commands_25 @ %def commands_25 @ \subsubsection{Alternative Setups} Declare a list of alternative setups. <>= call test (commands_26, "commands_26", & "alternative setups", & u, results) <>= public :: commands_26 <>= subroutine commands_26 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_26" write (u, "(A)") "* Purpose: declare alternative setups for simulation" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'int i = 0') call ifile_append (ifile, 'alt_setup = ({ i = 1 }, { i = 2 })') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_expr (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_26" end subroutine commands_26 @ %def commands_26 @ \subsubsection{Unstable Particle} Define decay processes and declare a particle as unstable. Also check the commands stable, polarized, unpolarized. <>= call test (commands_27, "commands_27", & "unstable and polarized particles", & u, results) <>= public :: commands_27 <>= subroutine commands_27 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib write (u, "(A)") "* Test output: commands_27" write (u, "(A)") "* Purpose: modify particle properties" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call global%global_init () call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) allocate (lib) call lib%init (var_str ("commands_27_lib")) call global%add_prclib (lib) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'ff = 0.4') call ifile_append (ifile, 'process d1 = s => f, fbar') call ifile_append (ifile, 'unstable s (d1)') call ifile_append (ifile, 'polarized f, fbar') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Show model" write (u, "(A)") call global%model%write (u) write (u, "(A)") write (u, "(A)") "* Extra Input" write (u, "(A)") call ifile_final (ifile) call ifile_append (ifile, '?diagonal_decay = true') call ifile_append (ifile, 'unstable s (d1)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%final () call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Show model" write (u, "(A)") call global%model%write (u) write (u, "(A)") write (u, "(A)") "* Extra Input" write (u, "(A)") call ifile_final (ifile) call ifile_append (ifile, '?isotropic_decay = true') call ifile_append (ifile, 'unstable s (d1)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%final () call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Show model" write (u, "(A)") call global%model%write (u) write (u, "(A)") write (u, "(A)") "* Extra Input" write (u, "(A)") call ifile_final (ifile) call ifile_append (ifile, 'stable s') call ifile_append (ifile, 'unpolarized f') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%final () call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Show model" write (u, "(A)") call global%model%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_model_file_init () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_27" end subroutine commands_27 @ %def commands_27 @ \subsubsection{Quit the program} Quit the program. <>= call test (commands_28, "commands_28", & "quit", & u, results) <>= public :: commands_28 <>= subroutine commands_28 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root1, pn_root2 type(string_t), dimension(0) :: no_vars write (u, "(A)") "* Test output: commands_28" write (u, "(A)") "* Purpose: quit the program" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file: quit without code" write (u, "(A)") call ifile_append (ifile, 'quit') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root1, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root1, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write (u, vars = no_vars) write (u, "(A)") write (u, "(A)") "* Input file: quit with code" write (u, "(A)") call ifile_final (ifile) call command_list%final () call ifile_append (ifile, 'quit ( 3 + 4 )') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root2, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root2, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write (u, vars = no_vars) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_28" end subroutine commands_28 @ %def commands_28 @ \subsubsection{SLHA interface} Testing commands steering the SLHA interface. <>= call test (commands_29, "commands_29", & "SLHA interface", & u, results) <>= public :: commands_29 <>= subroutine commands_29 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(var_list_t), pointer :: model_vars type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_29" write (u, "(A)") "* Purpose: test SLHA interface" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call syntax_model_file_init () call syntax_slha_init () call global%global_init () write (u, "(A)") "* Model MSSM, read SLHA file" write (u, "(A)") call ifile_append (ifile, 'model = "MSSM"') call ifile_append (ifile, '?slha_read_decays = true') call ifile_append (ifile, 'read_slha ("sps1ap_decays.slha")') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Model MSSM, default values:" write (u, "(A)") call global%model%write (u, verbose = .false., & show_vertices = .false., show_particles = .false.) write (u, "(A)") write (u, "(A)") "* Selected global variables" write (u, "(A)") model_vars => global%model%get_var_list_ptr () call model_vars%write_var (var_str ("mch1"), u) call model_vars%write_var (var_str ("wch1"), u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") "* Model MSSM, values from SLHA file" write (u, "(A)") call global%model%write (u, verbose = .false., & show_vertices = .false., show_particles = .false.) write (u, "(A)") write (u, "(A)") "* Selected global variables" write (u, "(A)") model_vars => global%model%get_var_list_ptr () call model_vars%write_var (var_str ("mch1"), u) call model_vars%write_var (var_str ("wch1"), u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_slha_final () call syntax_model_file_final () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_29" end subroutine commands_29 @ %def commands_29 @ \subsubsection{Expressions for scales} Declare a scale, factorization scale or factorization scale expression. <>= call test (commands_30, "commands_30", & "scales", & u, results) <>= public :: commands_30 <>= subroutine commands_30 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_30" write (u, "(A)") "* Purpose: define scales" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'scale = 200 GeV') call ifile_append (ifile, & 'factorization_scale = eval Pt [particle]') call ifile_append (ifile, & 'renormalization_scale = eval E [particle]') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_expr (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_30" end subroutine commands_30 @ %def commands_30 @ \subsubsection{Weight and reweight expressions} Declare an expression for event weights and reweighting. <>= call test (commands_31, "commands_31", & "event weights/reweighting", & u, results) <>= public :: commands_31 <>= subroutine commands_31 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_31" write (u, "(A)") "* Purpose: define weight/reweight" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'weight = eval Pz [particle]') call ifile_append (ifile, 'reweight = eval M2 [particle]') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_expr (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_31" end subroutine commands_31 @ %def commands_31 @ \subsubsection{Selecting events} Declare an expression for selecting events in an analysis. <>= call test (commands_32, "commands_32", & "event selection", & u, results) <>= public :: commands_32 <>= subroutine commands_32 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root write (u, "(A)") "* Test output: commands_32" write (u, "(A)") "* Purpose: define selection" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'selection = any PDG == 13 [particle]') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) call global%write_expr (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_32" end subroutine commands_32 @ %def commands_32 @ \subsubsection{Executing shell commands} Execute a shell command. <>= call test (commands_33, "commands_33", & "execute shell command", & u, results) <>= public :: commands_33 <>= subroutine commands_33 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root integer :: u_file, iostat character(3) :: buffer write (u, "(A)") "* Test output: commands_33" write (u, "(A)") "* Purpose: execute shell command" write (u, "(A)") write (u, "(A)") "* Initialization" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'exec ("echo foo >> bar")') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root, u) write (u, "(A)") write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) u_file = free_unit () open (u_file, file = "bar", & action = "read", status = "old") do read (u_file, "(A)", iostat = iostat) buffer if (iostat /= 0) exit end do write (u, "(A,A)") "should be 'foo': ", trim (buffer) close (u_file) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call command_list%final () call global%final () call syntax_cmd_list_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_33" end subroutine commands_33 @ %def commands_33 @ \subsubsection{Callback} Instead of an explicit write, use the callback feature to write the analysis file during event generation. We generate 4 events and arrange that the callback is executed while writing the 3rd event. <>= call test (commands_34, "commands_34", & "analysis via callback", & u, results) <>= public :: commands_34 <>= subroutine commands_34 (u) integer, intent(in) :: u type(ifile_t) :: ifile type(command_list_t), target :: command_list type(rt_data_t), target :: global type(parse_node_t), pointer :: pn_root type(prclib_entry_t), pointer :: lib type(event_callback_34_t) :: event_callback write (u, "(A)") "* Test output: commands_34" write (u, "(A)") "* Purpose: write analysis data" write (u, "(A)") write (u, "(A)") "* Initialization: create observable" write (u, "(A)") call syntax_cmd_list_init () call global%global_init () call syntax_model_file_init () call global%global_init () call global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) call global%var_list%set_string (var_str ("$method"), & var_str ("unit_test"), is_known=.true.) call global%var_list%set_string (var_str ("$phs_method"), & var_str ("single"), is_known=.true.) call global%var_list%set_string (var_str ("$integration_method"),& var_str ("midpoint"), is_known=.true.) call global%var_list%set_real (var_str ("sqrts"), & 1000._default, is_known=.true.) call global%var_list%set_log (var_str ("?vis_history"),& .false., is_known=.true.) call global%var_list%set_log (var_str ("?integration_timer"),& .false., is_known = .true.) call global%var_list%set_int (var_str ("seed"), 0, is_known=.true.) allocate (lib) call lib%init (var_str ("lib_cmd34")) call global%add_prclib (lib) write (u, "(A)") "* Prepare callback for writing analysis to I/O unit" write (u, "(A)") event_callback%u = u call global%set_event_callback (event_callback) write (u, "(A)") "* Input file" write (u, "(A)") call ifile_append (ifile, 'model = "Test"') call ifile_append (ifile, 'process commands_34_p = s, s => s, s') call ifile_append (ifile, 'compile') call ifile_append (ifile, 'iterations = 1:1000') call ifile_append (ifile, 'integrate (commands_34_p)') call ifile_append (ifile, 'observable sq') call ifile_append (ifile, 'analysis = record sq (sqrts)') call ifile_append (ifile, 'n_events = 4') call ifile_append (ifile, 'event_callback_interval = 3') call ifile_append (ifile, 'simulate (commands_34_p)') call ifile_write (ifile, u) write (u, "(A)") write (u, "(A)") "* Parse file" write (u, "(A)") call parse_ifile (ifile, pn_root) write (u, "(A)") "* Compile command list" write (u, "(A)") call command_list%compile (pn_root, global) call command_list%write (u) write (u, "(A)") write (u, "(A)") "* Execute command list" write (u, "(A)") call command_list%execute (global) write (u, "(A)") write (u, "(A)") "* Cleanup" call ifile_final (ifile) call analysis_final () call command_list%final () call global%final () call syntax_cmd_list_final () call syntax_model_file_final () write (u, "(A)") write (u, "(A)") "* Test output end: commands_34" end subroutine commands_34 @ %def commands_34 @ For this test, we invent a callback object which simply writes the analysis file, using the standard call for this. Here we rely on the fact that the analysis data are stored as a global entity, otherwise we would have to access them via the event object. <>= type, extends (event_callback_t) :: event_callback_34_t private integer :: u = 0 contains procedure :: write => event_callback_34_write procedure :: proc => event_callback_34 end type event_callback_34_t @ %def event_callback_t @ The output routine is unused. The actual callback should write the analysis data to the output unit that we have injected into the callback object. <>= subroutine event_callback_34_write (event_callback, unit) class(event_callback_34_t), intent(in) :: event_callback integer, intent(in), optional :: unit end subroutine event_callback_34_write subroutine event_callback_34 (event_callback, i, event) class(event_callback_34_t), intent(in) :: event_callback integer(i64), intent(in) :: i class(generic_event_t), intent(in) :: event call analysis_write (event_callback%u) end subroutine event_callback_34 @ %def event_callback_34_write @ %def event_callback_34 @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Toplevel module WHIZARD} <<[[whizard.f90]]>>= <> module whizard use io_units <> use system_defs, only: VERSION_STRING use system_defs, only: EOF, BACKSLASH use diagnostics use os_interface use ifiles use lexers use parser use eval_trees use models use phs_forests use prclib_stacks use slha_interface use blha_config use rt_data use commands <> <> <> <> save contains <> end module whizard @ %def whizard @ \subsection{Options} Here we introduce a wrapper that holds various user options, so they can transparently be passed from the main program to the [[whizard]] object. Most parameters are used for initializing the [[global]] state. <>= public :: whizard_options_t <>= type :: whizard_options_t type(string_t) :: job_id type(string_t), dimension(:), allocatable :: pack_args type(string_t), dimension(:), allocatable :: unpack_args type(string_t) :: preload_model type(string_t) :: default_lib type(string_t) :: preload_libraries logical :: rebuild_library = .false. logical :: recompile_library = .false. logical :: rebuild_phs = .false. logical :: rebuild_grids = .false. logical :: rebuild_events = .false. end type whizard_options_t @ %def whizard_options_t @ \subsection{Parse tree stack} We collect all parse trees that we generate in the [[whizard]] object. To this end, we create a stack of parse trees. They must not be finalized before the [[global]] object is finalized, because items such as a cut definition may contain references to the parse tree from which they were generated. <>= type, extends (parse_tree_t) :: pt_entry_t type(pt_entry_t), pointer :: previous => null () end type pt_entry_t @ %def pt_entry_t @ This is the stack. Since we always prepend, we just need the [[last]] pointer. <>= type :: pt_stack_t type(pt_entry_t), pointer :: last => null () contains <> end type pt_stack_t @ %def pt_stack_t @ The finalizer is called at the very end. <>= procedure :: final => pt_stack_final <>= subroutine pt_stack_final (pt_stack) class(pt_stack_t), intent(inout) :: pt_stack type(pt_entry_t), pointer :: current do while (associated (pt_stack%last)) current => pt_stack%last pt_stack%last => current%previous call parse_tree_final (current%parse_tree_t) deallocate (current) end do end subroutine pt_stack_final @ %def pt_stack_final @ Create and push a new entry, keeping the previous ones. <>= procedure :: push => pt_stack_push <>= subroutine pt_stack_push (pt_stack, parse_tree) class(pt_stack_t), intent(inout) :: pt_stack type(parse_tree_t), intent(out), pointer :: parse_tree type(pt_entry_t), pointer :: current allocate (current) parse_tree => current%parse_tree_t current%previous => pt_stack%last pt_stack%last => current end subroutine pt_stack_push @ %def pt_stack_push @ \subsection{The [[whizard]] object} An object of type [[whizard_t]] is the top-level wrapper for a \whizard\ instance. The object holds various default settings and the current state of the generator, the [[global]] object of type [[rt_data_t]]. This object contains, for instance, the list of variables and the process libraries. Since components of the [[global]] subobject are frequently used as targets, the [[whizard]] object should also consistently carry the [[target]] attribute. The various self-tests do no not use this object. They initialize only specific subsets of the system, according to their needs. Note: we intend to allow several concurrent instances. In the current implementation, there are still a few obstacles to this: the model library and the syntax tables are global variables, and the error handling uses global state. This should be improved. <>= public :: whizard_t <>= type :: whizard_t type(whizard_options_t) :: options type(rt_data_t) :: global type(pt_stack_t) :: pt_stack contains <> end type whizard_t @ %def whizard_t @ \subsection{Initialization and finalization} <>= procedure :: init => whizard_init <>= subroutine whizard_init (whizard, options, paths, logfile) class(whizard_t), intent(out), target :: whizard type(whizard_options_t), intent(in) :: options type(paths_t), intent(in), optional :: paths type(string_t), intent(in), optional :: logfile call init_syntax_tables () whizard%options = options call whizard%global%global_init (paths, logfile) call whizard%init_job_id () call whizard%init_rebuild_flags () call whizard%unpack_files () call whizard%preload_model () call whizard%preload_library () call whizard%global%init_fallback_model & (var_str ("SM_hadrons"), var_str ("SM_hadrons.mdl")) end subroutine whizard_init @ %def whizard_init @ Apart from the global data which have been initialized above, the process and model lists need to be finalized. <>= procedure :: final => whizard_final <>= subroutine whizard_final (whizard) class(whizard_t), intent(inout), target :: whizard call whizard%global%final () call whizard%pt_stack%final () call whizard%pack_files () call final_syntax_tables () end subroutine whizard_final @ %def whizard_final @ Set the job ID, if nonempty. If the ID string is empty, the value remains undefined. <>= procedure :: init_job_id => whizard_init_job_id <>= subroutine whizard_init_job_id (whizard) class(whizard_t), intent(inout), target :: whizard associate (var_list => whizard%global%var_list, options => whizard%options) if (options%job_id /= "") then call var_list%set_string (var_str ("$job_id"), & options%job_id, is_known=.true.) end if end associate end subroutine whizard_init_job_id @ %def whizard_init_job_id @ Set the rebuild flags. They can be specified on the command line and set the initial value for the associated logical variables. <>= procedure :: init_rebuild_flags => whizard_init_rebuild_flags <>= subroutine whizard_init_rebuild_flags (whizard) class(whizard_t), intent(inout), target :: whizard associate (var_list => whizard%global%var_list, options => whizard%options) call var_list%append_log (var_str ("?rebuild_library"), & options%rebuild_library, intrinsic=.true.) call var_list%append_log (var_str ("?recompile_library"), & options%recompile_library, intrinsic=.true.) call var_list%append_log (var_str ("?rebuild_phase_space"), & options%rebuild_phs, intrinsic=.true.) call var_list%append_log (var_str ("?rebuild_grids"), & options%rebuild_grids, intrinsic=.true.) call var_list%append_log (var_str ("?rebuild_events"), & options%rebuild_events, intrinsic=.true.) end associate end subroutine whizard_init_rebuild_flags @ %def whizard_init_rebuild_flags @ Pack/unpack files in the working directory, if requested. <>= procedure :: pack_files => whizard_pack_files procedure :: unpack_files => whizard_unpack_files <>= subroutine whizard_pack_files (whizard) class(whizard_t), intent(in), target :: whizard logical :: exist integer :: i type(string_t) :: file if (allocated (whizard%options%pack_args)) then do i = 1, size (whizard%options%pack_args) file = whizard%options%pack_args(i) call msg_message ("Packing file/dir '" // char (file) // "'") exist = os_file_exist (file) .or. os_dir_exist (file) if (exist) then call os_pack_file (whizard%options%pack_args(i), & whizard%global%os_data) else call msg_error ("File/dir '" // char (file) // "' not found") end if end do end if end subroutine whizard_pack_files subroutine whizard_unpack_files (whizard) class(whizard_t), intent(in), target :: whizard logical :: exist integer :: i type(string_t) :: file if (allocated (whizard%options%unpack_args)) then do i = 1, size (whizard%options%unpack_args) file = whizard%options%unpack_args(i) call msg_message ("Unpacking file '" // char (file) // "'") exist = os_file_exist (file) if (exist) then call os_unpack_file (whizard%options%unpack_args(i), & whizard%global%os_data) else call msg_error ("File '" // char (file) // "' not found") end if end do end if end subroutine whizard_unpack_files @ %def whizard_pack_files @ %def whizard_unpack_files @ This procedure preloads a model, if a model name is given. <>= procedure :: preload_model => whizard_preload_model <>= subroutine whizard_preload_model (whizard) class(whizard_t), intent(inout), target :: whizard type(string_t) :: model_name model_name = whizard%options%preload_model if (model_name /= "") then call whizard%global%read_model (model_name, whizard%global%preload_model) whizard%global%model => whizard%global%preload_model if (associated (whizard%global%model)) then call whizard%global%model%link_var_list (whizard%global%var_list) call whizard%global%var_list%set_string (var_str ("$model_name"), & model_name, is_known = .true.) call msg_message ("Preloaded model: " & // char (model_name)) else call msg_fatal ("Preloading model " // char (model_name) & // " failed") end if else call msg_message ("No model preloaded") end if end subroutine whizard_preload_model @ %def whizard_preload_model @ This procedure preloads a library, if a library name is given. Note: This version just opens a new library with that name. It does not load (yet) an existing library on file, as previous \whizard\ versions would do. <>= procedure :: preload_library => whizard_preload_library <>= subroutine whizard_preload_library (whizard) class(whizard_t), intent(inout), target :: whizard type(string_t) :: library_name, libs type(string_t), dimension(:), allocatable :: libname_static type(prclib_entry_t), pointer :: lib_entry integer :: i call get_prclib_static (libname_static) do i = 1, size (libname_static) allocate (lib_entry) call lib_entry%init_static (libname_static(i)) call whizard%global%add_prclib (lib_entry) end do libs = adjustl (whizard%options%preload_libraries) if (libs == "" .and. whizard%options%default_lib /= "") then allocate (lib_entry) call lib_entry%init (whizard%options%default_lib) call whizard%global%add_prclib (lib_entry) call msg_message ("Preloaded library: " // & char (whizard%options%default_lib)) end if SCAN_LIBS: do while (libs /= "") call split (libs, library_name, " ") if (library_name /= "") then allocate (lib_entry) call lib_entry%init (library_name) call whizard%global%add_prclib (lib_entry) call msg_message ("Preloaded library: " // char (library_name)) end if end do SCAN_LIBS end subroutine whizard_preload_library @ %def whizard_preload_library @ \subsection{Initialization and finalization: syntax tables} Initialize/finalize the syntax tables used by WHIZARD. These are effectively singleton objects. We introduce a module variable that tracks the initialization status. Without syntax tables, essentially nothing will work. Any initializer has to call this. <>= logical :: syntax_tables_exist = .false. @ %def syntax_tables_exist @ <>= public :: init_syntax_tables public :: final_syntax_tables <>= subroutine init_syntax_tables () if (.not. syntax_tables_exist) then call syntax_model_file_init () call syntax_phs_forest_init () call syntax_pexpr_init () call syntax_slha_init () call syntax_cmd_list_init () syntax_tables_exist = .true. end if end subroutine init_syntax_tables subroutine final_syntax_tables () if (syntax_tables_exist) then call syntax_model_file_final () call syntax_phs_forest_final () call syntax_pexpr_final () call syntax_slha_final () call syntax_cmd_list_final () syntax_tables_exist = .false. end if end subroutine final_syntax_tables @ %def init_syntax_tables @ %def final_syntax_tables @ Write the syntax tables to external files. <>= public :: write_syntax_tables <>= subroutine write_syntax_tables () integer :: unit character(*), parameter :: file_model = "whizard.model_file.syntax" character(*), parameter :: file_phs = "whizard.phase_space_file.syntax" character(*), parameter :: file_pexpr = "whizard.prt_expressions.syntax" character(*), parameter :: file_slha = "whizard.slha.syntax" character(*), parameter :: file_sindarin = "whizard.sindarin.syntax" if (.not. syntax_tables_exist) call init_syntax_tables () unit = free_unit () print *, "Writing file '" // file_model // "'" open (unit=unit, file=file_model, status="replace", action="write") write (unit, "(A)") VERSION_STRING write (unit, "(A)") "Syntax definition file: " // file_model call syntax_model_file_write (unit) close (unit) print *, "Writing file '" // file_phs // "'" open (unit=unit, file=file_phs, status="replace", action="write") write (unit, "(A)") VERSION_STRING write (unit, "(A)") "Syntax definition file: " // file_phs call syntax_phs_forest_write (unit) close (unit) print *, "Writing file '" // file_pexpr // "'" open (unit=unit, file=file_pexpr, status="replace", action="write") write (unit, "(A)") VERSION_STRING write (unit, "(A)") "Syntax definition file: " // file_pexpr call syntax_pexpr_write (unit) close (unit) print *, "Writing file '" // file_slha // "'" open (unit=unit, file=file_slha, status="replace", action="write") write (unit, "(A)") VERSION_STRING write (unit, "(A)") "Syntax definition file: " // file_slha call syntax_slha_write (unit) close (unit) print *, "Writing file '" // file_sindarin // "'" open (unit=unit, file=file_sindarin, status="replace", action="write") write (unit, "(A)") VERSION_STRING write (unit, "(A)") "Syntax definition file: " // file_sindarin call syntax_cmd_list_write (unit) close (unit) end subroutine write_syntax_tables @ %def write_syntax_tables @ \subsection{Execute command lists} Process commands given on the command line, stored as an [[ifile]]. The whole input is read, compiled and executed as a whole. <>= procedure :: process_ifile => whizard_process_ifile <>= subroutine whizard_process_ifile (whizard, ifile, quit, quit_code) class(whizard_t), intent(inout), target :: whizard type(ifile_t), intent(in) :: ifile logical, intent(out) :: quit integer, intent(out) :: quit_code type(lexer_t), target :: lexer type(stream_t), target :: stream call msg_message ("Reading commands given on the command line") call lexer_init_cmd_list (lexer) call stream_init (stream, ifile) call whizard%process_stream (stream, lexer, quit, quit_code) call stream_final (stream) call lexer_final (lexer) end subroutine whizard_process_ifile @ %def whizard_process_ifile @ Process standard input as a command list. The whole input is read, compiled and executed as a whole. <>= procedure :: process_stdin => whizard_process_stdin <>= subroutine whizard_process_stdin (whizard, quit, quit_code) class(whizard_t), intent(inout), target :: whizard logical, intent(out) :: quit integer, intent(out) :: quit_code type(lexer_t), target :: lexer type(stream_t), target :: stream call msg_message ("Reading commands from standard input") call lexer_init_cmd_list (lexer) call stream_init (stream, 5) call whizard%process_stream (stream, lexer, quit, quit_code) call stream_final (stream) call lexer_final (lexer) end subroutine whizard_process_stdin @ %def whizard_process_stdin @ Process a file as a command list. <>= procedure :: process_file => whizard_process_file <>= subroutine whizard_process_file (whizard, file, quit, quit_code) class(whizard_t), intent(inout), target :: whizard type(string_t), intent(in) :: file logical, intent(out) :: quit integer, intent(out) :: quit_code type(lexer_t), target :: lexer type(stream_t), target :: stream logical :: exist call msg_message ("Reading commands from file '" // char (file) // "'") inquire (file=char(file), exist=exist) if (exist) then call lexer_init_cmd_list (lexer) call stream_init (stream, char (file)) call whizard%process_stream (stream, lexer, quit, quit_code) call stream_final (stream) call lexer_final (lexer) else call msg_error ("File '" // char (file) // "' not found") end if end subroutine whizard_process_file @ %def whizard_process_file @ <>= procedure :: process_stream => whizard_process_stream <>= subroutine whizard_process_stream (whizard, stream, lexer, quit, quit_code) class(whizard_t), intent(inout), target :: whizard type(stream_t), intent(inout), target :: stream type(lexer_t), intent(inout), target :: lexer logical, intent(out) :: quit integer, intent(out) :: quit_code type(parse_tree_t), pointer :: parse_tree type(command_list_t), target :: command_list call lexer_assign_stream (lexer, stream) call whizard%pt_stack%push (parse_tree) call parse_tree_init (parse_tree, syntax_cmd_list, lexer) if (associated (parse_tree%get_root_ptr ())) then whizard%global%lexer => lexer call command_list%compile (parse_tree%get_root_ptr (), & whizard%global) end if call whizard%global%activate () call command_list%execute (whizard%global) call command_list%final () quit = whizard%global%quit quit_code = whizard%global%quit_code end subroutine whizard_process_stream @ %def whizard_process_stream @ \subsection{The WHIZARD shell} This procedure implements interactive mode. One line is processed at a time. <>= procedure :: shell => whizard_shell <>= subroutine whizard_shell (whizard, quit_code) class(whizard_t), intent(inout), target :: whizard integer, intent(out) :: quit_code type(lexer_t), target :: lexer type(stream_t), target :: stream type(string_t) :: prompt1 type(string_t) :: prompt2 type(string_t) :: input type(string_t) :: extra integer :: last integer :: iostat logical :: mask_tmp logical :: quit call msg_message ("Launching interactive shell") call lexer_init_cmd_list (lexer) prompt1 = "whish? " prompt2 = " > " COMMAND_LOOP: do call put (6, prompt1) call get (5, input, iostat=iostat) if (iostat > 0 .or. iostat == EOF) exit COMMAND_LOOP CONTINUE_INPUT: do last = len_trim (input) if (extract (input, last, last) /= BACKSLASH) exit CONTINUE_INPUT call put (6, prompt2) call get (5, extra, iostat=iostat) if (iostat > 0) exit COMMAND_LOOP input = replace (input, last, extra) end do CONTINUE_INPUT call stream_init (stream, input) mask_tmp = mask_fatal_errors mask_fatal_errors = .true. call whizard%process_stream (stream, lexer, quit, quit_code) msg_count = 0 mask_fatal_errors = mask_tmp call stream_final (stream) if (quit) exit COMMAND_LOOP end do COMMAND_LOOP print * call lexer_final (lexer) end subroutine whizard_shell @ %def whizard_shell @ \clearpage %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Query Feature Support} This module accesses the various optional features (modules) that WHIZARD can support and repors on their availability. <<[[features.f90]]>>= module features use string_utils, only: lower_case use system_dependencies, only: WHIZARD_VERSION <> <> <> contains <> end module features @ %def features @ \subsection{Output} <>= public :: print_features <>= subroutine print_features () print "(A)", "WHIZARD " // WHIZARD_VERSION print "(A)", "Build configuration:" <> print "(A)", "Optional features available in this build:" <> end subroutine print_features @ %def print_features @ \subsection{Query function} <>= subroutine check (feature, recognized, result, help) character(*), intent(in) :: feature logical, intent(out) :: recognized character(*), intent(out) :: result, help recognized = .true. result = "no" select case (lower_case (trim (feature))) <> case default recognized = .false. end select end subroutine check @ %def check @ Print this result: <>= subroutine print_check (feature) character(*), intent(in) :: feature character(16) :: f logical :: recognized character(10) :: result character(48) :: help call check (feature, recognized, result, help) if (.not. recognized) then result = "unknown" help = "" end if f = feature print "(2x,A,1x,A,'(',A,')')", f, result, trim (help) end subroutine print_check @ %def print_check @ \subsection{Basic configuration} <>= call print_check ("precision") <>= use kinds, only: default <>= case ("precision") write (result, "(I0)") precision (1._default) help = "significant decimals of real/complex numbers" @ \subsection{Optional features case by case} <>= call print_check ("OpenMP") <>= use system_dependencies, only: openmp_is_active <>= case ("openmp") if (openmp_is_active ()) then result = "yes" end if help = "OpenMP parallel execution" @ <>= call print_check ("GoSam") <>= use system_dependencies, only: GOSAM_AVAILABLE <>= case ("gosam") if (GOSAM_AVAILABLE) then result = "yes" end if help = "external NLO matrix element provider" @ <>= call print_check ("OpenLoops") <>= use system_dependencies, only: OPENLOOPS_AVAILABLE <>= case ("openloops") if (OPENLOOPS_AVAILABLE) then result = "yes" end if help = "external NLO matrix element provider" @ <>= call print_check ("Recola") <>= use system_dependencies, only: RECOLA_AVAILABLE <>= case ("recola") if (RECOLA_AVAILABLE) then result = "yes" end if help = "external NLO matrix element provider" @ <>= call print_check ("LHAPDF") <>= use system_dependencies, only: LHAPDF5_AVAILABLE use system_dependencies, only: LHAPDF6_AVAILABLE <>= case ("lhapdf") if (LHAPDF5_AVAILABLE) then result = "v5" else if (LHAPDF6_AVAILABLE) then result = "v6" end if help = "PDF library" @ <>= call print_check ("HOPPET") <>= use system_dependencies, only: HOPPET_AVAILABLE <>= case ("hoppet") if (HOPPET_AVAILABLE) then result = "yes" end if help = "PDF evolution package" @ <>= call print_check ("fastjet") <>= use jets, only: fastjet_available <>= case ("fastjet") if (fastjet_available ()) then result = "yes" end if help = "jet-clustering package" @ <>= call print_check ("Pythia6") <>= use system_dependencies, only: PYTHIA6_AVAILABLE <>= case ("pythia6") if (PYTHIA6_AVAILABLE) then result = "yes" end if help = "direct access for shower/hadronization" @ <>= call print_check ("Pythia8") <>= use system_dependencies, only: PYTHIA8_AVAILABLE <>= case ("pythia8") if (PYTHIA8_AVAILABLE) then result = "yes" end if help = "direct access for shower/hadronization" @ <>= call print_check ("StdHEP") <>= case ("stdhep") result = "yes" help = "event I/O format" @ <>= call print_check ("HepMC") <>= use hepmc_interface, only: hepmc_is_available <>= case ("hepmc") if (hepmc_is_available ()) then result = "yes" end if help = "event I/O format" @ <>= call print_check ("LCIO") <>= use lcio_interface, only: lcio_is_available <>= case ("lcio") if (lcio_is_available ()) then result = "yes" end if help = "event I/O format" @ <>= call print_check ("MetaPost") <>= use system_dependencies, only: EVENT_ANALYSIS <>= case ("metapost") result = EVENT_ANALYSIS help = "graphical event analysis via LaTeX/MetaPost" @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Index: trunk/src/process_integration/process_integration.nw =================================================================== --- trunk/src/process_integration/process_integration.nw (revision 8753) +++ trunk/src/process_integration/process_integration.nw (revision 8754) @@ -1,19589 +1,19623 @@ % -*- ess-noweb-default-code-mode: f90-mode; noweb-default-code-mode: f90-mode; -*- % WHIZARD code as NOWEB source: integration and process objects and such %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Integration and Process Objects} \includemodulegraph{process_integration} This is the central part of the \whizard\ package. It provides the functionality for evaluating structure functions, kinematics and matrix elements, integration and event generation. It combines the various parts that deal with those tasks individually and organizes the data transfer between them. \begin{description} \item[subevt\_expr] This enables process observables as (abstract) expressions, to be evaluated for each process call. \item[parton\_states] A [[parton_state_t]] object represents an elementary partonic interaction. There are two versions: one for the isolated elementary process, one for the elementary process convoluted with the structure-function chain. The parton state is an effective state. It needs not coincide with the seed-kinematics state which is used in evaluating phase space. \item[process] Here, all pieces are combined for the purpose of evaluating the elementary processes. The whole algorithm is coded in terms of abstract data types as defined in the appropriate modules: [[prc_core]] for matrix-element evaluation, [[prc_core_def]] for the associated configuration and driver, [[sf_base]] for beams and structure-functions, [[phs_base]] for phase space, and [[mci_base]] for integration and event generation. \item[process\_config] \item[process\_counter] Very simple object for statistics \item[process\_mci] \item[pcm] \item[kinematics] \item[instances] While the above modules set up all static information, the instances have the changing event data. There are term and process instances but no component instances. \item[process\_stacks] Process stacks collect process objects. \end{description} We combine here hard interactions, phase space, and (for scatterings) structure functions and interfaces them to the integration module. The process object implements the combination of a fixed beam and structure-function setup with a number of elementary processes. The latter are called process components. The process object represents an entity which is supposedly observable. It should be meaningful to talk about the cross section of a process. The individual components of a process are, technically, processes themselves, but they may have unphysical cross sections which have to be added for a physical result. Process components may be exclusive tree-level elementary processes, dipole subtraction term, loop corrections, etc. The beam and structure function setup is common to all process components. Thus, there is only one instance of this part. The process may be a scattering process or a decay process. In the latter case, there are no structure functions, and the beam setup consists of a single particle. Otherwise, the two classes are treated on the same footing. Once a sampling point has been chosen, a process determines a set of partons with a correlated density matrix of quantum numbers. In general, each sampling point will generate, for each process component, one or more distinct parton configurations. This is the [[computed]] state. The computed state is the subject of the multi-channel integration algorithm. For NLO computations, it is necessary to project the computed states onto another set of parton configurations (e.g., by recombining certain pairs). This is the [[observed]] state. When computing partonic observables, the information is taken from the observed state. For the purpose of event generation, we will later select one parton configuration from the observed state and collapse the correlated quantum state. This configuration is then dressed by applying parton shower, decays and hadronization. The decay chain, in particular, combines a scattering process with possible subsequent decay processes on the parton level, which are full-fledged process objects themselves. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process observables} We define an abstract [[subevt_expr_t]] object as an extension of the [[subevt_t]] type. The object contains a local variable list, variable instances (as targets for pointers in the variable list), and evaluation trees. The evaluation trees reference both the variables and the [[subevt]]. There are two instances of the abstract type: one for process instances, one for physical events. Both have a common logical expression [[selection]] which determines whether the object passes user-defined cuts. The intention is that we fill the [[subevt_t]] base object and compute the variables once we have evaluated a kinematical phase space point (or a complete event). We then evaluate the expressions and can use the results in further calculations. The [[process_expr_t]] extension contains furthermore scale and weight expressions. The [[event_expr_t]] extension contains a reweighting-factor expression and a logical expression for event analysis. In practice, we will link the variable list of the [[event_obs]] object to the variable list of the currently active [[process_obs]] object, such that the process variables are available to both objects. Event variables are meaningful only for physical events. Note that there are unit tests, but they are deferred to the [[expr_tests]] module. <<[[subevt_expr.f90]]>>= <> module subevt_expr <> <> use constants, only: zero, one use io_units use format_utils, only: write_separator use diagnostics use lorentz use subevents use variables use flavors use quantum_numbers use interactions use particles use expr_base <> <> <> <> contains <> end module subevt_expr @ %def subevt_expr @ \subsection{Abstract base type} <>= type, extends (subevt_t), abstract :: subevt_expr_t logical :: subevt_filled = .false. type(var_list_t) :: var_list real(default) :: sqrts_hat = 0 integer :: n_in = 0 integer :: n_out = 0 integer :: n_tot = 0 logical :: has_selection = .false. class(expr_t), allocatable :: selection logical :: colorize_subevt = .false. contains <> end type subevt_expr_t @ %def subevt_expr_t @ Output: Base and extended version. We already have a [[write]] routine for the [[subevt_t]] parent type. <>= procedure :: base_write => subevt_expr_write <>= subroutine subevt_expr_write (object, unit, pacified) class(subevt_expr_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: pacified integer :: u u = given_output_unit (unit) write (u, "(1x,A)") "Local variables:" call write_separator (u) call var_list_write (object%var_list, u, follow_link=.false., & pacified = pacified) call write_separator (u) if (object%subevt_filled) then call object%subevt_t%write (u, pacified = pacified) if (object%has_selection) then call write_separator (u) write (u, "(1x,A)") "Selection expression:" call write_separator (u) call object%selection%write (u) end if else write (u, "(1x,A)") "subevt: [undefined]" end if end subroutine subevt_expr_write @ %def subevt_expr_write @ Finalizer. <>= procedure (subevt_expr_final), deferred :: final procedure :: base_final => subevt_expr_final <>= subroutine subevt_expr_final (object) class(subevt_expr_t), intent(inout) :: object call object%var_list%final () if (object%has_selection) then call object%selection%final () end if end subroutine subevt_expr_final @ %def subevt_expr_final @ \subsection{Initialization} Initialization: define local variables and establish pointers. The common variables are [[sqrts]] (the nominal beam energy, fixed), [[sqrts_hat]] (the actual energy), [[n_in]], [[n_out]], and [[n_tot]] for the [[subevt]]. With the exception of [[sqrts]], all are implemented as pointers to subobjects. <>= procedure (subevt_expr_setup_vars), deferred :: setup_vars procedure :: base_setup_vars => subevt_expr_setup_vars <>= subroutine subevt_expr_setup_vars (expr, sqrts) class(subevt_expr_t), intent(inout), target :: expr real(default), intent(in) :: sqrts call expr%var_list%final () call var_list_append_real (expr%var_list, & var_str ("sqrts"), sqrts, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_real_ptr (expr%var_list, & var_str ("sqrts_hat"), expr%sqrts_hat, & is_known = expr%subevt_filled, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_int_ptr (expr%var_list, & var_str ("n_in"), expr%n_in, & is_known = expr%subevt_filled, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_int_ptr (expr%var_list, & var_str ("n_out"), expr%n_out, & is_known = expr%subevt_filled, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_int_ptr (expr%var_list, & var_str ("n_tot"), expr%n_tot, & is_known = expr%subevt_filled, & locked = .true., verbose = .false., intrinsic = .true.) end subroutine subevt_expr_setup_vars @ %def subevt_expr_setup_vars @ Append the subevent expr (its base-type core) itself to the variable list, if it is not yet present. <>= procedure :: setup_var_self => subevt_expr_setup_var_self <>= subroutine subevt_expr_setup_var_self (expr) class(subevt_expr_t), intent(inout), target :: expr if (.not. expr%var_list%contains (var_str ("@evt"))) then call var_list_append_subevt_ptr & (expr%var_list, & var_str ("@evt"), expr%subevt_t, & is_known = expr%subevt_filled, & locked = .true., verbose = .false., intrinsic=.true.) end if end subroutine subevt_expr_setup_var_self @ %def subevt_expr_setup_var_self @ Link a variable list to the local one. This could be done event by event, but before evaluating expressions. <>= procedure :: link_var_list => subevt_expr_link_var_list <>= subroutine subevt_expr_link_var_list (expr, var_list) class(subevt_expr_t), intent(inout) :: expr type(var_list_t), intent(in), target :: var_list call expr%var_list%link (var_list) end subroutine subevt_expr_link_var_list @ %def subevt_expr_link_var_list @ Compile the selection expression. If there is no expression, the build method will not allocate the expression object. <>= procedure :: setup_selection => subevt_expr_setup_selection <>= subroutine subevt_expr_setup_selection (expr, ef_cuts) class(subevt_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_cuts call ef_cuts%build (expr%selection) if (allocated (expr%selection)) then call expr%setup_var_self () call expr%selection%setup_lexpr (expr%var_list) expr%has_selection = .true. end if end subroutine subevt_expr_setup_selection @ %def subevt_expr_setup_selection @ (De)activate color storage and evaluation for the expression. The subevent particles will have color information. <>= procedure :: colorize => subevt_expr_colorize <>= subroutine subevt_expr_colorize (expr, colorize_subevt) class(subevt_expr_t), intent(inout), target :: expr logical, intent(in) :: colorize_subevt expr%colorize_subevt = colorize_subevt end subroutine subevt_expr_colorize @ %def subevt_expr_colorize @ \subsection{Evaluation} Reset to initial state, i.e., mark the [[subevt]] as invalid. <>= procedure :: reset_contents => subevt_expr_reset_contents procedure :: base_reset_contents => subevt_expr_reset_contents <>= subroutine subevt_expr_reset_contents (expr) class(subevt_expr_t), intent(inout) :: expr expr%subevt_filled = .false. end subroutine subevt_expr_reset_contents @ %def subevt_expr_reset_contents @ Evaluate the selection expression and return the result. There is also a deferred version: this should evaluate the remaining expressions if the event has passed. <>= procedure :: base_evaluate => subevt_expr_evaluate <>= subroutine subevt_expr_evaluate (expr, passed) class(subevt_expr_t), intent(inout) :: expr logical, intent(out) :: passed if (expr%has_selection) then call expr%selection%evaluate () if (expr%selection%is_known ()) then passed = expr%selection%get_log () else call msg_error ("Evaluate selection expression: result undefined") passed = .false. end if else passed = .true. end if end subroutine subevt_expr_evaluate @ %def subevt_expr_evaluate @ \subsection{Implementation for partonic events} This implementation contains the expressions that we can evaluate for the partonic process during integration. <>= public :: parton_expr_t <>= type, extends (subevt_expr_t) :: parton_expr_t integer, dimension(:), allocatable :: i_beam integer, dimension(:), allocatable :: i_in integer, dimension(:), allocatable :: i_out logical :: has_scale = .false. logical :: has_fac_scale = .false. logical :: has_ren_scale = .false. logical :: has_weight = .false. class(expr_t), allocatable :: scale class(expr_t), allocatable :: fac_scale class(expr_t), allocatable :: ren_scale class(expr_t), allocatable :: weight contains <> end type parton_expr_t @ %def parton_expr_t @ Finalizer. <>= procedure :: final => parton_expr_final <>= subroutine parton_expr_final (object) class(parton_expr_t), intent(inout) :: object call object%base_final () if (object%has_scale) then call object%scale%final () end if if (object%has_fac_scale) then call object%fac_scale%final () end if if (object%has_ren_scale) then call object%ren_scale%final () end if if (object%has_weight) then call object%weight%final () end if end subroutine parton_expr_final @ %def parton_expr_final @ Output: continue writing the active expressions, after the common selection expression. Note: the [[prefix]] argument is declared in the [[write]] method of the [[subevt_t]] base type. Here, it is unused. <>= procedure :: write => parton_expr_write <>= subroutine parton_expr_write (object, unit, prefix, pacified) class(parton_expr_t), intent(in) :: object integer, intent(in), optional :: unit character(*), intent(in), optional :: prefix logical, intent(in), optional :: pacified integer :: u u = given_output_unit (unit) call object%base_write (u, pacified = pacified) if (object%subevt_filled) then if (object%has_scale) then call write_separator (u) write (u, "(1x,A)") "Scale expression:" call write_separator (u) call object%scale%write (u) end if if (object%has_fac_scale) then call write_separator (u) write (u, "(1x,A)") "Factorization scale expression:" call write_separator (u) call object%fac_scale%write (u) end if if (object%has_ren_scale) then call write_separator (u) write (u, "(1x,A)") "Renormalization scale expression:" call write_separator (u) call object%ren_scale%write (u) end if if (object%has_weight) then call write_separator (u) write (u, "(1x,A)") "Weight expression:" call write_separator (u) call object%weight%write (u) end if end if end subroutine parton_expr_write @ %def parton_expr_write @ Define variables. <>= procedure :: setup_vars => parton_expr_setup_vars <>= subroutine parton_expr_setup_vars (expr, sqrts) class(parton_expr_t), intent(inout), target :: expr real(default), intent(in) :: sqrts call expr%base_setup_vars (sqrts) end subroutine parton_expr_setup_vars @ %def parton_expr_setup_vars @ Compile the scale expressions. If a pointer is disassociated, there is no expression. <>= procedure :: setup_scale => parton_expr_setup_scale procedure :: setup_fac_scale => parton_expr_setup_fac_scale procedure :: setup_ren_scale => parton_expr_setup_ren_scale <>= subroutine parton_expr_setup_scale (expr, ef_scale) class(parton_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_scale call ef_scale%build (expr%scale) if (allocated (expr%scale)) then call expr%setup_var_self () call expr%scale%setup_expr (expr%var_list) expr%has_scale = .true. end if end subroutine parton_expr_setup_scale subroutine parton_expr_setup_fac_scale (expr, ef_fac_scale) class(parton_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_fac_scale call ef_fac_scale%build (expr%fac_scale) if (allocated (expr%fac_scale)) then call expr%setup_var_self () call expr%fac_scale%setup_expr (expr%var_list) expr%has_fac_scale = .true. end if end subroutine parton_expr_setup_fac_scale subroutine parton_expr_setup_ren_scale (expr, ef_ren_scale) class(parton_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_ren_scale call ef_ren_scale%build (expr%ren_scale) if (allocated (expr%ren_scale)) then call expr%setup_var_self () call expr%ren_scale%setup_expr (expr%var_list) expr%has_ren_scale = .true. end if end subroutine parton_expr_setup_ren_scale @ %def parton_expr_setup_scale @ %def parton_expr_setup_fac_scale @ %def parton_expr_setup_ren_scale @ Compile the weight expression. <>= procedure :: setup_weight => parton_expr_setup_weight <>= subroutine parton_expr_setup_weight (expr, ef_weight) class(parton_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_weight call ef_weight%build (expr%weight) if (allocated (expr%weight)) then call expr%setup_var_self () call expr%weight%setup_expr (expr%var_list) expr%has_weight = .true. end if end subroutine parton_expr_setup_weight @ %def parton_expr_setup_weight @ Filling the partonic state consists of two parts. The first routine prepares the subevt without assigning momenta. It takes the particles from an [[interaction_t]]. It needs the indices and flavors for the beam, incoming, and outgoing particles. We can assume that the particle content of the subevt does not change. Therefore, we set the event variables [[n_in]], [[n_out]], [[n_tot]] already in this initialization step. <>= procedure :: setup_subevt => parton_expr_setup_subevt <>= subroutine parton_expr_setup_subevt (expr, int, & i_beam, i_in, i_out, f_beam, f_in, f_out) class(parton_expr_t), intent(inout) :: expr type(interaction_t), intent(in), target :: int integer, dimension(:), intent(in) :: i_beam, i_in, i_out type(flavor_t), dimension(:), intent(in) :: f_beam, f_in, f_out allocate (expr%i_beam (size (i_beam))) allocate (expr%i_in (size (i_in))) allocate (expr%i_out (size (i_out))) expr%i_beam = i_beam expr%i_in = i_in expr%i_out = i_out call interaction_to_subevt (int, & expr%i_beam, expr%i_in, expr%i_out, expr%subevt_t) call subevt_set_pdg_beam (expr%subevt_t, f_beam%get_pdg ()) call subevt_set_pdg_incoming (expr%subevt_t, f_in%get_pdg ()) call subevt_set_pdg_outgoing (expr%subevt_t, f_out%get_pdg ()) call subevt_set_p2_beam (expr%subevt_t, f_beam%get_mass () ** 2) call subevt_set_p2_incoming (expr%subevt_t, f_in%get_mass () ** 2) call subevt_set_p2_outgoing (expr%subevt_t, f_out%get_mass () ** 2) expr%n_in = size (i_in) expr%n_out = size (i_out) expr%n_tot = expr%n_in + expr%n_out end subroutine parton_expr_setup_subevt @ %def parton_expr_setup_subevt @ Transfer PDG codes, masses (initalization) and momenta to a predefined subevent. We use the flavor assignment of the first branch in the interaction state matrix. Only incoming and outgoing particles are transferred. Switch momentum sign for incoming particles. <>= interface interaction_momenta_to_subevt module procedure interaction_momenta_to_subevt_id module procedure interaction_momenta_to_subevt_tr end interface <>= subroutine interaction_to_subevt (int, j_beam, j_in, j_out, subevt) type(interaction_t), intent(in), target :: int integer, dimension(:), intent(in) :: j_beam, j_in, j_out type(subevt_t), intent(out) :: subevt type(flavor_t), dimension(:), allocatable :: flv integer :: n_beam, n_in, n_out, i, j allocate (flv (int%get_n_tot ())) flv = quantum_numbers_get_flavor (int%get_quantum_numbers (1)) n_beam = size (j_beam) n_in = size (j_in) n_out = size (j_out) call subevt_init (subevt, n_beam + n_in + n_out) do i = 1, n_beam j = j_beam(i) call subevt_set_beam (subevt, i, & flv(j)%get_pdg (), & vector4_null, & flv(j)%get_mass () ** 2) end do do i = 1, n_in j = j_in(i) call subevt_set_incoming (subevt, n_beam + i, & flv(j)%get_pdg (), & vector4_null, & flv(j)%get_mass () ** 2) end do do i = 1, n_out j = j_out(i) call subevt_set_outgoing (subevt, n_beam + n_in + i, & flv(j)%get_pdg (), & vector4_null, & flv(j)%get_mass () ** 2) end do end subroutine interaction_to_subevt subroutine interaction_momenta_to_subevt_id (int, j_beam, j_in, j_out, subevt) type(interaction_t), intent(in) :: int integer, dimension(:), intent(in) :: j_beam, j_in, j_out type(subevt_t), intent(inout) :: subevt call subevt_set_p_beam (subevt, - int%get_momenta (j_beam)) call subevt_set_p_incoming (subevt, - int%get_momenta (j_in)) call subevt_set_p_outgoing (subevt, int%get_momenta (j_out)) end subroutine interaction_momenta_to_subevt_id subroutine interaction_momenta_to_subevt_tr & (int, j_beam, j_in, j_out, lt, subevt) type(interaction_t), intent(in) :: int integer, dimension(:), intent(in) :: j_beam, j_in, j_out type(subevt_t), intent(inout) :: subevt type(lorentz_transformation_t), intent(in) :: lt call subevt_set_p_beam & (subevt, - lt * int%get_momenta (j_beam)) call subevt_set_p_incoming & (subevt, - lt * int%get_momenta (j_in)) call subevt_set_p_outgoing & (subevt, lt * int%get_momenta (j_out)) end subroutine interaction_momenta_to_subevt_tr @ %def interaction_momenta_to_subevt @ The second part takes the momenta from the interaction object and thus completes the subevt. The partonic energy can then be computed. <>= procedure :: fill_subevt => parton_expr_fill_subevt <>= subroutine parton_expr_fill_subevt (expr, int) class(parton_expr_t), intent(inout) :: expr type(interaction_t), intent(in), target :: int call interaction_momenta_to_subevt (int, & expr%i_beam, expr%i_in, expr%i_out, expr%subevt_t) expr%sqrts_hat = subevt_get_sqrts_hat (expr%subevt_t) expr%subevt_filled = .true. end subroutine parton_expr_fill_subevt @ %def parton_expr_fill_subevt @ Evaluate, if the event passes the selection. For absent expressions we take default values. <>= procedure :: evaluate => parton_expr_evaluate <>= subroutine parton_expr_evaluate & (expr, passed, scale, fac_scale, ren_scale, weight, scale_forced, force_evaluation) class(parton_expr_t), intent(inout) :: expr logical, intent(out) :: passed real(default), intent(out) :: scale real(default), intent(out) :: fac_scale real(default), intent(out) :: ren_scale real(default), intent(out) :: weight real(default), intent(in), allocatable, optional :: scale_forced logical, intent(in), optional :: force_evaluation logical :: force_scale, force_eval force_scale = .false.; force_eval = .false. if (present (scale_forced)) force_scale = allocated (scale_forced) if (present (force_evaluation)) force_eval = force_evaluation call expr%base_evaluate (passed) if (passed .or. force_eval) then if (force_scale) then scale = scale_forced else if (expr%has_scale) then call expr%scale%evaluate () if (expr%scale%is_known ()) then scale = expr%scale%get_real () else call msg_error ("Evaluate scale expression: result undefined") scale = zero end if else scale = expr%sqrts_hat end if if (force_scale) then fac_scale = scale_forced else if (expr%has_fac_scale) then call expr%fac_scale%evaluate () if (expr%fac_scale%is_known ()) then fac_scale = expr%fac_scale%get_real () else call msg_error ("Evaluate factorization scale expression: & &result undefined") fac_scale = zero end if else fac_scale = scale end if if (force_scale) then ren_scale = scale_forced else if (expr%has_ren_scale) then call expr%ren_scale%evaluate () if (expr%ren_scale%is_known ()) then ren_scale = expr%ren_scale%get_real () else call msg_error ("Evaluate renormalization scale expression: & &result undefined") ren_scale = zero end if else ren_scale = scale end if if (expr%has_weight) then call expr%weight%evaluate () if (expr%weight%is_known ()) then weight = expr%weight%get_real () else call msg_error ("Evaluate weight expression: result undefined") weight = zero end if else weight = one end if else weight = zero end if end subroutine parton_expr_evaluate @ %def parton_expr_evaluate @ Return the beam/incoming parton indices. <>= procedure :: get_beam_index => parton_expr_get_beam_index procedure :: get_in_index => parton_expr_get_in_index <>= subroutine parton_expr_get_beam_index (expr, i_beam) class(parton_expr_t), intent(in) :: expr integer, dimension(:), intent(out) :: i_beam i_beam = expr%i_beam end subroutine parton_expr_get_beam_index subroutine parton_expr_get_in_index (expr, i_in) class(parton_expr_t), intent(in) :: expr integer, dimension(:), intent(out) :: i_in i_in = expr%i_in end subroutine parton_expr_get_in_index @ %def parton_expr_get_beam_index @ %def parton_expr_get_in_index @ \subsection{Implementation for full events} This implementation contains the expressions that we can evaluate for the full event. It also contains data that pertain to the event, suitable for communication with external event formats. These data simultaneously serve as pointer targets for the variable lists hidden in the expressions (eval trees). Squared matrix element and weight values: when reading events from file, the [[ref]] value is the number in the file, while the [[prc]] value is the number that we calculate from the momenta in the file, possibly with different parameters. When generating events the first time, or if we do not recalculate, the numbers should coincide. Furthermore, the array of [[alt]] values is copied from an array of alternative event records. These values should represent calculated values. <>= public :: event_expr_t <>= type, extends (subevt_expr_t) :: event_expr_t logical :: has_reweight = .false. logical :: has_analysis = .false. class(expr_t), allocatable :: reweight class(expr_t), allocatable :: analysis logical :: has_id = .false. type(string_t) :: id logical :: has_num_id = .false. integer :: num_id = 0 logical :: has_index = .false. integer :: index = 0 logical :: has_sqme_ref = .false. real(default) :: sqme_ref = 0 logical :: has_sqme_prc = .false. real(default) :: sqme_prc = 0 logical :: has_weight_ref = .false. real(default) :: weight_ref = 0 logical :: has_weight_prc = .false. real(default) :: weight_prc = 0 logical :: has_excess_prc = .false. real(default) :: excess_prc = 0 integer :: n_alt = 0 logical :: has_sqme_alt = .false. real(default), dimension(:), allocatable :: sqme_alt logical :: has_weight_alt = .false. real(default), dimension(:), allocatable :: weight_alt contains <> end type event_expr_t @ %def event_expr_t @ Finalizer for the expressions. <>= procedure :: final => event_expr_final <>= subroutine event_expr_final (object) class(event_expr_t), intent(inout) :: object call object%base_final () if (object%has_reweight) then call object%reweight%final () end if if (object%has_analysis) then call object%analysis%final () end if end subroutine event_expr_final @ %def event_expr_final @ Output: continue writing the active expressions, after the common selection expression. Note: the [[prefix]] argument is declared in the [[write]] method of the [[subevt_t]] base type. Here, it is unused. <>= procedure :: write => event_expr_write <>= subroutine event_expr_write (object, unit, prefix, pacified) class(event_expr_t), intent(in) :: object integer, intent(in), optional :: unit character(*), intent(in), optional :: prefix logical, intent(in), optional :: pacified integer :: u u = given_output_unit (unit) call object%base_write (u, pacified = pacified) if (object%subevt_filled) then if (object%has_reweight) then call write_separator (u) write (u, "(1x,A)") "Reweighting expression:" call write_separator (u) call object%reweight%write (u) end if if (object%has_analysis) then call write_separator (u) write (u, "(1x,A)") "Analysis expression:" call write_separator (u) call object%analysis%write (u) end if end if end subroutine event_expr_write @ %def event_expr_write @ Initializer. This is required only for the [[sqme_alt]] and [[weight_alt]] arrays. <>= procedure :: init => event_expr_init <>= subroutine event_expr_init (expr, n_alt) class(event_expr_t), intent(out) :: expr integer, intent(in), optional :: n_alt if (present (n_alt)) then expr%n_alt = n_alt allocate (expr%sqme_alt (n_alt), source = 0._default) allocate (expr%weight_alt (n_alt), source = 0._default) end if end subroutine event_expr_init @ %def event_expr_init @ Define variables. We have the variables of the base type plus specific variables for full events. There is the event index. <>= procedure :: setup_vars => event_expr_setup_vars <>= subroutine event_expr_setup_vars (expr, sqrts) class(event_expr_t), intent(inout), target :: expr real(default), intent(in) :: sqrts call expr%base_setup_vars (sqrts) call var_list_append_string_ptr (expr%var_list, & var_str ("$process_id"), expr%id, & is_known = expr%has_id, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_int_ptr (expr%var_list, & var_str ("process_num_id"), expr%num_id, & is_known = expr%has_num_id, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_real_ptr (expr%var_list, & var_str ("sqme"), expr%sqme_prc, & is_known = expr%has_sqme_prc, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_real_ptr (expr%var_list, & var_str ("sqme_ref"), expr%sqme_ref, & is_known = expr%has_sqme_ref, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_int_ptr (expr%var_list, & var_str ("event_index"), expr%index, & is_known = expr%has_index, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_real_ptr (expr%var_list, & var_str ("event_weight"), expr%weight_prc, & is_known = expr%has_weight_prc, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_real_ptr (expr%var_list, & var_str ("event_weight_ref"), expr%weight_ref, & is_known = expr%has_weight_ref, & locked = .true., verbose = .false., intrinsic = .true.) call var_list_append_real_ptr (expr%var_list, & var_str ("event_excess"), expr%excess_prc, & is_known = expr%has_excess_prc, & locked = .true., verbose = .false., intrinsic = .true.) end subroutine event_expr_setup_vars @ %def event_expr_setup_vars @ Compile the analysis expression. If the pointer is disassociated, there is no expression. <>= procedure :: setup_analysis => event_expr_setup_analysis <>= subroutine event_expr_setup_analysis (expr, ef_analysis) class(event_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_analysis call ef_analysis%build (expr%analysis) if (allocated (expr%analysis)) then call expr%setup_var_self () call expr%analysis%setup_lexpr (expr%var_list) expr%has_analysis = .true. end if end subroutine event_expr_setup_analysis @ %def event_expr_setup_analysis @ Compile the reweight expression. <>= procedure :: setup_reweight => event_expr_setup_reweight <>= subroutine event_expr_setup_reweight (expr, ef_reweight) class(event_expr_t), intent(inout), target :: expr class(expr_factory_t), intent(in) :: ef_reweight call ef_reweight%build (expr%reweight) if (allocated (expr%reweight)) then call expr%setup_var_self () call expr%reweight%setup_expr (expr%var_list) expr%has_reweight = .true. end if end subroutine event_expr_setup_reweight @ %def event_expr_setup_reweight @ Store the string or numeric process ID. This should be done during initialization. <>= procedure :: set_process_id => event_expr_set_process_id procedure :: set_process_num_id => event_expr_set_process_num_id <>= subroutine event_expr_set_process_id (expr, id) class(event_expr_t), intent(inout) :: expr type(string_t), intent(in) :: id expr%id = id expr%has_id = .true. end subroutine event_expr_set_process_id subroutine event_expr_set_process_num_id (expr, num_id) class(event_expr_t), intent(inout) :: expr integer, intent(in) :: num_id expr%num_id = num_id expr%has_num_id = .true. end subroutine event_expr_set_process_num_id @ %def event_expr_set_process_id @ %def event_expr_set_process_num_id @ Reset / set the data that pertain to a particular event. The event index is reset unless explicitly told to keep it. <>= procedure :: reset_contents => event_expr_reset_contents procedure :: set => event_expr_set <>= subroutine event_expr_reset_contents (expr) class(event_expr_t), intent(inout) :: expr call expr%base_reset_contents () expr%has_sqme_ref = .false. expr%has_sqme_prc = .false. expr%has_sqme_alt = .false. expr%has_weight_ref = .false. expr%has_weight_prc = .false. expr%has_weight_alt = .false. expr%has_excess_prc = .false. end subroutine event_expr_reset_contents subroutine event_expr_set (expr, & weight_ref, weight_prc, weight_alt, & excess_prc, & sqme_ref, sqme_prc, sqme_alt) class(event_expr_t), intent(inout) :: expr real(default), intent(in), optional :: weight_ref, weight_prc real(default), intent(in), optional :: excess_prc real(default), intent(in), optional :: sqme_ref, sqme_prc real(default), dimension(:), intent(in), optional :: sqme_alt, weight_alt if (present (sqme_ref)) then expr%has_sqme_ref = .true. expr%sqme_ref = sqme_ref end if if (present (sqme_prc)) then expr%has_sqme_prc = .true. expr%sqme_prc = sqme_prc end if if (present (sqme_alt)) then expr%has_sqme_alt = .true. expr%sqme_alt = sqme_alt end if if (present (weight_ref)) then expr%has_weight_ref = .true. expr%weight_ref = weight_ref end if if (present (weight_prc)) then expr%has_weight_prc = .true. expr%weight_prc = weight_prc end if if (present (weight_alt)) then expr%has_weight_alt = .true. expr%weight_alt = weight_alt end if if (present (excess_prc)) then expr%has_excess_prc = .true. expr%excess_prc = excess_prc end if end subroutine event_expr_set @ %def event_expr_reset_contents event_expr_set @ Access the subevent index. <>= procedure :: has_event_index => event_expr_has_event_index procedure :: get_event_index => event_expr_get_event_index <>= function event_expr_has_event_index (expr) result (flag) class(event_expr_t), intent(in) :: expr logical :: flag flag = expr%has_index end function event_expr_has_event_index function event_expr_get_event_index (expr) result (index) class(event_expr_t), intent(in) :: expr integer :: index if (expr%has_index) then index = expr%index else index = 0 end if end function event_expr_get_event_index @ %def event_expr_has_event_index @ %def event_expr_get_event_index @ Set/increment the subevent index. Initialize it if necessary. <>= procedure :: set_event_index => event_expr_set_event_index procedure :: reset_event_index => event_expr_reset_event_index procedure :: increment_event_index => event_expr_increment_event_index <>= subroutine event_expr_set_event_index (expr, index) class(event_expr_t), intent(inout) :: expr integer, intent(in) :: index expr%index = index expr%has_index = .true. end subroutine event_expr_set_event_index subroutine event_expr_reset_event_index (expr) class(event_expr_t), intent(inout) :: expr expr%has_index = .false. end subroutine event_expr_reset_event_index subroutine event_expr_increment_event_index (expr, offset) class(event_expr_t), intent(inout) :: expr integer, intent(in), optional :: offset if (expr%has_index) then expr%index = expr%index + 1 else if (present (offset)) then call expr%set_event_index (offset + 1) else call expr%set_event_index (1) end if end subroutine event_expr_increment_event_index @ %def event_expr_set_event_index @ %def event_expr_increment_event_index @ Fill the event expression: take the particle data and kinematics from a [[particle_set]] object. We allow the particle content to change for each event. Therefore, we set the event variables each time. Also increment the event index; initialize it if necessary. <>= procedure :: fill_subevt => event_expr_fill_subevt <>= subroutine event_expr_fill_subevt (expr, particle_set) class(event_expr_t), intent(inout) :: expr type(particle_set_t), intent(in) :: particle_set call particle_set%to_subevt (expr%subevt_t, expr%colorize_subevt) expr%sqrts_hat = subevt_get_sqrts_hat (expr%subevt_t) expr%n_in = subevt_get_n_in (expr%subevt_t) expr%n_out = subevt_get_n_out (expr%subevt_t) expr%n_tot = expr%n_in + expr%n_out expr%subevt_filled = .true. end subroutine event_expr_fill_subevt @ %def event_expr_fill_subevt @ Evaluate, if the event passes the selection. For absent expressions we take default values. <>= procedure :: evaluate => event_expr_evaluate <>= subroutine event_expr_evaluate (expr, passed, reweight, analysis_flag) class(event_expr_t), intent(inout) :: expr logical, intent(out) :: passed real(default), intent(out) :: reweight logical, intent(out) :: analysis_flag call expr%base_evaluate (passed) if (passed) then if (expr%has_reweight) then call expr%reweight%evaluate () if (expr%reweight%is_known ()) then reweight = expr%reweight%get_real () else call msg_error ("Evaluate reweight expression: & &result undefined") reweight = 0 end if else reweight = 1 end if if (expr%has_analysis) then call expr%analysis%evaluate () if (expr%analysis%is_known ()) then analysis_flag = expr%analysis%get_log () else call msg_error ("Evaluate analysis expression: & &result undefined") analysis_flag = .false. end if else analysis_flag = .true. end if end if end subroutine event_expr_evaluate @ %def event_expr_evaluate @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Parton states} A [[parton_state_t]] object contains the effective kinematics and dynamics of an elementary partonic interaction, with or without the beam/structure function state included. The type is abstract and has two distinct extensions. The [[isolated_state_t]] extension describes the isolated elementary interaction where the [[int_eff]] subobject contains the complex transition amplitude, exclusive in all quantum numbers. The particle content and kinematics describe the effective partonic state. The [[connected_state_t]] extension contains the partonic [[subevt]] and the expressions for cuts and scales which use it. In the isolated state, the effective partonic interaction may either be identical to the hard interaction, in which case it is just a pointer to the latter. Or it may involve a rearrangement of partons, in which case we allocate it explicitly and flag this by [[int_is_allocated]]. The [[trace]] evaluator contains the absolute square of the effective transition amplitude matrix, summed over final states. It is also summed over initial states, depending on the the beam setup allows. The result is used for integration. The [[matrix]] evaluator is the counterpart of [[trace]] which is kept exclusive in all observable quantum numbers. The [[flows]] evaluator is furthermore exclusive in colors, but neglecting all color interference. The [[matrix]] and [[flows]] evaluators are filled only for sampling points that become part of physical events. Note: It would be natural to make the evaluators allocatable. The extra [[has_XXX]] flags indicate whether evaluators are active, instead. This module contains no unit tests. The tests are covered by the [[processes]] module below. <<[[parton_states.f90]]>>= <> module parton_states <> <> use io_units use format_utils, only: write_separator use diagnostics use lorentz use subevents use variables use expr_base use model_data use flavors use helicities use colors use quantum_numbers use state_matrices use polarizations use interactions use evaluators use beams use sf_base use process_constants use prc_core use subevt_expr <> <> <> contains <> end module parton_states @ %def parton_states @ \subsection{Abstract base type} The common part are the evaluators, one for the trace (summed over all quantum numbers), one for the transition matrix (summed only over unobservable quantum numbers), and one for the flow distribution (transition matrix without interferences, exclusive in color flow). <>= type, abstract :: parton_state_t logical :: has_trace = .false. logical :: has_matrix = .false. logical :: has_flows = .false. type(evaluator_t) :: trace type(evaluator_t) :: matrix type(evaluator_t) :: flows contains <> end type parton_state_t @ %def parton_state_t @ The [[isolated_state_t]] extension contains the [[sf_chain_eff]] object and the (hard) effective interaction [[int_eff]], separately, both are implemented as a pointer. The evaluators (trace, matrix, flows) apply to the hard interaction only. If the effective interaction differs from the hard interaction, the pointer is allocated explicitly. Analogously for [[sf_chain_eff]]. <>= public :: isolated_state_t <>= type, extends (parton_state_t) :: isolated_state_t logical :: sf_chain_is_allocated = .false. type(sf_chain_instance_t), pointer :: sf_chain_eff => null () logical :: int_is_allocated = .false. type(interaction_t), pointer :: int_eff => null () contains <> end type isolated_state_t @ %def isolated_state_t @ The [[connected_state_t]] extension contains all data that enable the evaluation of observables for the effective connected state. The evaluators connect the (effective) structure-function chain and hard interaction that were kept separate in the [[isolated_state_t]]. The [[flows_sf]] evaluator is an extended copy of the structure-function The [[expr]] subobject consists of the [[subevt]], a simple event record, expressions for cuts etc.\ which refer to this record, and a [[var_list]] which contains event-specific variables, linked to the process variable list. Variables used within the expressions are looked up in [[var_list]]. <>= public :: connected_state_t <>= type, extends (parton_state_t) :: connected_state_t type(state_flv_content_t) :: state_flv logical :: has_flows_sf = .false. type(evaluator_t) :: flows_sf logical :: has_expr = .false. type(parton_expr_t) :: expr contains <> end type connected_state_t @ %def connected_state_t @ Output: each evaluator is written only when it is active. The [[sf_chain]] is only written if it is explicitly allocated. <>= procedure :: write => parton_state_write <>= subroutine parton_state_write (state, unit, testflag) class(parton_state_t), intent(in) :: state integer, intent(in), optional :: unit logical, intent(in), optional :: testflag integer :: u u = given_output_unit (unit) select type (state) class is (isolated_state_t) if (state%sf_chain_is_allocated) then call write_separator (u) call state%sf_chain_eff%write (u) end if if (state%int_is_allocated) then call write_separator (u) write (u, "(1x,A)") & "Effective interaction:" call write_separator (u) call state%int_eff%basic_write (u, testflag = testflag) end if class is (connected_state_t) if (state%has_flows_sf) then call write_separator (u) write (u, "(1x,A)") & "Evaluator (extension of the beam evaluator & &with color contractions):" call write_separator (u) call state%flows_sf%write (u, testflag = testflag) end if end select if (state%has_trace) then call write_separator (u) write (u, "(1x,A)") & "Evaluator (trace of the squared transition matrix):" call write_separator (u) call state%trace%write (u, testflag = testflag) end if if (state%has_matrix) then call write_separator (u) write (u, "(1x,A)") & "Evaluator (squared transition matrix):" call write_separator (u) call state%matrix%write (u, testflag = testflag) end if if (state%has_flows) then call write_separator (u) write (u, "(1x,A)") & "Evaluator (squared color-flow matrix):" call write_separator (u) call state%flows%write (u, testflag = testflag) end if select type (state) class is (connected_state_t) if (state%has_expr) then call write_separator (u) call state%expr%write (u) end if end select end subroutine parton_state_write @ %def parton_state_write @ Finalize interaction and evaluators, but only if allocated. <>= procedure :: final => parton_state_final <>= subroutine parton_state_final (state) class(parton_state_t), intent(inout) :: state if (state%has_flows) then call state%flows%final () state%has_flows = .false. end if if (state%has_matrix) then call state%matrix%final () state%has_matrix = .false. end if if (state%has_trace) then call state%trace%final () state%has_trace = .false. end if select type (state) class is (connected_state_t) if (state%has_flows_sf) then call state%flows_sf%final () state%has_flows_sf = .false. end if call state%expr%final () class is (isolated_state_t) if (state%int_is_allocated) then call state%int_eff%final () deallocate (state%int_eff) state%int_is_allocated = .false. end if if (state%sf_chain_is_allocated) then call state%sf_chain_eff%final () end if end select end subroutine parton_state_final @ %def parton_state_final @ \subsection{Common Initialization} Initialize the isolated parton state. In this version, the effective structure-function chain [[sf_chain_eff]] and the effective interaction [[int_eff]] both are trivial pointers to the seed structure-function chain and to the hard interaction, respectively. <>= procedure :: init => isolated_state_init <>= subroutine isolated_state_init (state, sf_chain, int) class(isolated_state_t), intent(out) :: state type(sf_chain_instance_t), intent(in), target :: sf_chain type(interaction_t), intent(in), target :: int state%sf_chain_eff => sf_chain state%int_eff => int end subroutine isolated_state_init @ %def isolated_state_init @ \subsection{Evaluator initialization: isolated state} Create an evaluator for the trace of the squared transition matrix. The trace goes over all outgoing quantum numbers. Whether we trace over incoming quantum numbers other than color, depends on the given [[qn_mask_in]]. There are two options: explicitly computing the color factor table ([[use_cf]] false; [[nc]] defined), or taking the color factor table from the hard matrix element data. <>= procedure :: setup_square_trace => isolated_state_setup_square_trace <>= subroutine isolated_state_setup_square_trace (state, core, & qn_mask_in, col, keep_fs_flavor) class(isolated_state_t), intent(inout), target :: state class(prc_core_t), intent(in) :: core type(quantum_numbers_mask_t), intent(in), dimension(:) :: qn_mask_in !!! Actually need allocatable attribute here for once because col might !!! enter the subroutine non-allocated. integer, intent(in), dimension(:), allocatable :: col logical, intent(in) :: keep_fs_flavor type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask associate (data => core%data) allocate (qn_mask (data%n_in + data%n_out)) qn_mask( : data%n_in) = & quantum_numbers_mask (.false., .true., .false.) & .or. qn_mask_in qn_mask(data%n_in + 1 : ) = & quantum_numbers_mask (.not. keep_fs_flavor, .true., .true.) if (core%use_color_factors) then call state%trace%init_square (state%int_eff, qn_mask, & col_flow_index = data%cf_index, & col_factor = data%color_factors, & col_index_hi = col, & nc = core%nc) else call state%trace%init_square (state%int_eff, qn_mask, nc = core%nc) end if end associate state%has_trace = .true. end subroutine isolated_state_setup_square_trace @ %def isolated_state_setup_square_trace @ Set up an identity-evaluator for the trace. This implies that [[me]] is considered to be a squared amplitude, as for example for BLHA matrix elements. <>= procedure :: setup_identity_trace => isolated_state_setup_identity_trace <>= subroutine isolated_state_setup_identity_trace (state, core, qn_mask_in, & keep_fs_flavors, keep_colors) class(isolated_state_t), intent(inout), target :: state class(prc_core_t), intent(in) :: core type(quantum_numbers_mask_t), intent(in), dimension(:) :: qn_mask_in logical, intent(in), optional :: keep_fs_flavors, keep_colors type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask logical :: fs_flv_flag, col_flag fs_flv_flag = .true.; col_flag = .true. if (present(keep_fs_flavors)) fs_flv_flag = .not. keep_fs_flavors if (present(keep_colors)) col_flag = .not. keep_colors associate (data => core%data) allocate (qn_mask (data%n_in + data%n_out)) qn_mask( : data%n_in) = & quantum_numbers_mask (.false., col_flag, .false.) .or. qn_mask_in qn_mask(data%n_in + 1 : ) = & quantum_numbers_mask (fs_flv_flag, col_flag, .true.) end associate call state%int_eff%set_mask (qn_mask) call state%trace%init_identity (state%int_eff) state%has_trace = .true. end subroutine isolated_state_setup_identity_trace @ %def isolated_state_setup_identity_trace @ Set up the evaluator for the transition matrix, exclusive in helicities where this is requested. For all unstable final-state particles we keep polarization according to the applicable decay options. If the process is a decay itself, this applies also to the initial state. For all polarized final-state particles, we keep polarization including off-diagonal entries. We drop helicity completely for unpolarized final-state particles. For the initial state, if the particle has not been handled yet, we apply the provided [[qn_mask_in]] which communicates the beam properties. <>= procedure :: setup_square_matrix => isolated_state_setup_square_matrix <>= subroutine isolated_state_setup_square_matrix & (state, core, model, qn_mask_in, col) class(isolated_state_t), intent(inout), target :: state class(prc_core_t), intent(in) :: core class(model_data_t), intent(in), target :: model type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask_in integer, dimension(:), intent(in) :: col type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask type(flavor_t), dimension(:), allocatable :: flv integer :: i logical :: helmask, helmask_hd associate (data => core%data) allocate (qn_mask (data%n_in + data%n_out)) allocate (flv (data%n_flv)) do i = 1, data%n_in + data%n_out call flv%init (data%flv_state(i,:), model) if ((data%n_in == 1 .or. i > data%n_in) & .and. any (.not. flv%is_stable ())) then helmask = all (flv%decays_isotropically ()) helmask_hd = all (flv%decays_diagonal ()) qn_mask(i) = quantum_numbers_mask (.false., .true., helmask, & mask_hd = helmask_hd) else if (i > data%n_in) then helmask = all (.not. flv%is_polarized ()) qn_mask(i) = quantum_numbers_mask (.false., .true., helmask) else qn_mask(i) = quantum_numbers_mask (.false., .true., .false.) & .or. qn_mask_in(i) end if end do if (core%use_color_factors) then call state%matrix%init_square (state%int_eff, qn_mask, & col_flow_index = data%cf_index, & col_factor = data%color_factors, & col_index_hi = col, & nc = core%nc) else call state%matrix%init_square (state%int_eff, & qn_mask, & nc = core%nc) end if end associate state%has_matrix = .true. end subroutine isolated_state_setup_square_matrix @ %def isolated_state_setup_square_matrix @ This procedure initializes the evaluator that computes the contributions to color flows, neglecting color interference. The incoming-particle mask can be used to sum over incoming flavor. Helicity handling: see above. <>= procedure :: setup_square_flows => isolated_state_setup_square_flows <>= subroutine isolated_state_setup_square_flows (state, core, model, qn_mask_in) class(isolated_state_t), intent(inout), target :: state class(prc_core_t), intent(in) :: core class(model_data_t), intent(in), target :: model type(quantum_numbers_mask_t), dimension(:), intent(in) :: qn_mask_in type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask type(flavor_t), dimension(:), allocatable :: flv integer :: i logical :: helmask, helmask_hd associate (data => core%data) allocate (qn_mask (data%n_in + data%n_out)) allocate (flv (data%n_flv)) do i = 1, data%n_in + data%n_out call flv%init (data%flv_state(i,:), model) if ((data%n_in == 1 .or. i > data%n_in) & .and. any (.not. flv%is_stable ())) then helmask = all (flv%decays_isotropically ()) helmask_hd = all (flv%decays_diagonal ()) qn_mask(i) = quantum_numbers_mask (.false., .false., helmask, & mask_hd = helmask_hd) else if (i > data%n_in) then helmask = all (.not. flv%is_polarized ()) qn_mask(i) = quantum_numbers_mask (.false., .false., helmask) else qn_mask(i) = quantum_numbers_mask (.false., .false., .false.) & .or. qn_mask_in(i) end if end do call state%flows%init_square (state%int_eff, qn_mask, & expand_color_flows = .true.) end associate state%has_flows = .true. end subroutine isolated_state_setup_square_flows @ %def isolated_state_setup_square_flows @ \subsection{Evaluator initialization: connected state} Set up a trace evaluator as a product of two evaluators (incoming state, effective interaction). In the result, all quantum numbers are summed over. If the optional [[int]] interaction is provided, use this for the first factor in the convolution. Otherwise, use the final interaction of the stored [[sf_chain]]. The [[resonant]] flag applies if we want to construct a decay chain. The resonance property can propagate to the final event output. If an extended structure function is required [[requires_extended_sf]], we have to not consider [[sub]] as a quantum number. <>= procedure :: setup_connected_trace => connected_state_setup_connected_trace <>= subroutine connected_state_setup_connected_trace & (state, isolated, int, resonant, undo_helicities, & keep_fs_flavors, requires_extended_sf) class(connected_state_t), intent(inout), target :: state type(isolated_state_t), intent(in), target :: isolated type(interaction_t), intent(in), optional, target :: int logical, intent(in), optional :: resonant logical, intent(in), optional :: undo_helicities logical, intent(in), optional :: keep_fs_flavors logical, intent(in), optional :: requires_extended_sf type(quantum_numbers_mask_t) :: mask type(interaction_t), pointer :: src_int, beam_int logical :: reduce, fs_flv_flag if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, & "connected_state_setup_connected_trace") reduce = .false.; fs_flv_flag = .true. if (present (undo_helicities)) reduce = undo_helicities if (present (keep_fs_flavors)) fs_flv_flag = .not. keep_fs_flavors mask = quantum_numbers_mask (fs_flv_flag, .true., .true.) if (present (int)) then src_int => int else src_int => isolated%sf_chain_eff%get_out_int_ptr () end if if (debug2_active (D_PROCESS_INTEGRATION)) then call src_int%basic_write () end if call state%trace%init_product (src_int, isolated%trace, & qn_mask_conn = mask, & qn_mask_rest = mask, & connections_are_resonant = resonant, & ignore_sub_for_qn = requires_extended_sf) if (reduce) then beam_int => isolated%sf_chain_eff%get_beam_int_ptr () call undo_qn_hel (beam_int, mask, beam_int%get_n_tot ()) call undo_qn_hel (src_int, mask, src_int%get_n_tot ()) call beam_int%set_matrix_element (cmplx (1, 0, default)) call src_int%set_matrix_element (cmplx (1, 0, default)) end if state%has_trace = .true. contains subroutine undo_qn_hel (int_in, mask, n_tot) type(interaction_t), intent(inout) :: int_in type(quantum_numbers_mask_t), intent(in) :: mask integer, intent(in) :: n_tot type(quantum_numbers_mask_t), dimension(n_tot) :: mask_in mask_in = mask call int_in%set_mask (mask_in) end subroutine undo_qn_hel end subroutine connected_state_setup_connected_trace @ %def connected_state_setup_connected_trace @ Set up a matrix evaluator as a product of two evaluators (incoming state, effective interation). In the intermediate state, color and helicity is summed over. In the final state, we keep the quantum numbers which are present in the original evaluators. <>= procedure :: setup_connected_matrix => connected_state_setup_connected_matrix <>= subroutine connected_state_setup_connected_matrix & (state, isolated, int, resonant, qn_filter_conn) class(connected_state_t), intent(inout), target :: state type(isolated_state_t), intent(in), target :: isolated type(interaction_t), intent(in), optional, target :: int logical, intent(in), optional :: resonant type(quantum_numbers_t), intent(in), optional :: qn_filter_conn type(quantum_numbers_mask_t) :: mask type(interaction_t), pointer :: src_int mask = quantum_numbers_mask (.false., .true., .true.) if (present (int)) then src_int => int else src_int => isolated%sf_chain_eff%get_out_int_ptr () end if call state%matrix%init_product & (src_int, isolated%matrix, mask, & qn_filter_conn = qn_filter_conn, & connections_are_resonant = resonant) state%has_matrix = .true. end subroutine connected_state_setup_connected_matrix @ %def connected_state_setup_connected_matrix @ Set up a matrix evaluator as a product of two evaluators (incoming state, effective interation). In the intermediate state, only helicity is summed over. In the final state, we keep the quantum numbers which are present in the original evaluators. If the optional [[int]] interaction is provided, use this for the first factor in the convolution. Otherwise, use the final interaction of the stored [[sf_chain]], after creating an intermediate interaction that includes a correlated color state. We assume that for a caller-provided [[int]], this is not necessary. + +For fixed-order NLO differential distribution, we are interested at +the partonic level, no parton showering takes place as this would +demand for a proper matching. So, the flows in the [[connected_state]] +are not needed, and the color part will be masked for the interaction +coming from the [[sf_chain]]. The squared matrix elements coming from +the OLP provider at the moment do not come with flows anyhow. This +needs to be revised once the matching to the shower is completed. <>= procedure :: setup_connected_flows => connected_state_setup_connected_flows <>= subroutine connected_state_setup_connected_flows & - (state, isolated, int, resonant, qn_filter_conn) + (state, isolated, int, resonant, qn_filter_conn, mask_color) class(connected_state_t), intent(inout), target :: state type(isolated_state_t), intent(in), target :: isolated type(interaction_t), intent(in), optional, target :: int - logical, intent(in), optional :: resonant + logical, intent(in), optional :: resonant, mask_color type(quantum_numbers_t), intent(in), optional :: qn_filter_conn type(quantum_numbers_mask_t) :: mask + type(quantum_numbers_mask_t), dimension(:), allocatable :: mask_sf type(interaction_t), pointer :: src_int + logical :: mask_c + mask_c = .false. + if (present (mask_color)) mask_c = mask_color mask = quantum_numbers_mask (.false., .false., .true.) if (present (int)) then src_int => int else src_int => isolated%sf_chain_eff%get_out_int_ptr () call state%flows_sf%init_color_contractions (src_int) state%has_flows_sf = .true. src_int => state%flows_sf%interaction_t + if (mask_c) then + allocate (mask_sf (src_int%get_n_tot ())) + mask_sf = quantum_numbers_mask (.false., .true., .false.) + call src_int%reduce_state_matrix (mask_sf, keep_order = .true.) + end if end if call state%flows%init_product (src_int, isolated%flows, mask, & qn_filter_conn = qn_filter_conn, & connections_are_resonant = resonant) state%has_flows = .true. end subroutine connected_state_setup_connected_flows @ %def connected_state_setup_connected_flows @ Determine and store the flavor content for the connected state. This queries the [[matrix]] evaluator component, which should hold the requested flavor information. <>= procedure :: setup_state_flv => connected_state_setup_state_flv <>= subroutine connected_state_setup_state_flv (state, n_out_hard) class(connected_state_t), intent(inout), target :: state integer, intent(in) :: n_out_hard call interaction_get_flv_content & (state%matrix%interaction_t, state%state_flv, n_out_hard) end subroutine connected_state_setup_state_flv @ %def connected_state_setup_state_flv @ Return the current flavor state object. <>= procedure :: get_state_flv => connected_state_get_state_flv <>= function connected_state_get_state_flv (state) result (state_flv) class(connected_state_t), intent(in) :: state type(state_flv_content_t) :: state_flv state_flv = state%state_flv end function connected_state_get_state_flv @ %def connected_state_get_state_flv @ \subsection{Cuts and expressions} Set up the [[subevt]] that corresponds to the connected interaction. The index arrays refer to the interaction. We assign the particles as follows: the beam particles are the first two (decay process: one) entries in the trace evaluator. The incoming partons are identified by their link to the outgoing partons of the structure-function chain. The outgoing partons are those of the trace evaluator, which include radiated partons during the structure-function chain. <>= procedure :: setup_subevt => connected_state_setup_subevt <>= subroutine connected_state_setup_subevt (state, sf_chain, f_beam, f_in, f_out) class(connected_state_t), intent(inout), target :: state type(sf_chain_instance_t), intent(in), target :: sf_chain type(flavor_t), dimension(:), intent(in) :: f_beam, f_in, f_out integer :: n_beam, n_in, n_out, n_vir, n_tot, i, j integer, dimension(:), allocatable :: i_beam, i_in, i_out integer :: sf_out_i type(interaction_t), pointer :: sf_int sf_int => sf_chain%get_out_int_ptr () n_beam = size (f_beam) n_in = size (f_in) n_out = size (f_out) n_vir = state%trace%get_n_vir () n_tot = state%trace%get_n_tot () allocate (i_beam (n_beam), i_in (n_in), i_out (n_out)) i_beam = [(i, i = 1, n_beam)] do j = 1, n_in sf_out_i = sf_chain%get_out_i (j) i_in(j) = interaction_find_link & (state%trace%interaction_t, sf_int, sf_out_i) end do i_out = [(i, i = n_vir + 1, n_tot)] call state%expr%setup_subevt (state%trace%interaction_t, & i_beam, i_in, i_out, f_beam, f_in, f_out) state%has_expr = .true. end subroutine connected_state_setup_subevt @ %def connected_state_setup_subevt @ Initialize the variable list specific for this state/term. We insert event variables ([[sqrts_hat]]) and link the process variable list. The variable list acquires pointers to subobjects of [[state]], which must therefore have a [[target]] attribute. <>= procedure :: setup_var_list => connected_state_setup_var_list <>= subroutine connected_state_setup_var_list (state, process_var_list, beam_data) class(connected_state_t), intent(inout), target :: state type(var_list_t), intent(in), target :: process_var_list type(beam_data_t), intent(in) :: beam_data call state%expr%setup_vars (beam_data%get_sqrts ()) call state%expr%link_var_list (process_var_list) end subroutine connected_state_setup_var_list @ %def connected_state_setup_var_list @ Allocate the cut expression etc. <>= procedure :: setup_cuts => connected_state_setup_cuts procedure :: setup_scale => connected_state_setup_scale procedure :: setup_fac_scale => connected_state_setup_fac_scale procedure :: setup_ren_scale => connected_state_setup_ren_scale procedure :: setup_weight => connected_state_setup_weight <>= subroutine connected_state_setup_cuts (state, ef_cuts) class(connected_state_t), intent(inout), target :: state class(expr_factory_t), intent(in) :: ef_cuts call state%expr%setup_selection (ef_cuts) end subroutine connected_state_setup_cuts subroutine connected_state_setup_scale (state, ef_scale) class(connected_state_t), intent(inout), target :: state class(expr_factory_t), intent(in) :: ef_scale call state%expr%setup_scale (ef_scale) end subroutine connected_state_setup_scale subroutine connected_state_setup_fac_scale (state, ef_fac_scale) class(connected_state_t), intent(inout), target :: state class(expr_factory_t), intent(in) :: ef_fac_scale call state%expr%setup_fac_scale (ef_fac_scale) end subroutine connected_state_setup_fac_scale subroutine connected_state_setup_ren_scale (state, ef_ren_scale) class(connected_state_t), intent(inout), target :: state class(expr_factory_t), intent(in) :: ef_ren_scale call state%expr%setup_ren_scale (ef_ren_scale) end subroutine connected_state_setup_ren_scale subroutine connected_state_setup_weight (state, ef_weight) class(connected_state_t), intent(inout), target :: state class(expr_factory_t), intent(in) :: ef_weight call state%expr%setup_weight (ef_weight) end subroutine connected_state_setup_weight @ %def connected_state_setup_expressions @ Reset the expression object: invalidate the subevt. <>= procedure :: reset_expressions => connected_state_reset_expressions <>= subroutine connected_state_reset_expressions (state) class(connected_state_t), intent(inout) :: state if (state%has_expr) call state%expr%reset_contents () end subroutine connected_state_reset_expressions @ %def connected_state_reset_expressions @ \subsection{Evaluation} Transfer momenta to the trace evaluator and fill the [[subevt]] with this effective kinematics, if applicable. Note: we may want to apply a boost for the [[subevt]]. <>= procedure :: receive_kinematics => parton_state_receive_kinematics <>= subroutine parton_state_receive_kinematics (state) class(parton_state_t), intent(inout), target :: state if (state%has_trace) then call state%trace%receive_momenta () select type (state) class is (connected_state_t) if (state%has_expr) then call state%expr%fill_subevt (state%trace%interaction_t) end if end select end if end subroutine parton_state_receive_kinematics @ %def parton_state_receive_kinematics @ Recover kinematics: We assume that the trace evaluator is filled with momenta. Send those momenta back to the sources, then fill the variables and subevent as above. The incoming momenta of the connected state are not connected to the isolated state but to the beam interaction. Therefore, the incoming momenta within the isolated state do not become defined, yet. Instead, we reconstruct the beam (and ISR) momentum configuration. <>= procedure :: send_kinematics => parton_state_send_kinematics <>= subroutine parton_state_send_kinematics (state) class(parton_state_t), intent(inout), target :: state if (state%has_trace) then call interaction_send_momenta (state%trace%interaction_t) select type (state) class is (connected_state_t) call state%expr%fill_subevt (state%trace%interaction_t) end select end if end subroutine parton_state_send_kinematics @ %def parton_state_send_kinematics @ Evaluate the expressions. The routine evaluates first the cut expression. If the event passes, it evaluates the other expressions. Where no expressions are defined, default values are inserted. <>= procedure :: evaluate_expressions => connected_state_evaluate_expressions <>= subroutine connected_state_evaluate_expressions (state, passed, & scale, fac_scale, ren_scale, weight, scale_forced, force_evaluation) class(connected_state_t), intent(inout) :: state logical, intent(out) :: passed real(default), intent(out) :: scale, fac_scale, ren_scale, weight real(default), intent(in), allocatable, optional :: scale_forced logical, intent(in), optional :: force_evaluation if (state%has_expr) then call state%expr%evaluate (passed, scale, fac_scale, ren_scale, weight, & scale_forced, force_evaluation) end if end subroutine connected_state_evaluate_expressions @ %def connected_state_evaluate_expressions @ Evaluate the structure-function chain, if it is allocated explicitly. The argument is the factorization scale. If the chain is merely a pointer, the chain should already be evaluated at this point. <>= procedure :: evaluate_sf_chain => isolated_state_evaluate_sf_chain <>= subroutine isolated_state_evaluate_sf_chain (state, fac_scale) class(isolated_state_t), intent(inout) :: state real(default), intent(in) :: fac_scale if (state%sf_chain_is_allocated) call state%sf_chain_eff%evaluate (fac_scale) end subroutine isolated_state_evaluate_sf_chain @ %def isolated_state_evaluate_sf_chain @ Evaluate the trace. <>= procedure :: evaluate_trace => parton_state_evaluate_trace <>= subroutine parton_state_evaluate_trace (state) class(parton_state_t), intent(inout) :: state if (state%has_trace) call state%trace%evaluate () end subroutine parton_state_evaluate_trace @ %def parton_state_evaluate_trace <>= procedure :: evaluate_matrix => parton_state_evaluate_matrix <>= subroutine parton_state_evaluate_matrix (state) class(parton_state_t), intent(inout) :: state if (state%has_matrix) call state%matrix%evaluate () end subroutine parton_state_evaluate_matrix @ %def parton_state_evaluate_matrix @ Evaluate the extra evaluators that we need for physical events. <>= procedure :: evaluate_event_data => parton_state_evaluate_event_data <>= subroutine parton_state_evaluate_event_data (state, only_momenta) class(parton_state_t), intent(inout) :: state logical, intent(in), optional :: only_momenta logical :: only_mom only_mom = .false.; if (present (only_momenta)) only_mom = only_momenta select type (state) type is (connected_state_t) if (state%has_flows_sf) then call state%flows_sf%receive_momenta () if (.not. only_mom) call state%flows_sf%evaluate () end if end select if (state%has_matrix) then call state%matrix%receive_momenta () if (.not. only_mom) call state%matrix%evaluate () end if if (state%has_flows) then call state%flows%receive_momenta () if (.not. only_mom) call state%flows%evaluate () end if end subroutine parton_state_evaluate_event_data @ %def parton_state_evaluate_event_data @ Normalize the helicity density matrix by its trace, i.e., factor out the trace and put it into an overall normalization factor. The trace and flow evaluators are unchanged. <>= procedure :: normalize_matrix_by_trace => & parton_state_normalize_matrix_by_trace <>= subroutine parton_state_normalize_matrix_by_trace (state) class(parton_state_t), intent(inout) :: state if (state%has_matrix) call state%matrix%normalize_by_trace () end subroutine parton_state_normalize_matrix_by_trace @ %def parton_state_normalize_matrix_by_trace @ \subsection{Accessing the state} Three functions return a pointer to the event-relevant interactions. <>= procedure :: get_trace_int_ptr => parton_state_get_trace_int_ptr procedure :: get_matrix_int_ptr => parton_state_get_matrix_int_ptr procedure :: get_flows_int_ptr => parton_state_get_flows_int_ptr <>= function parton_state_get_trace_int_ptr (state) result (ptr) class(parton_state_t), intent(in), target :: state type(interaction_t), pointer :: ptr if (state%has_trace) then ptr => state%trace%interaction_t else ptr => null () end if end function parton_state_get_trace_int_ptr function parton_state_get_matrix_int_ptr (state) result (ptr) class(parton_state_t), intent(in), target :: state type(interaction_t), pointer :: ptr if (state%has_matrix) then ptr => state%matrix%interaction_t else ptr => null () end if end function parton_state_get_matrix_int_ptr function parton_state_get_flows_int_ptr (state) result (ptr) class(parton_state_t), intent(in), target :: state type(interaction_t), pointer :: ptr if (state%has_flows) then ptr => state%flows%interaction_t else ptr => null () end if end function parton_state_get_flows_int_ptr @ %def parton_state_get_trace_int_ptr @ %def parton_state_get_matrix_int_ptr @ %def parton_state_get_flows_int_ptr @ Return the indices of the beam particles and the outgoing particles within the trace (and thus, matrix and flows) evaluator, respectively. <>= procedure :: get_beam_index => connected_state_get_beam_index procedure :: get_in_index => connected_state_get_in_index <>= subroutine connected_state_get_beam_index (state, i_beam) class(connected_state_t), intent(in) :: state integer, dimension(:), intent(out) :: i_beam call state%expr%get_beam_index (i_beam) end subroutine connected_state_get_beam_index subroutine connected_state_get_in_index (state, i_in) class(connected_state_t), intent(in) :: state integer, dimension(:), intent(out) :: i_in call state%expr%get_in_index (i_in) end subroutine connected_state_get_in_index @ %def connected_state_get_beam_index @ %def connected_state_get_in_index @ <>= public :: refill_evaluator <>= subroutine refill_evaluator (sqme, qn, flv_index, evaluator) complex(default), intent(in), dimension(:) :: sqme type(quantum_numbers_t), intent(in), dimension(:,:) :: qn integer, intent(in), dimension(:), optional :: flv_index type(evaluator_t), intent(inout) :: evaluator integer :: i, i_flv do i = 1, size (sqme) if (present (flv_index)) then i_flv = flv_index(i) else i_flv = i end if call evaluator%add_to_matrix_element (qn(:,i_flv), sqme(i), & match_only_flavor = .true.) end do end subroutine refill_evaluator @ %def refill_evaluator @ Return the number of outgoing (hard) particles for the state. <>= procedure :: get_n_out => parton_state_get_n_out <>= function parton_state_get_n_out (state) result (n) class(parton_state_t), intent(in), target :: state integer :: n n = state%trace%get_n_out () end function parton_state_get_n_out @ %def parton_state_get_n_out @ \subsection{Unit tests} <<[[parton_states_ut.f90]]>>= <> module parton_states_ut use unit_tests use parton_states_uti <> <> contains <> end module parton_states_ut @ %def parton_states_ut <<[[parton_states_uti.f90]]>>= <> module parton_states_uti <> <> use constants, only: zero use numeric_utils use flavors use colors use helicities use quantum_numbers use sf_base, only: sf_chain_instance_t use state_matrices, only: state_matrix_t use prc_template_me, only: prc_template_me_t use interactions, only: interaction_t use models, only: model_t, create_test_model use parton_states <> <> contains <> end module parton_states_uti @ %def parton_states_uti @ <>= public :: parton_states_test <>= subroutine parton_states_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine parton_states_test @ %def parton_states_test @ \subsubsection{Test a simple isolated state} <>= call test (parton_states_1, "parton_states_1", & "Create a 2 -> 2 isolated state and compute trace", & u, results) <>= public :: parton_states_1 <>= subroutine parton_states_1 (u) integer, intent(in) :: u type(state_matrix_t), allocatable :: state type(flavor_t), dimension(2) :: flv_in type(flavor_t), dimension(2) :: flv_out1, flv_out2 type(flavor_t), dimension(4) :: flv_tot type(helicity_t), dimension(4) :: hel type(color_t), dimension(4) :: col integer :: h1, h2, h3, h4 integer :: f integer :: i type(quantum_numbers_t), dimension(4) :: qn type(prc_template_me_t) :: core type(sf_chain_instance_t), target :: sf_chain type(interaction_t), target :: int type(isolated_state_t) :: isolated_state integer :: n_states = 0 integer, dimension(:), allocatable :: col_flow_index type(quantum_numbers_mask_t), dimension(2) :: qn_mask integer, dimension(8) :: i_allowed_states complex(default), dimension(8) :: me complex(default) :: me_check_tot, me_check_1, me_check_2, me2 logical :: tmp1, tmp2 type(model_t), pointer :: test_model => null () write (u, "(A)") "* Test output: parton_states_1" write (u, "(A)") "* Purpose: Test the standard parton states" write (u, "(A)") call flv_in%init ([11, -11]) call flv_out1%init ([1, -1]) call flv_out2%init ([2, -2]) write (u, "(A)") "* Using incoming flavors: " call flavor_write_array (flv_in, u) write (u, "(A)") "* Two outgoing flavor structures: " call flavor_write_array (flv_out1, u) call flavor_write_array (flv_out2, u) write (u, "(A)") "* Initialize state matrix" allocate (state) call state%init () write (u, "(A)") "* Fill state matrix" call col(3)%init ([1]) call col(4)%init ([-1]) do f = 1, 2 do h1 = -1, 1, 2 do h2 = -1, 1, 2 do h3 = -1, 1, 2 do h4 = -1, 1, 2 n_states = n_states + 1 call hel%init ([h1, h2, h3, h4], [h1, h2, h3, h4]) if (f == 1) then flv_tot = [flv_in, flv_out1] else flv_tot = [flv_in, flv_out2] end if call qn%init (flv_tot, col, hel) call state%add_state (qn) end do end do end do end do end do !!! Two flavors, one color flow, 2 x 2 x 2 x 2 helicity configurations !!! -> 32 states. write (u, "(A)") write (u, "(A,I2)") "* Generated number of states: ", n_states call state%freeze () !!! Indices of the helicity configurations which are non-zero i_allowed_states = [6, 7, 10, 11, 22, 23, 26, 27] me = [cmplx (-1.89448E-5_default, 9.94456E-7_default, default), & cmplx (-8.37887E-2_default, 4.30842E-3_default, default), & cmplx (-1.99997E-1_default, -1.01985E-2_default, default), & cmplx ( 1.79717E-5_default, 9.27038E-7_default, default), & cmplx (-1.74859E-5_default, 8.78819E-7_default, default), & cmplx ( 1.67577E-1_default, -8.61683E-3_default, default), & cmplx ( 2.41331E-1_default, 1.23306E-2_default, default), & cmplx (-3.59435E-5_default, -1.85407E-6_default, default)] me_check_tot = cmplx (zero, zero, default) me_check_1 = cmplx (zero, zero, default) me_check_2 = cmplx (zero, zero, default) do i = 1, 8 me2 = me(i) * conjg (me(i)) me_check_tot = me_check_tot + me2 if (i < 5) then me_check_1 = me_check_1 + me2 else me_check_2 = me_check_2 + me2 end if call state%set_matrix_element (i_allowed_states(i), me(i)) end do !!! Do not forget the color factor me_check_tot = 3._default * me_check_tot me_check_1 = 3._default * me_check_1 me_check_2 = 3._default * me_check_2 write (u, "(A)") write (u, "(A)") "* Setup interaction" call int%basic_init (2, 0, 2, set_relations = .true.) call int%set_state_matrix (state) core%data%n_in = 2; core%data%n_out = 2 core%data%n_flv = 2 allocate (core%data%flv_state (4, 2)) core%data%flv_state (1, :) = [11, 11] core%data%flv_state (2, :) = [-11, -11] core%data%flv_state (3, :) = [1, 2] core%data%flv_state (4, :) = [-1, -2] core%use_color_factors = .false. core%nc = 3 write (u, "(A)") "* Init isolated state" call isolated_state%init (sf_chain, int) !!! There is only one color flow. allocate (col_flow_index (n_states)); col_flow_index = 1 call qn_mask%init (.false., .false., .true., mask_cg = .false.) write (u, "(A)") "* Give a trace to the isolated state" call isolated_state%setup_square_trace (core, qn_mask, col_flow_index, .false.) call isolated_state%evaluate_trace () write (u, "(A)") write (u, "(A)", advance = "no") "* Squared matrix element correct: " write (u, "(L1)") nearly_equal (me_check_tot, & isolated_state%trace%get_matrix_element (1), rel_smallness = 0.00001_default) write (u, "(A)") "* Give a matrix to the isolated state" call create_test_model (var_str ("SM"), test_model) call isolated_state%setup_square_matrix (core, test_model, qn_mask, col_flow_index) call isolated_state%evaluate_matrix () write (u, "(A)") "* Sub-matrixelements correct: " tmp1 = nearly_equal (me_check_1, & isolated_state%matrix%get_matrix_element (1), rel_smallness = 0.00001_default) tmp2 = nearly_equal (me_check_2, & isolated_state%matrix%get_matrix_element (2), rel_smallness = 0.00001_default) write (u, "(A,L1,A,L1)") "* 1: ", tmp1, ", 2: ", tmp2 write (u, "(A)") "* Test output end: parton_states_1" end subroutine parton_states_1 @ %def parton_states_1 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process component management} This module contains tools for managing and combining process components and matrix-element code and values, acting at a level below the actual process definition. \subsection{Abstract base type} The types introduced here are abstract base types. <<[[pcm_base.f90]]>>= <> module pcm_base <> use io_units use diagnostics use format_utils, only: write_integer_array use format_utils, only: write_separator use physics_defs, only: BORN, NLO_REAL <> use os_interface, only: os_data_t use process_libraries, only: process_component_def_t use process_libraries, only: process_library_t use prc_core_def use prc_core use variables, only: var_list_t use mappings, only: mapping_defaults_t use phs_base, only: phs_config_t use phs_forests, only: phs_parameters_t use mci_base, only: mci_t use model_data, only: model_data_t use models, only: model_t use blha_config, only: blha_master_t use blha_olp_interfaces, only: blha_template_t use process_config use process_mci, only: process_mci_entry_t <> <> <> <> <> contains <> end module pcm_base @ %def pcm_base @ \subsection{Core management} This object holds information about the cores used by the components and allocates the corresponding manager instance. [[i_component]] is the index of the process component which this core belongs to. The pointer to the core definition is a convenient help in configuring the core itself. We allow for a [[blha_config]] configuration object that covers BLHA cores. The BLHA standard is suitable generic to warrant support outside of specific type extension (i.e., applies to LO and NLO if requested). The BLHA configuration is allocated only if the core requires it. <>= public :: core_entry_t <>= type :: core_entry_t integer :: i_component = 0 logical :: active = .false. class(prc_core_def_t), pointer :: core_def => null () type(blha_template_t), allocatable :: blha_config class(prc_core_t), allocatable :: core contains <> end type core_entry_t @ %def core_entry_t @ <>= procedure :: get_core_ptr => core_entry_get_core_ptr <>= function core_entry_get_core_ptr (core_entry) result (core) class(core_entry_t), intent(in), target :: core_entry class(prc_core_t), pointer :: core if (allocated (core_entry%core)) then core => core_entry%core else core => null () end if end function core_entry_get_core_ptr @ %def core_entry_get_core_ptr @ Configure the core object after allocation with correct type. The [[core_def]] object pointer and the index [[i_component]] of the associated process component are already there. <>= procedure :: configure => core_entry_configure <>= subroutine core_entry_configure (core_entry, lib, id) class(core_entry_t), intent(inout) :: core_entry type(process_library_t), intent(in), target :: lib type(string_t), intent(in) :: id call core_entry%core%init & (core_entry%core_def, lib, id, core_entry%i_component) end subroutine core_entry_configure @ %def core_entry_configure @ \subsection{Process component manager} This object may hold process and method-specific data, and it should allocate the corresponding manager instance. The number of components determines the [[component_selected]] array. [[i_phs_config]] is a lookup table that returns the PHS configuration index for a given component index. [[i_core]] is a lookup table that returns the core-entry index for a given component index. <>= public :: pcm_t <>= type, abstract :: pcm_t logical :: initialized = .false. logical :: has_pdfs = .false. integer :: n_components = 0 integer :: n_cores = 0 integer :: n_mci = 0 logical, dimension(:), allocatable :: component_selected logical, dimension(:), allocatable :: component_active integer, dimension(:), allocatable :: i_phs_config integer, dimension(:), allocatable :: i_core integer, dimension(:), allocatable :: i_mci type(blha_template_t) :: blha_defaults logical :: uses_blha = .false. type(os_data_t) :: os_data contains <> end type pcm_t @ %def pcm_t @ The factory method. We use the [[inout]] intent, so calling this again is an error. <>= procedure(pcm_allocate_instance), deferred :: allocate_instance <>= abstract interface subroutine pcm_allocate_instance (pcm, instance) import class(pcm_t), intent(in) :: pcm class(pcm_instance_t), intent(inout), allocatable :: instance end subroutine pcm_allocate_instance end interface @ %def pcm_allocate_instance @ <>= procedure(pcm_is_nlo), deferred :: is_nlo <>= abstract interface function pcm_is_nlo (pcm) result (is_nlo) import logical :: is_nlo class(pcm_t), intent(in) :: pcm end function pcm_is_nlo end interface @ %def pcm_is_nlo @ <>= procedure(pcm_final), deferred :: final <>= abstract interface subroutine pcm_final (pcm) import class(pcm_t), intent(inout) :: pcm end subroutine pcm_final end interface @ %def pcm_final @ \subsection{Initialization methods} The PCM has the duty to coordinate and configure the process-object components. Initialize the PCM configuration itself, using environment data. <>= procedure(pcm_init), deferred :: init <>= abstract interface subroutine pcm_init (pcm, env, meta) import class(pcm_t), intent(out) :: pcm type(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta end subroutine pcm_init end interface @ %def pcm_init @ Initialize the BLHA configuration block, the component-independent default settings. This is to be called by [[pcm_init]]. We use the provided variable list. This block is filled regardless of whether BLHA is actually used, because why not? We use a default value for the scheme (not set in unit tests). <>= procedure :: set_blha_defaults => pcm_set_blha_defaults <>= subroutine pcm_set_blha_defaults (pcm, polarized_beams, var_list) class(pcm_t), intent(inout) :: pcm type(var_list_t), intent(in) :: var_list logical, intent(in) :: polarized_beams logical :: muon_yukawa_off real(default) :: top_yukawa type(string_t) :: ew_scheme muon_yukawa_off = & var_list%get_lval (var_str ("?openloops_switch_off_muon_yukawa")) top_yukawa = & var_list%get_rval (var_str ("blha_top_yukawa")) ew_scheme = & var_list%get_sval (var_str ("$blha_ew_scheme")) if (ew_scheme == "") ew_scheme = "Gmu" call pcm%blha_defaults%init & (polarized_beams, muon_yukawa_off, top_yukawa, ew_scheme) end subroutine pcm_set_blha_defaults @ %def pcm_set_blha_defaults @ Read the method settings from the variable list and store them in the BLHA master. The details depend on the [[pcm]] concrete type. <>= procedure(pcm_set_blha_methods), deferred :: set_blha_methods <>= abstract interface subroutine pcm_set_blha_methods (pcm, blha_master, var_list) import class(pcm_t), intent(inout) :: pcm type(blha_master_t), intent(inout) :: blha_master type(var_list_t), intent(in) :: var_list end subroutine pcm_set_blha_methods end interface @ %def pcm_set_blha_methods @ Produce the LO and NLO flavor-state tables (as far as available), as appropriate for BLHA configuration. We may inspect either the PCM itself or the array of process cores. <>= procedure(pcm_get_blha_flv_states), deferred :: get_blha_flv_states <>= abstract interface subroutine pcm_get_blha_flv_states (pcm, core_entry, flv_born, flv_real) import class(pcm_t), intent(in) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry integer, dimension(:,:), allocatable, intent(out) :: flv_born integer, dimension(:,:), allocatable, intent(out) :: flv_real end subroutine pcm_get_blha_flv_states end interface @ %def pcm_get_blha_flv_states @ Allocate the right number of process components. The number is also stored in the process meta. Initially, all components are active but none are selected. <>= procedure :: allocate_components => pcm_allocate_components <>= subroutine pcm_allocate_components (pcm, comp, meta) class(pcm_t), intent(inout) :: pcm type(process_component_t), dimension(:), allocatable, intent(out) :: comp type(process_metadata_t), intent(in) :: meta pcm%n_components = meta%n_components allocate (comp (pcm%n_components)) allocate (pcm%component_selected (pcm%n_components), source = .false.) allocate (pcm%component_active (pcm%n_components), source = .true.) end subroutine pcm_allocate_components @ %def pcm_allocate_components @ Each process component belongs to a category/type, which we identify by a universal integer constant. The categories can be taken from the process definition. For easy lookup, we store the categories in an array. <>= procedure(pcm_categorize_components), deferred :: categorize_components <>= abstract interface subroutine pcm_categorize_components (pcm, config) import class(pcm_t), intent(inout) :: pcm type(process_config_data_t), intent(in) :: config end subroutine pcm_categorize_components end interface @ %def pcm_categorize_components @ Allocate the right number and type(s) of process-core objects, i.e., the interface object between the process and matrix-element code. Within the [[pcm]] block, also associate cores with components and store relevant configuration data, including the [[i_core]] lookup table. <>= procedure(pcm_allocate_cores), deferred :: allocate_cores <>= abstract interface subroutine pcm_allocate_cores (pcm, config, core_entry) import class(pcm_t), intent(inout) :: pcm type(process_config_data_t), intent(in) :: config type(core_entry_t), dimension(:), allocatable, intent(out) :: core_entry end subroutine pcm_allocate_cores end interface @ %def pcm_allocate_cores @ Generate and interface external code for a single core, if this is required. <>= procedure(pcm_prepare_any_external_code), deferred :: & prepare_any_external_code <>= abstract interface subroutine pcm_prepare_any_external_code & (pcm, core_entry, i_core, libname, model, var_list) import class(pcm_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry integer, intent(in) :: i_core type(string_t), intent(in) :: libname type(model_data_t), intent(in), target :: model type(var_list_t), intent(in) :: var_list end subroutine pcm_prepare_any_external_code end interface @ %def pcm_prepare_any_external_code @ Prepare the BLHA configuration for a core object that requires it. This does not affect the core object, which may not yet be allocated. <>= procedure(pcm_setup_blha), deferred :: setup_blha <>= abstract interface subroutine pcm_setup_blha (pcm, core_entry) import class(pcm_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry end subroutine pcm_setup_blha end interface @ %def pcm_setup_blha @ Configure the BLHA interface for a core object that requires it. This is separate from the previous method, assuming that the [[pcm]] has to allocate the actual cores and acquire some data in-between. <>= procedure(pcm_prepare_blha_core), deferred :: prepare_blha_core <>= abstract interface subroutine pcm_prepare_blha_core (pcm, core_entry, model) import class(pcm_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry class(model_data_t), intent(in), target :: model end subroutine pcm_prepare_blha_core end interface @ %def pcm_prepare_blha_core @ Allocate and configure the MCI (multi-channel integrator) records and their relation to process components, appropriate for the algorithm implemented by [[pcm]]. Create a [[mci_t]] template: the procedure [[dispatch_mci]] is called as a factory method for allocating the [[mci_t]] object with a specific concrete type. The call may depend on the concrete [[pcm]] type. <>= public :: dispatch_mci_proc <>= abstract interface subroutine dispatch_mci_proc (mci, var_list, process_id, is_nlo) import class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo end subroutine dispatch_mci_proc end interface @ %def dispatch_mci_proc <>= procedure(pcm_setup_mci), deferred :: setup_mci procedure(pcm_call_dispatch_mci), deferred :: call_dispatch_mci <>= abstract interface subroutine pcm_setup_mci (pcm, mci_entry) import class(pcm_t), intent(inout) :: pcm type(process_mci_entry_t), & dimension(:), allocatable, intent(out) :: mci_entry end subroutine pcm_setup_mci end interface abstract interface subroutine pcm_call_dispatch_mci (pcm, & dispatch_mci, var_list, process_id, mci_template) import class(pcm_t), intent(inout) :: pcm procedure(dispatch_mci_proc) :: dispatch_mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id class(mci_t), intent(out), allocatable :: mci_template end subroutine pcm_call_dispatch_mci end interface @ %def pcm_setup_mci @ %def pcm_call_dispatch_mci @ Proceed with PCM configuration based on the core and component configuration data. Base version is empty. <>= procedure(pcm_complete_setup), deferred :: complete_setup <>= abstract interface subroutine pcm_complete_setup (pcm, core_entry, component, model) import class(pcm_t), intent(inout) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry type(process_component_t), dimension(:), intent(inout) :: component type(model_t), intent(in), target :: model end subroutine pcm_complete_setup end interface @ %def pcm_complete_setup @ \subsubsection{Retrieve information} Return the core index that belongs to a particular component. <>= procedure :: get_i_core => pcm_get_i_core <>= function pcm_get_i_core (pcm, i_component) result (i_core) class(pcm_t), intent(in) :: pcm integer, intent(in) :: i_component integer :: i_core if (allocated (pcm%i_core)) then i_core = pcm%i_core(i_component) else i_core = 0 end if end function pcm_get_i_core @ %def pcm_get_i_core @ \subsubsection{Phase-space configuration} Allocate and initialize the right number and type(s) of phase-space configuration entries. The [[i_phs_config]] lookup table must be set accordingly. <>= procedure(pcm_init_phs_config), deferred :: init_phs_config <>= abstract interface subroutine pcm_init_phs_config & (pcm, phs_entry, meta, env, phs_par, mapping_defs) import class(pcm_t), intent(inout) :: pcm type(process_phs_config_t), & dimension(:), allocatable, intent(out) :: phs_entry type(process_metadata_t), intent(in) :: meta type(process_environment_t), intent(in) :: env type(mapping_defaults_t), intent(in) :: mapping_defs type(phs_parameters_t), intent(in) :: phs_par end subroutine pcm_init_phs_config end interface @ %def pcm_init_phs_config @ Initialize a single component. We require all process-configuration blocks, and specific templates for the phase-space and integrator configuration. We also provide the current component index [[i]] and the [[active]] flag. <>= procedure(pcm_init_component), deferred :: init_component <>= abstract interface subroutine pcm_init_component & (pcm, component, i, active, phs_config, env, meta, config) import class(pcm_t), intent(in) :: pcm type(process_component_t), intent(out) :: component integer, intent(in) :: i logical, intent(in) :: active class(phs_config_t), allocatable, intent(in) :: phs_config type(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta type(process_config_data_t), intent(in) :: config end subroutine pcm_init_component end interface @ %def pcm_init_component @ Record components in the process [[meta]] data if they have turned out to be inactive. <>= procedure :: record_inactive_components => pcm_record_inactive_components <>= subroutine pcm_record_inactive_components (pcm, component, meta) class(pcm_t), intent(inout) :: pcm type(process_component_t), dimension(:), intent(in) :: component type(process_metadata_t), intent(inout) :: meta integer :: i pcm%component_active = component%active do i = 1, pcm%n_components if (.not. component(i)%active) call meta%deactivate_component (i) end do end subroutine pcm_record_inactive_components @ %def pcm_record_inactive_components @ \subsection{Manager instance} This object deals with the actual (squared) matrix element values. <>= public :: pcm_instance_t <>= type, abstract :: pcm_instance_t class(pcm_t), pointer :: config => null () logical :: bad_point = .false. contains <> end type pcm_instance_t @ %def pcm_instance_t @ <>= procedure(pcm_instance_final), deferred :: final <>= abstract interface subroutine pcm_instance_final (pcm_instance) import class(pcm_instance_t), intent(inout) :: pcm_instance end subroutine pcm_instance_final end interface @ %def pcm_instance_final @ <>= procedure :: link_config => pcm_instance_link_config <>= subroutine pcm_instance_link_config (pcm_instance, config) class(pcm_instance_t), intent(inout) :: pcm_instance class(pcm_t), intent(in), target :: config pcm_instance%config => config end subroutine pcm_instance_link_config @ %def pcm_instance_link_config @ <>= procedure :: is_valid => pcm_instance_is_valid <>= function pcm_instance_is_valid (pcm_instance) result (valid) logical :: valid class(pcm_instance_t), intent(in) :: pcm_instance valid = .not. pcm_instance%bad_point end function pcm_instance_is_valid @ %def pcm_instance_is_valid @ <>= procedure :: set_bad_point => pcm_instance_set_bad_point <>= pure subroutine pcm_instance_set_bad_point (pcm_instance, bad_point) class(pcm_instance_t), intent(inout) :: pcm_instance logical, intent(in) :: bad_point pcm_instance%bad_point = pcm_instance%bad_point .or. bad_point end subroutine pcm_instance_set_bad_point @ %def pcm_instance_set_bad_point @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{The process object} <<[[process.f90]]>>= <> module process <> <> <> use io_units use format_utils, only: write_separator use constants use diagnostics use numeric_utils use lorentz use cputime use md5 use rng_base use dispatch_rng, only: dispatch_rng_factory use dispatch_rng, only: update_rng_seed_in_var_list use os_interface use sm_qcd use integration_results use mci_base use flavors use model_data use models use physics_defs use process_libraries use process_constants use particles use variables use beam_structures use beams use interactions use pdg_arrays use expr_base use sf_base use sf_mappings use resonances, only: resonance_history_t, resonance_history_set_t use prc_test_core, only: test_t use prc_core_def, only: prc_core_def_t use prc_core, only: prc_core_t, helicity_selection_t use prc_external, only: prc_external_t use prc_recola, only: prc_recola_t use blha_olp_interfaces, only: prc_blha_t, blha_template_t use prc_threshold, only: prc_threshold_t use phs_fks, only: phs_fks_config_t use phs_base use mappings, only: mapping_defaults_t use phs_forests, only: phs_parameters_t use phs_wood, only: phs_wood_config_t use dispatch_phase_space, only: dispatch_phs use blha_config, only: blha_master_t use nlo_data, only: FKS_DEFAULT, FKS_RESONANCES use parton_states, only: connected_state_t use pcm_base use pcm use process_counter use process_config use process_mci <> <> <> <> <> contains <> end module process @ %def process @ \subsection{Process status} Store counter and status information in a process object. <>= type :: process_status_t private end type process_status_t @ %def process_status_t @ \subsection{Process status} Store integration results in a process object. <>= type :: process_results_t private end type process_results_t @ %def process_results_t @ \subsection{The process type} A process object is the workspace for the process instance. After initialization, its contents are filled by integration passes which shape the integration grids and compute cross sections. Processes are set up initially from user-level configuration data. After calculating integrals and thus developing integration grid data, the program may use a process object or a copy of it for the purpose of generating events. The process object consists of several subobjects with their specific purposes. The corresponding types are defined below. (Technically, the subobject type definitions have to come before the process type definition, but with NOWEB magic we reverse this order here.) The [[type]] determines whether we are considering a decay or a scattering process. The [[meta]] object describes the process and its environment. All contents become fixed when the object is initialized. The [[config]] object holds physical and technical configuration data that have been obtained during process initialization, and which are common to all process components. The individual process components are configured in the [[component]] objects. These objects contain more configuration parameters and workspace, as needed for the specific process variant. The [[term]] objects describe parton configurations which are technically used as phase-space points. Each process component may split into several terms with distinct kinematics and particle content. Furthermore, each term may project on a different physical state, e.g., by particle recombination. The [[term]] object provides the framework for this projection, for applying cuts, weight, and thus completing the process calculation. The [[beam_config]] object describes the incoming particles, either the decay mother or the scattering beams. It also contains the structure-function information. The [[mci_entry]] objects configure a MC input parameter set and integrator, each. The number of parameters depends on the process component and on the beam and structure-function setup. The [[pcm]] component is the process-component manager. This polymorphic object manages and hides the details of dealing with NLO processes where several components have to be combined in a non-trivial way. It also acts as an abstract factory for the corresponding object in [[process_instance]], which does the actual work for this matter. <>= public :: process_t <>= type :: process_t private type(process_metadata_t) :: & meta type(process_environment_t) :: & env type(process_config_data_t) :: & config class(pcm_t), allocatable :: & pcm type(process_component_t), dimension(:), allocatable :: & component type(process_phs_config_t), dimension(:), allocatable :: & phs_entry type(core_entry_t), dimension(:), allocatable :: & core_entry type(process_mci_entry_t), dimension(:), allocatable :: & mci_entry class(rng_factory_t), allocatable :: & rng_factory type(process_beam_config_t) :: & beam_config type(process_term_t), dimension(:), allocatable :: & term type(process_status_t) :: & status type(process_results_t) :: & result contains <> end type process_t @ %def process_t @ \subsection{Process pointer} Wrapper type for storing pointers to process objects in arrays. <>= public :: process_ptr_t <>= type :: process_ptr_t type(process_t), pointer :: p => null () end type process_ptr_t @ %def process_ptr_t @ \subsection{Output} This procedure is an important debugging and inspection tool; it is not used during normal operation. The process object is written to a file (identified by unit, which may also be standard output). Optional flags determine whether we show everything or just the interesting parts. The shorthand as a traditional TBP. <>= procedure :: write => process_write <>= subroutine process_write (process, screen, unit, & show_os_data, show_var_list, show_rng, show_expressions, pacify) class(process_t), intent(in) :: process logical, intent(in) :: screen integer, intent(in), optional :: unit logical, intent(in), optional :: show_os_data logical, intent(in), optional :: show_var_list logical, intent(in), optional :: show_rng logical, intent(in), optional :: show_expressions logical, intent(in), optional :: pacify integer :: u, iostat character(0) :: iomsg integer, dimension(:), allocatable :: v_list u = given_output_unit (unit) allocate (v_list (0)) call set_flag (v_list, F_SHOW_OS_DATA, show_os_data) call set_flag (v_list, F_SHOW_VAR_LIST, show_var_list) call set_flag (v_list, F_SHOW_RNG, show_rng) call set_flag (v_list, F_SHOW_EXPRESSIONS, show_expressions) call set_flag (v_list, F_PACIFY, pacify) if (screen) then call process%write_formatted (u, "LISTDIRECTED", v_list, iostat, iomsg) else call process%write_formatted (u, "DT", v_list, iostat, iomsg) end if end subroutine process_write @ %def process_write @ Standard DTIO procedure with binding. For the particular application, the screen format is triggered by the [[LISTDIRECTED]] option for the [[iotype]] format editor string. The other options activate when the particular parameter value is found in [[v_list]]. NOTE: The DTIO [[generic]] binding is supported by gfortran since 7.0. TODO wk 2018: The default could be to show everything, and we should have separate switches for all major parts. Currently, there are only a few. <>= ! generic :: write (formatted) => write_formatted procedure :: write_formatted => process_write_formatted <>= subroutine process_write_formatted (dtv, unit, iotype, v_list, iostat, iomsg) class(process_t), intent(in) :: dtv integer, intent(in) :: unit character(*), intent(in) :: iotype integer, dimension(:), intent(in) :: v_list integer, intent(out) :: iostat character(*), intent(inout) :: iomsg integer :: u logical :: screen logical :: var_list logical :: rng_factory logical :: expressions logical :: counters logical :: os_data logical :: model logical :: pacify integer :: i u = unit select case (iotype) case ("LISTDIRECTED") screen = .true. case default screen = .false. end select var_list = flagged (v_list, F_SHOW_VAR_LIST) rng_factory = flagged (v_list, F_SHOW_RNG, .true.) expressions = flagged (v_list, F_SHOW_EXPRESSIONS) counters = .true. os_data = flagged (v_list, F_SHOW_OS_DATA) model = .false. pacify = flagged (v_list, F_PACIFY) associate (process => dtv) if (screen) then write (msg_buffer, "(A)") repeat ("-", 72) call msg_message () else call write_separator (u, 2) end if call process%meta%write (u, screen) if (var_list) then call process%env%write (u, show_var_list=var_list, & show_model=.false., show_lib=.false., & show_os_data=os_data) else if (.not. screen) then write (u, "(1x,A)") "Variable list: [not shown]" end if if (process%meta%type == PRC_UNKNOWN) then call write_separator (u, 2) return else if (screen) then return end if call write_separator (u) call process%config%write (u, counters, model, expressions) if (rng_factory) then if (allocated (process%rng_factory)) then call write_separator (u) call process%rng_factory%write (u) end if end if call write_separator (u, 2) if (allocated (process%component)) then write (u, "(1x,A)") "Process component configuration:" do i = 1, size (process%component) call write_separator (u) call process%component(i)%write (u) end do else write (u, "(1x,A)") "Process component configuration: [undefined]" end if call write_separator (u, 2) if (allocated (process%term)) then write (u, "(1x,A)") "Process term configuration:" do i = 1, size (process%term) call write_separator (u) call process%term(i)%write (u) end do else write (u, "(1x,A)") "Process term configuration: [undefined]" end if call write_separator (u, 2) call process%beam_config%write (u) call write_separator (u, 2) if (allocated (process%mci_entry)) then write (u, "(1x,A)") "Multi-channel integrator configurations:" do i = 1, size (process%mci_entry) call write_separator (u) write (u, "(1x,A,I0,A)") "MCI #", i, ":" call process%mci_entry(i)%write (u, pacify) end do end if call write_separator (u, 2) end associate iostat = 0 iomsg = "" end subroutine process_write_formatted @ %def process_write_formatted @ <>= procedure :: write_meta => process_write_meta <>= subroutine process_write_meta (process, unit, testflag) class(process_t), intent(in) :: process integer, intent(in), optional :: unit logical, intent(in), optional :: testflag integer :: u, i u = given_output_unit (unit) select case (process%meta%type) case (PRC_UNKNOWN) write (u, "(1x,A)") "Process instance [undefined]" return case (PRC_DECAY) write (u, "(1x,A)", advance="no") "Process instance [decay]:" case (PRC_SCATTERING) write (u, "(1x,A)", advance="no") "Process instance [scattering]:" case default call msg_bug ("process_instance_write: undefined process type") end select write (u, "(1x,A,A,A)") "'", char (process%meta%id), "'" write (u, "(3x,A,A,A)") "Run ID = '", char (process%meta%run_id), "'" if (allocated (process%meta%component_id)) then write (u, "(3x,A)") "Process components:" do i = 1, size (process%meta%component_id) if (process%pcm%component_selected(i)) then write (u, "(3x,'*')", advance="no") else write (u, "(4x)", advance="no") end if write (u, "(1x,I0,9A)") i, ": '", & char (process%meta%component_id (i)), "': ", & char (process%meta%component_description (i)) end do end if end subroutine process_write_meta @ %def process_write_meta @ Screen output. Write a short account of the process configuration and the current results. The verbose version lists the components, the short version just the results. <>= procedure :: show => process_show <>= subroutine process_show (object, unit, verbose) class(process_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: verbose integer :: u logical :: verb real(default) :: err_percent u = given_output_unit (unit) verb = .true.; if (present (verbose)) verb = verbose if (verb) then call object%meta%show (u, object%config%model%get_name ()) select case (object%meta%type) case (PRC_DECAY) write (u, "(2x,A)", advance="no") "Computed width =" case (PRC_SCATTERING) write (u, "(2x,A)", advance="no") "Computed cross section =" case default; return end select else if (object%meta%run_id /= "") then write (u, "('Run',1x,A,':',1x)", advance="no") & char (object%meta%run_id) end if write (u, "(A)", advance="no") char (object%meta%id) select case (object%meta%num_id) case (0) write (u, "(':')") case default write (u, "(1x,'(',I0,')',':')") object%meta%num_id end select write (u, "(2x)", advance="no") end if if (object%has_integral_tot ()) then write (u, "(ES14.7,1x,'+-',ES9.2)", advance="no") & object%get_integral_tot (), object%get_error_tot () select case (object%meta%type) case (PRC_DECAY) write (u, "(1x,A)", advance="no") "GeV" case (PRC_SCATTERING) write (u, "(1x,A)", advance="no") "fb " case default write (u, "(1x,A)", advance="no") " " end select if (object%get_integral_tot () /= 0) then err_percent = abs (100 & * object%get_error_tot () / object%get_integral_tot ()) else err_percent = 0 end if if (err_percent == 0) then write (u, "(1x,'(',F4.0,4x,'%)')") err_percent else if (err_percent < 0.1) then write (u, "(1x,'(',F7.3,1x,'%)')") err_percent else if (err_percent < 1) then write (u, "(1x,'(',F6.2,2x,'%)')") err_percent else if (err_percent < 10) then write (u, "(1x,'(',F5.1,3x,'%)')") err_percent else write (u, "(1x,'(',F4.0,4x,'%)')") err_percent end if else write (u, "(A)") "[integral undefined]" end if end subroutine process_show @ %def process_show @ Finalizer. Explicitly iterate over all subobjects that may contain allocated pointers. TODO wk 2018 (workaround): The finalizer for the [[config_data]] component is not called. The reason is that this deletes model data local to the process, but these could be referenced by pointers (flavor objects) from some persistent event record. Obviously, such side effects should be avoided, but this requires refactoring the event-handling procedures. <>= procedure :: final => process_final <>= subroutine process_final (process) class(process_t), intent(inout) :: process integer :: i ! call process%meta%final () call process%env%final () ! call process%config%final () if (allocated (process%component)) then do i = 1, size (process%component) call process%component(i)%final () end do end if if (allocated (process%term)) then do i = 1, size (process%term) call process%term(i)%final () end do end if call process%beam_config%final () if (allocated (process%mci_entry)) then do i = 1, size (process%mci_entry) call process%mci_entry(i)%final () end do end if if (allocated (process%pcm)) then call process%pcm%final () deallocate (process%pcm) end if end subroutine process_final @ %def process_final @ \subsubsection{Process setup} Initialize a process. We need a process library [[lib]] and the process identifier [[proc_id]] (string). We will fetch the current run ID from the variable list [[var_list]]. We collect all important data from the environment and store them in the appropriate places. OS data, model, and variable list are copied into [[env]] (true snapshot), also the process library (pointer only). The [[meta]] subobject is initialized with process ID and attributes taken from the process library. We initialize the [[config]] subobject with all data that are relevant for this run, using the settings from [[env]]. These data determine the MD5 sum for this run, which allows us to identify the setup and possibly skips in a later re-run. We also allocate and initialize the embedded RNG factory. We take the seed from the [[var_list]], and we should return the [[var_list]] to the caller with a new seed. Finally, we allocate the process component manager [[pcm]], which implements the chosen algorithm for process integration. The first task of the manager is to allocate the component array and to determine the component categories (e.g., Born/Virtual etc.). TODO wk 2018: The [[pcm]] dispatcher should be provided by the caller, if we eventually want to eliminate dependencies on concrete [[pcm_t]] extensions. <>= procedure :: init => process_init <>= subroutine process_init & (process, proc_id, lib, os_data, model, var_list, beam_structure) class(process_t), intent(out) :: process type(string_t), intent(in) :: proc_id type(process_library_t), intent(in), target :: lib type(os_data_t), intent(in) :: os_data class(model_t), intent(in), target :: model type(var_list_t), intent(inout), target, optional :: var_list type(beam_structure_t), intent(in), optional :: beam_structure integer :: next_rng_seed if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, "process_init") associate & (meta => process%meta, env => process%env, config => process%config) call env%init & (model, lib, os_data, var_list, beam_structure) call meta%init & (proc_id, lib, env%get_var_list_ptr ()) call config%init & (meta, env) call dispatch_rng_factory & (process%rng_factory, env%get_var_list_ptr (), next_rng_seed) call update_rng_seed_in_var_list (var_list, next_rng_seed) call dispatch_pcm & (process%pcm, config%process_def%is_nlo ()) associate (pcm => process%pcm) call pcm%init (env, meta) call pcm%allocate_components (process%component, meta) call pcm%categorize_components (config) end associate end associate end subroutine process_init @ %def process_init @ \subsection{Process component manager} The [[pcm]] (read: process-component manager) takes the responsibility of steering the actual algorithm of configuration and integration. Depending on the concrete type, different algorithms can be implemented. The first version of this supports just two implementations: leading-order (tree-level) integration and event generation, and NLO (QCD/FKS subtraction). We thus can start with a single logical for steering the dispatcher. TODO wk 2018: Eventually, we may eliminate all references to the extensions of [[pcm_t]] from this module and therefore move this outside the module as well. <>= subroutine dispatch_pcm (pcm, is_nlo) class(pcm_t), allocatable, intent(out) :: pcm logical, intent(in) :: is_nlo if (.not. is_nlo) then allocate (pcm_default_t :: pcm) else allocate (pcm_nlo_t :: pcm) end if end subroutine dispatch_pcm @ %def dispatch_pcm @ This step is performed after phase-space and core objects are done: collect all missing information and prepare the process component manager for the appropriate integration algorithm. <>= procedure :: complete_pcm_setup => process_complete_pcm_setup <>= subroutine process_complete_pcm_setup (process) class(process_t), intent(inout) :: process call process%pcm%complete_setup & (process%core_entry, process%component, process%env%get_model_ptr ()) end subroutine process_complete_pcm_setup @ %def process_complete_pcm_setup @ \subsection{Core management} Allocate cores (interface objects to matrix-element code). The [[dispatch_core]] procedure is taken as an argument, so we do not depend on the implementation, and thus on the specific core types. The [[helicity_selection]] object collects data that the matrix-element code needs for configuring the appropriate behavior. After the cores have been allocated, and assuming the phs initial configuration has been done before, we proceed with computing the [[pcm]] internal data. <>= procedure :: setup_cores => process_setup_cores <>= subroutine process_setup_cores (process, dispatch_core, & helicity_selection, use_color_factors, has_beam_pol) class(process_t), intent(inout) :: process procedure(dispatch_core_proc) :: dispatch_core type(helicity_selection_t), intent(in), optional :: helicity_selection logical, intent(in), optional :: use_color_factors logical, intent(in), optional :: has_beam_pol integer :: i associate (pcm => process%pcm) call pcm%allocate_cores (process%config, process%core_entry) do i = 1, size (process%core_entry) call dispatch_core (process%core_entry(i)%core, & process%core_entry(i)%core_def, & process%config%model, & helicity_selection, & process%config%qcd, & use_color_factors, & has_beam_pol) call process%core_entry(i)%configure & (process%env%get_lib_ptr (), process%meta%id) if (process%core_entry(i)%core%uses_blha ()) then call pcm%setup_blha (process%core_entry(i)) end if end do end associate end subroutine process_setup_cores @ %def process_setup_cores <>= abstract interface subroutine dispatch_core_proc (core, core_def, model, & helicity_selection, qcd, use_color_factors, has_beam_pol) import class(prc_core_t), allocatable, intent(inout) :: core class(prc_core_def_t), intent(in) :: core_def class(model_data_t), intent(in), target, optional :: model type(helicity_selection_t), intent(in), optional :: helicity_selection type(qcd_t), intent(in), optional :: qcd logical, intent(in), optional :: use_color_factors logical, intent(in), optional :: has_beam_pol end subroutine dispatch_core_proc end interface @ %def dispatch_core_proc @ Use the [[pcm]] to initialize the BLHA interface for each core which requires it. <>= procedure :: prepare_blha_cores => process_prepare_blha_cores <>= subroutine process_prepare_blha_cores (process) class(process_t), intent(inout), target :: process integer :: i associate (pcm => process%pcm) do i = 1, size (process%core_entry) associate (core_entry => process%core_entry(i)) if (core_entry%core%uses_blha ()) then pcm%uses_blha = .true. call pcm%prepare_blha_core (core_entry, process%config%model) end if end associate end do end associate end subroutine process_prepare_blha_cores @ %def process_prepare_blha_cores @ Create the BLHA interface data, using PCM for specific data, and write the BLHA contract file(s). We take various configuration data and copy them to the [[blha_master]] record, which then creates and writes the contracts. For assigning the QCD/EW coupling powers, we inspect the first process component only. The other parameters are taken as-is from the process environment variables. <>= procedure :: create_blha_interface => process_create_blha_interface <>= subroutine process_create_blha_interface (process) class(process_t), intent(inout) :: process integer :: alpha_power, alphas_power integer :: openloops_phs_tolerance, openloops_stability_log logical :: use_cms type(string_t) :: ew_scheme, correction_type type(string_t) :: openloops_extra_cmd type(blha_master_t) :: blha_master integer, dimension(:,:), allocatable :: flv_born, flv_real if (process%pcm%uses_blha) then call collect_configuration_parameters (process%get_var_list_ptr ()) call process%component(1)%config%get_coupling_powers & (alpha_power, alphas_power) associate (pcm => process%pcm) call pcm%set_blha_methods (blha_master, process%get_var_list_ptr ()) call blha_master%set_ew_scheme (ew_scheme) call blha_master%allocate_config_files () call blha_master%set_correction_type (correction_type) call blha_master%setup_additional_features ( & openloops_phs_tolerance, & use_cms, & openloops_stability_log, & extra_cmd = openloops_extra_cmd, & beam_structure = process%env%get_beam_structure ()) call pcm%get_blha_flv_states (process%core_entry, flv_born, flv_real) call blha_master%set_photon_characteristics (flv_born, process%config%n_in) call blha_master%generate (process%meta%id, & process%config%model, process%config%n_in, & alpha_power, alphas_power, & flv_born, flv_real) call blha_master%write_olp (process%meta%id) end associate end if contains subroutine collect_configuration_parameters (var_list) type(var_list_t), intent(in) :: var_list openloops_phs_tolerance = & var_list%get_ival (var_str ("openloops_phs_tolerance")) openloops_stability_log = & var_list%get_ival (var_str ("openloops_stability_log")) use_cms = & var_list%get_lval (var_str ("?openloops_use_cms")) ew_scheme = & var_list%get_sval (var_str ("$blha_ew_scheme")) correction_type = & var_list%get_sval (var_str ("$nlo_correction_type")) openloops_extra_cmd = & var_list%get_sval (var_str ("$openloops_extra_cmd")) end subroutine collect_configuration_parameters end subroutine process_create_blha_interface @ %def process_create_blha_interface @ Initialize the process components, one by one. We require templates for the [[mci]] (integrator) and [[phs_config]] (phase-space) configuration data. The [[active]] flag is set if the component has an associated matrix element, so we can compute it. The case of no core is a unit-test case. The specifics depend on the algorithm and are delegated to the [[pcm]] process-component manager. The optional [[phs_config]] overrides a pre-generated config array (for unit test). <>= procedure :: init_components => process_init_components <>= subroutine process_init_components (process, phs_config) class(process_t), intent(inout), target :: process class(phs_config_t), allocatable, intent(in), optional :: phs_config integer :: i, i_core class(prc_core_t), pointer :: core logical :: active associate (pcm => process%pcm) do i = 1, pcm%n_components i_core = pcm%get_i_core(i) if (i_core > 0) then core => process%get_core_ptr (i_core) active = core%has_matrix_element () else active = .true. end if select type (pcm => process%pcm) type is (pcm_nlo_t) if (pcm%use_real_partition .and. .not. pcm%use_real_singular) then if (pcm%component_type(i) == COMP_REAL_SING) then active = .false. end if end if end select if (present (phs_config)) then call pcm%init_component (process%component(i), & i, & active, & phs_config, & process%env, process%meta, process%config) else call pcm%init_component (process%component(i), & i, & active, & process%phs_entry(pcm%i_phs_config(i))%phs_config, & process%env, process%meta, process%config) end if end do end associate end subroutine process_init_components @ %def process_init_components @ If process components have turned out to be inactive, this has to be recorded in the [[meta]] block. Delegate to the [[pcm]]. <>= procedure :: record_inactive_components => process_record_inactive_components <>= subroutine process_record_inactive_components (process) class(process_t), intent(inout) :: process associate (pcm => process%pcm) call pcm%record_inactive_components (process%component, process%meta) end associate end subroutine process_record_inactive_components @ %def process_record_inactive_components @ Determine the process terms for each process component. <>= procedure :: setup_terms => process_setup_terms <>= subroutine process_setup_terms (process, with_beams) class(process_t), intent(inout), target :: process logical, intent(in), optional :: with_beams class(model_data_t), pointer :: model integer :: i, j, k, i_term integer, dimension(:), allocatable :: n_entry integer :: n_components, n_tot integer :: i_sub type(string_t) :: subtraction_method class(prc_core_t), pointer :: core => null () logical :: setup_subtraction_component, singular_real logical :: requires_spin_correlations integer :: nlo_type_to_fetch, n_emitters i_sub = 0 model => process%config%model n_components = process%meta%n_components allocate (n_entry (n_components), source = 0) do i = 1, n_components associate (component => process%component(i)) if (component%active) then n_entry(i) = 1 if (component%get_nlo_type () == NLO_REAL) then select type (pcm => process%pcm) type is (pcm_nlo_t) if (component%component_type /= COMP_REAL_FIN) & n_entry(i) = n_entry(i) + pcm%region_data%get_n_phs () end select end if end if end associate end do n_tot = sum (n_entry) allocate (process%term (n_tot)) k = 0 if (process%is_nlo_calculation ()) then i_sub = process%component(1)%config%get_associated_subtraction () subtraction_method = process%component(i_sub)%config%get_me_method () if (debug_on) call msg_debug2 & (D_PROCESS_INTEGRATION, "process_setup_terms: ", subtraction_method) end if do i = 1, n_components associate (component => process%component(i)) if (.not. component%active) cycle allocate (component%i_term (n_entry(i))) do j = 1, n_entry(i) singular_real = component%get_nlo_type () == NLO_REAL & .and. component%component_type /= COMP_REAL_FIN setup_subtraction_component = singular_real .and. j == n_entry(i) i_term = k + j component%i_term(j) = i_term if (singular_real) then process%term(i_term)%i_sub = k + n_entry(i) else process%term(i_term)%i_sub = 0 end if if (setup_subtraction_component) then select type (pcm => process%pcm) class is (pcm_nlo_t) process%term(i_term)%i_core = pcm%i_core(pcm%i_sub) end select else process%term(i_term)%i_core = process%pcm%get_i_core(i) end if if (process%term(i_term)%i_core == 0) then call msg_bug ("Process '" // char (process%get_id ()) & // "': core not found!") end if core => process%get_core_term (i_term) if (i_sub > 0) then select type (pcm => process%pcm) type is (pcm_nlo_t) requires_spin_correlations = & pcm%region_data%requires_spin_correlations () n_emitters = pcm%region_data%get_n_emitters_sc () class default requires_spin_correlations = .false. n_emitters = 0 end select if (requires_spin_correlations) then call process%term(i_term)%init ( & i_term, i, j, core, model, & nlo_type = component%config%get_nlo_type (), & use_beam_pol = with_beams, & subtraction_method = subtraction_method, & has_pdfs = process%pcm%has_pdfs, & n_emitters = n_emitters) else call process%term(i_term)%init ( & i_term, i, j, core, model, & nlo_type = component%config%get_nlo_type (), & use_beam_pol = with_beams, & subtraction_method = subtraction_method, & has_pdfs = process%pcm%has_pdfs) end if else call process%term(i_term)%init ( & i_term, i, j, core, model, & nlo_type = component%config%get_nlo_type (), & use_beam_pol = with_beams, & has_pdfs = process%pcm%has_pdfs) end if end do end associate k = k + n_entry(i) end do process%config%n_terms = n_tot end subroutine process_setup_terms @ %def process_setup_terms @ Initialize the beam setup. This is the trivial version where the incoming state of the matrix element coincides with the initial state of the process. For a scattering process, we need the c.m. energy, all other variables are set to their default values (no polarization, lab frame and c.m.\ frame coincide, etc.) We assume that all components consistently describe a scattering process, i.e., two incoming particles. Note: The current layout of the [[beam_data_t]] record requires that the flavor for each beam is unique. For processes with multiple flavors in the initial state, one has to set up beams explicitly. This restriction could be removed by extending the code in the [[beams]] module. <>= procedure :: setup_beams_sqrts => process_setup_beams_sqrts <>= subroutine process_setup_beams_sqrts (process, sqrts, beam_structure, i_core) class(process_t), intent(inout) :: process real(default), intent(in) :: sqrts type(beam_structure_t), intent(in), optional :: beam_structure integer, intent(in), optional :: i_core type(pdg_array_t), dimension(:,:), allocatable :: pdg_in integer, dimension(2) :: pdg_scattering type(flavor_t), dimension(2) :: flv_in integer :: i, i0, ic allocate (pdg_in (2, process%meta%n_components)) i0 = 0 do i = 1, process%meta%n_components if (process%component(i)%active) then if (present (i_core)) then ic = i_core else ic = process%pcm%get_i_core (i) end if associate (core => process%core_entry(ic)%core) pdg_in(:,i) = core%data%get_pdg_in () end associate if (i0 == 0) i0 = i end if end do do i = 1, process%meta%n_components if (.not. process%component(i)%active) then pdg_in(:,i) = pdg_in(:,i0) end if end do if (all (pdg_array_get_length (pdg_in) == 1) .and. & all (pdg_in(1,:) == pdg_in(1,i0)) .and. & all (pdg_in(2,:) == pdg_in(2,i0))) then pdg_scattering = pdg_array_get (pdg_in(:,i0), 1) call flv_in%init (pdg_scattering, process%config%model) call process%beam_config%init_scattering (flv_in, sqrts, beam_structure) else call msg_fatal ("Setting up process '" // char (process%meta%id) // "':", & [var_str (" --------------------------------------------"), & var_str ("Inconsistent initial state. This happens if either "), & var_str ("several processes with non-matching initial states "), & var_str ("have been added, or for a single process with an "), & var_str ("initial state flavor sum. In that case, please set beams "), & var_str ("explicitly [singling out a flavor / structure function.]")]) end if end subroutine process_setup_beams_sqrts @ %def process_setup_beams_sqrts @ This is the version that applies to decay processes. The energy is the particle mass, hence no extra argument. <>= procedure :: setup_beams_decay => process_setup_beams_decay <>= subroutine process_setup_beams_decay (process, rest_frame, beam_structure, i_core) class(process_t), intent(inout), target :: process logical, intent(in), optional :: rest_frame type(beam_structure_t), intent(in), optional :: beam_structure integer, intent(in), optional :: i_core type(pdg_array_t), dimension(:,:), allocatable :: pdg_in integer, dimension(1) :: pdg_decay type(flavor_t), dimension(1) :: flv_in integer :: i, i0, ic allocate (pdg_in (1, process%meta%n_components)) i0 = 0 do i = 1, process%meta%n_components if (process%component(i)%active) then if (present (i_core)) then ic = i_core else ic = process%pcm%get_i_core (i) end if associate (core => process%core_entry(ic)%core) pdg_in(:,i) = core%data%get_pdg_in () end associate if (i0 == 0) i0 = i end if end do do i = 1, process%meta%n_components if (.not. process%component(i)%active) then pdg_in(:,i) = pdg_in(:,i0) end if end do if (all (pdg_array_get_length (pdg_in) == 1) & .and. all (pdg_in(1,:) == pdg_in(1,i0))) then pdg_decay = pdg_array_get (pdg_in(:,i0), 1) call flv_in%init (pdg_decay, process%config%model) call process%beam_config%init_decay (flv_in, rest_frame, beam_structure) else call msg_fatal ("Setting up decay '" & // char (process%meta%id) // "': decaying particle not unique") end if end subroutine process_setup_beams_decay @ %def process_setup_beams_decay @ We have to make sure that the masses of the various flavors in a given position in the particle string coincide. <>= procedure :: check_masses => process_check_masses <>= subroutine process_check_masses (process) class(process_t), intent(in) :: process type(flavor_t), dimension(:), allocatable :: flv real(default), dimension(:), allocatable :: mass integer :: i, j integer :: i_component class(prc_core_t), pointer :: core do i = 1, process%get_n_terms () i_component = process%term(i)%i_component if (.not. process%component(i_component)%active) cycle core => process%get_core_term (i) associate (data => core%data) allocate (flv (data%n_flv), mass (data%n_flv)) do j = 1, data%n_in + data%n_out call flv%init (data%flv_state(j,:), process%config%model) mass = flv%get_mass () if (any (.not. nearly_equal(mass, mass(1)))) then call msg_fatal ("Process '" // char (process%meta%id) // "': " & // "mass values in flavor combination do not coincide. ") end if end do deallocate (flv, mass) end associate end do end subroutine process_check_masses @ %def process_check_masses @ Set up index mapping for [[region_data]] for singular regions equivalent w.r.t. their amplitudes. Has to be called after [[region_data]] AND the [[core]] are fully set up. For processes with structure function, subprocesses which lead to the same amplitude for the hard interaction can differ if structure functions are applied. In this case we remap flavor structures to themselves if the eqvivalent hard interaction flavor structure has no identical initial state. <>= procedure :: optimize_nlo_singular_regions => process_optimize_nlo_singular_regions <>= subroutine process_optimize_nlo_singular_regions (process) class(process_t), intent(inout) :: process class(prc_core_t), pointer :: core, core_sub integer, dimension(:), allocatable :: eqv_flv_index_born integer, dimension(:), allocatable :: eqv_flv_index_real integer, dimension(:,:), allocatable :: flv_born, flv_real integer :: i_flv, i_flv2, n_in, i integer :: i_component, i_core, i_core_sub logical :: fetched_born, fetched_real logical :: optimize fetched_born = .false.; fetched_real = .false. select type (pcm => process%pcm) type is (pcm_nlo_t) optimize = pcm%settings%reuse_amplitudes_fks if (optimize) then do i_component = 1, pcm%n_components i_core = pcm%get_i_core(i_component) core => process%get_core_ptr (i_core) if (.not. core%data_known) cycle associate (data => core%data) if (pcm%nlo_type_core(i_core) == NLO_REAL .and. & .not. pcm%component_type(i_component) == COMP_SUB) then if (allocated (core%data%eqv_flv_index)) then eqv_flv_index_real = core%get_equivalent_flv_index () fetched_real = .true. end if i_core_sub = pcm%get_i_core (pcm%i_sub) core_sub => process%get_core_ptr (i_core_sub) if (allocated (core_sub%data%eqv_flv_index)) then eqv_flv_index_born = core_sub%get_equivalent_flv_index () fetched_born = .true. end if if (fetched_born .and. fetched_real) exit end if end associate end do if (.not. fetched_born .or. .not. fetched_real) then call msg_warning('Failed to fetch flavor equivalence indices. & &Disabling singular region optimization') optimize = .false. eqv_flv_index_born = [(i, i = 1, pcm%region_data%n_flv_born)] eqv_flv_index_real = [(i, i = 1, pcm%region_data%n_flv_real)] end if if (optimize .and. pcm%has_pdfs) then flv_born = pcm%region_data%get_flv_states_born () flv_real = pcm%region_data%get_flv_states_real () n_in = pcm%region_data%n_in do i_flv = 1, size (eqv_flv_index_born) do i_flv2 = 1, i_flv if (any (flv_born(1:n_in, eqv_flv_index_born(i_flv)) /= & flv_born(1:n_in, i_flv))) then eqv_flv_index_born(i_flv) = i_flv exit end if end do end do do i_flv = 1, size (eqv_flv_index_real) do i_flv2 = 1, i_flv if (any (flv_real(1:n_in, eqv_flv_index_real(i_flv)) /= & flv_real(1:n_in, i_flv))) then eqv_flv_index_real(i_flv) = i_flv exit end if end do end do end if else eqv_flv_index_born = [(i, i = 1, pcm%region_data%n_flv_born)] eqv_flv_index_real = [(i, i = 1, pcm%region_data%n_flv_real)] end if pcm%region_data%eqv_flv_index_born = eqv_flv_index_born pcm%region_data%eqv_flv_index_real = eqv_flv_index_real call pcm%region_data%find_eqv_regions (optimize) end select end subroutine process_optimize_nlo_singular_regions @ %def process_optimize_nlo_singular_regions @ For some structure functions we need to get the list of initial state flavors. This is a two-dimensional array. The first index is the beam index, the second index is the component index. Each array element is itself a PDG array object, which consists of the list of incoming PDG values for this beam and component. <>= procedure :: get_pdg_in => process_get_pdg_in <>= subroutine process_get_pdg_in (process, pdg_in) class(process_t), intent(in), target :: process type(pdg_array_t), dimension(:,:), allocatable, intent(out) :: pdg_in integer :: i, i_core allocate (pdg_in (process%config%n_in, process%meta%n_components)) do i = 1, process%meta%n_components if (process%component(i)%active) then i_core = process%pcm%get_i_core (i) associate (core => process%core_entry(i_core)%core) pdg_in(:,i) = core%data%get_pdg_in () end associate end if end do end subroutine process_get_pdg_in @ %def process_get_pdg_in @ The phase-space configuration object, in case we need it separately. <>= procedure :: get_phs_config => process_get_phs_config <>= function process_get_phs_config (process, i_component) result (phs_config) class(phs_config_t), pointer :: phs_config class(process_t), intent(in), target :: process integer, intent(in) :: i_component if (allocated (process%component)) then phs_config => process%component(i_component)%phs_config else phs_config => null () end if end function process_get_phs_config @ %def process_get_phs_config @ The resonance history set can be extracted from the phase-space configuration. However, this is only possible if the default phase-space method (wood) has been chosen. If [[include_trivial]] is set, we include the resonance history with no resonances in the set. <>= procedure :: extract_resonance_history_set & => process_extract_resonance_history_set <>= subroutine process_extract_resonance_history_set & (process, res_set, include_trivial, i_component) class(process_t), intent(in), target :: process type(resonance_history_set_t), intent(out) :: res_set logical, intent(in), optional :: include_trivial integer, intent(in), optional :: i_component integer :: i i = 1; if (present (i_component)) i = i_component select type (phs_config => process%get_phs_config (i)) class is (phs_wood_config_t) call phs_config%extract_resonance_history_set (res_set, include_trivial) class default call msg_error ("process '" // char (process%get_id ()) & // "': extract resonance histories: phase-space method must be & &'wood'. No resonances can be determined.") end select end subroutine process_extract_resonance_history_set @ %def process_extract_resonance_history_set @ Initialize from a complete beam setup. If the beam setup does not apply directly to the process, choose a fallback option as a straight scattering or decay process. <>= procedure :: setup_beams_beam_structure => process_setup_beams_beam_structure <>= subroutine process_setup_beams_beam_structure & (process, beam_structure, sqrts, decay_rest_frame) class(process_t), intent(inout) :: process type(beam_structure_t), intent(in) :: beam_structure real(default), intent(in) :: sqrts logical, intent(in), optional :: decay_rest_frame integer :: n_in logical :: applies n_in = process%get_n_in () call beam_structure%check_against_n_in (process%get_n_in (), applies) if (applies) then call process%beam_config%init_beam_structure & (beam_structure, sqrts, process%get_model_ptr (), decay_rest_frame) else if (n_in == 2) then call process%setup_beams_sqrts (sqrts, beam_structure) else call process%setup_beams_decay (decay_rest_frame, beam_structure) end if end subroutine process_setup_beams_beam_structure @ %def process_setup_beams_beam_structure @ Notify the user about beam setup. <>= procedure :: beams_startup_message => process_beams_startup_message <>= subroutine process_beams_startup_message (process, unit, beam_structure) class(process_t), intent(in) :: process integer, intent(in), optional :: unit type(beam_structure_t), intent(in), optional :: beam_structure call process%beam_config%startup_message (unit, beam_structure) end subroutine process_beams_startup_message @ %def process_beams_startup_message @ Initialize phase-space configuration by reading out the environment variables. We return the rebuild flags and store parameters in the blocks [[phs_par]] and [[mapping_defs]]. The phase-space configuration object(s) are allocated by [[pcm]]. <>= procedure :: init_phs_config => process_init_phs_config <>= subroutine process_init_phs_config (process) class(process_t), intent(inout) :: process type(var_list_t), pointer :: var_list type(phs_parameters_t) :: phs_par type(mapping_defaults_t) :: mapping_defs var_list => process%env%get_var_list_ptr () phs_par%m_threshold_s = & var_list%get_rval (var_str ("phs_threshold_s")) phs_par%m_threshold_t = & var_list%get_rval (var_str ("phs_threshold_t")) phs_par%off_shell = & var_list%get_ival (var_str ("phs_off_shell")) phs_par%keep_nonresonant = & var_list%get_lval (var_str ("?phs_keep_nonresonant")) phs_par%t_channel = & var_list%get_ival (var_str ("phs_t_channel")) mapping_defs%energy_scale = & var_list%get_rval (var_str ("phs_e_scale")) mapping_defs%invariant_mass_scale = & var_list%get_rval (var_str ("phs_m_scale")) mapping_defs%momentum_transfer_scale = & var_list%get_rval (var_str ("phs_q_scale")) mapping_defs%step_mapping = & var_list%get_lval (var_str ("?phs_step_mapping")) mapping_defs%step_mapping_exp = & var_list%get_lval (var_str ("?phs_step_mapping_exp")) mapping_defs%enable_s_mapping = & var_list%get_lval (var_str ("?phs_s_mapping")) associate (pcm => process%pcm) call pcm%init_phs_config (process%phs_entry, & process%meta, process%env, phs_par, mapping_defs) end associate end subroutine process_init_phs_config @ %def process_init_phs_config @ We complete the kinematics configuration after the beam setup, but before we configure the chain of structure functions. The reason is that we need the total energy [[sqrts]] for the kinematics, but the structure-function setup requires the number of channels, which depends on the kinematics configuration. For instance, the kinematics module may return the need for parameterizing an s-channel resonance. <>= procedure :: configure_phs => process_configure_phs <>= subroutine process_configure_phs (process, rebuild, ignore_mismatch, & combined_integration, subdir) class(process_t), intent(inout) :: process logical, intent(in), optional :: rebuild logical, intent(in), optional :: ignore_mismatch logical, intent(in), optional :: combined_integration type(string_t), intent(in), optional :: subdir real(default) :: sqrts integer :: i, i_born, nlo_type class(phs_config_t), pointer :: phs_config_born sqrts = process%get_sqrts () do i = 1, process%meta%n_components associate (component => process%component(i)) if (component%active) then select type (pcm => process%pcm) type is (pcm_default_t) call component%configure_phs (sqrts, process%beam_config, & rebuild, ignore_mismatch, subdir) class is (pcm_nlo_t) nlo_type = component%config%get_nlo_type () select case (nlo_type) case (BORN, NLO_VIRTUAL, NLO_SUBTRACTION) call component%configure_phs (sqrts, process%beam_config, & rebuild, ignore_mismatch, subdir) call check_and_extend_phs (component) case (NLO_REAL, NLO_MISMATCH, NLO_DGLAP) i_born = component%config%get_associated_born () if (component%component_type /= COMP_REAL_FIN) & call check_and_extend_phs (component) call process%component(i_born)%get_phs_config & (phs_config_born) select type (config => component%phs_config) type is (phs_fks_config_t) select type (phs_config_born) type is (phs_wood_config_t) config%md5sum_born_config = & phs_config_born%md5sum_phs_config call config%set_born_config (phs_config_born) call config%set_mode (component%config%get_nlo_type ()) end select end select call component%configure_phs (sqrts, & process%beam_config, rebuild, ignore_mismatch, subdir) end select class default call msg_bug ("process_configure_phs: unsupported PCM type") end select end if end associate end do contains subroutine check_and_extend_phs (component) type(process_component_t), intent(inout) :: component if (combined_integration) then select type (phs_config => component%phs_config) class is (phs_wood_config_t) phs_config%is_combined_integration = .true. call phs_config%increase_n_par () end select end if end subroutine check_and_extend_phs end subroutine process_configure_phs @ %def process_configure_phs @ <>= procedure :: print_phs_startup_message => process_print_phs_startup_message <>= subroutine process_print_phs_startup_message (process) class(process_t), intent(in) :: process integer :: i_component do i_component = 1, process%meta%n_components associate (component => process%component(i_component)) if (component%active) then call component%phs_config%startup_message () end if end associate end do end subroutine process_print_phs_startup_message @ %def process_print_phs_startup_message @ Insert the structure-function configuration data. First allocate the storage, then insert data one by one. The third procedure declares a mapping (of the MC input parameters) for a specific channel and structure-function combination. We take the number of channels from the corresponding entry in the [[config_data]] section. Otherwise, these a simple wrapper routines. The extra level in the call tree may allow for simple addressing of multiple concurrent beam configurations, not implemented currently. If we do not want structure functions, we simply do not call those procedures. <>= procedure :: init_sf_chain => process_init_sf_chain generic :: set_sf_channel => set_sf_channel_single procedure :: set_sf_channel_single => process_set_sf_channel generic :: set_sf_channel => set_sf_channel_array procedure :: set_sf_channel_array => process_set_sf_channel_array <>= subroutine process_init_sf_chain (process, sf_config, sf_trace_file) class(process_t), intent(inout) :: process type(sf_config_t), dimension(:), intent(in) :: sf_config type(string_t), intent(in), optional :: sf_trace_file type(string_t) :: file if (present (sf_trace_file)) then if (sf_trace_file /= "") then file = sf_trace_file else file = process%get_id () // "_sftrace.dat" end if call process%beam_config%init_sf_chain (sf_config, file) else call process%beam_config%init_sf_chain (sf_config) end if end subroutine process_init_sf_chain subroutine process_set_sf_channel (process, c, sf_channel) class(process_t), intent(inout) :: process integer, intent(in) :: c type(sf_channel_t), intent(in) :: sf_channel call process%beam_config%set_sf_channel (c, sf_channel) end subroutine process_set_sf_channel subroutine process_set_sf_channel_array (process, sf_channel) class(process_t), intent(inout) :: process type(sf_channel_t), dimension(:), intent(in) :: sf_channel integer :: c call process%beam_config%allocate_sf_channels (size (sf_channel)) do c = 1, size (sf_channel) call process%beam_config%set_sf_channel (c, sf_channel(c)) end do end subroutine process_set_sf_channel_array @ %def process_init_sf_chain @ %def process_set_sf_channel @ Notify about the structure-function setup. <>= procedure :: sf_startup_message => process_sf_startup_message <>= subroutine process_sf_startup_message (process, sf_string, unit) class(process_t), intent(in) :: process type(string_t), intent(in) :: sf_string integer, intent(in), optional :: unit call process%beam_config%sf_startup_message (sf_string, unit) end subroutine process_sf_startup_message @ %def process_sf_startup_message @ As soon as both the kinematics configuration and the structure-function setup are complete, we match parameterizations (channels) for both. The matching entries are (re)set in the [[component]] phase-space configuration, while the structure-function configuration is left intact. <>= procedure :: collect_channels => process_collect_channels <>= subroutine process_collect_channels (process, coll) class(process_t), intent(inout) :: process type(phs_channel_collection_t), intent(inout) :: coll integer :: i do i = 1, process%meta%n_components associate (component => process%component(i)) if (component%active) & call component%collect_channels (coll) end associate end do end subroutine process_collect_channels @ %def process_collect_channels @ Independently, we should be able to check if any component does not contain phase-space parameters. Such a process can only be integrated if there are structure functions. <>= procedure :: contains_trivial_component => process_contains_trivial_component <>= function process_contains_trivial_component (process) result (flag) class(process_t), intent(in) :: process logical :: flag integer :: i flag = .true. do i = 1, process%meta%n_components associate (component => process%component(i)) if (component%active) then if (component%get_n_phs_par () == 0) return end if end associate end do flag = .false. end function process_contains_trivial_component @ %def process_contains_trivial_component @ <>= procedure :: get_master_component => process_get_master_component <>= function process_get_master_component (process, i_mci) result (i_component) integer :: i_component class(process_t), intent(in) :: process integer, intent(in) :: i_mci integer :: i i_component = 0 do i = 1, size (process%component) if (process%component(i)%i_mci == i_mci) then i_component = i return end if end do end function process_get_master_component @ %def process_get_master_component @ Determine the MC parameter set structure and the MCI configuration for each process component. We need data from the structure-function and phase-space setup, so those should be complete before this is called. We also make a random-number generator instance for each MCI group. <>= procedure :: setup_mci => process_setup_mci <>= subroutine process_setup_mci (process, dispatch_mci) class(process_t), intent(inout) :: process procedure(dispatch_mci_proc) :: dispatch_mci class(mci_t), allocatable :: mci_template integer :: i, i_mci if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, "process_setup_mci") associate (pcm => process%pcm) call pcm%call_dispatch_mci (dispatch_mci, & process%get_var_list_ptr (), process%meta%id, mci_template) call pcm%setup_mci (process%mci_entry) process%config%n_mci = pcm%n_mci process%component(:)%i_mci = pcm%i_mci(:) do i = 1, pcm%n_components i_mci = process%pcm%i_mci(i) if (i_mci > 0) then associate (component => process%component(i), & mci_entry => process%mci_entry(i_mci)) call mci_entry%configure (mci_template, & process%meta%type, & i_mci, i, component, process%beam_config%n_sfpar, & process%rng_factory) call mci_entry%set_parameters (process%get_var_list_ptr ()) end associate end if end do end associate end subroutine process_setup_mci @ %def process_setup_mci @ Set cuts. This is a parse node, namely the right-hand side of the [[cut]] assignment. When creating an instance, we compile this into an evaluation tree. The parse node may be null. <>= procedure :: set_cuts => process_set_cuts <>= subroutine process_set_cuts (process, ef_cuts) class(process_t), intent(inout) :: process class(expr_factory_t), intent(in) :: ef_cuts allocate (process%config%ef_cuts, source = ef_cuts) end subroutine process_set_cuts @ %def process_set_cuts @ Analogously for the other expressions. <>= procedure :: set_scale => process_set_scale procedure :: set_fac_scale => process_set_fac_scale procedure :: set_ren_scale => process_set_ren_scale procedure :: set_weight => process_set_weight <>= subroutine process_set_scale (process, ef_scale) class(process_t), intent(inout) :: process class(expr_factory_t), intent(in) :: ef_scale allocate (process%config%ef_scale, source = ef_scale) end subroutine process_set_scale subroutine process_set_fac_scale (process, ef_fac_scale) class(process_t), intent(inout) :: process class(expr_factory_t), intent(in) :: ef_fac_scale allocate (process%config%ef_fac_scale, source = ef_fac_scale) end subroutine process_set_fac_scale subroutine process_set_ren_scale (process, ef_ren_scale) class(process_t), intent(inout) :: process class(expr_factory_t), intent(in) :: ef_ren_scale allocate (process%config%ef_ren_scale, source = ef_ren_scale) end subroutine process_set_ren_scale subroutine process_set_weight (process, ef_weight) class(process_t), intent(inout) :: process class(expr_factory_t), intent(in) :: ef_weight allocate (process%config%ef_weight, source = ef_weight) end subroutine process_set_weight @ %def process_set_scale @ %def process_set_fac_scale @ %def process_set_ren_scale @ %def process_set_weight @ \subsubsection{MD5 sum} The MD5 sum of the process object should reflect the state completely, including integration results. It is used for checking the integrity of event files. This global checksum includes checksums for the various parts. In particular, the MCI object receives a checksum that includes the configuration of all configuration parts relevant for an individual integration. This checksum is used for checking the integrity of integration grids. We do not need MD5 sums for the process terms, since these are generated from the component definitions. <>= procedure :: compute_md5sum => process_compute_md5sum <>= subroutine process_compute_md5sum (process) class(process_t), intent(inout) :: process integer :: i call process%config%compute_md5sum () do i = 1, process%config%n_components associate (component => process%component(i)) if (component%active) then call component%compute_md5sum () end if end associate end do call process%beam_config%compute_md5sum () do i = 1, process%config%n_mci call process%mci_entry(i)%compute_md5sum & (process%config, process%component, process%beam_config) end do end subroutine process_compute_md5sum @ %def process_compute_md5sum @ <>= procedure :: sampler_test => process_sampler_test <>= subroutine process_sampler_test (process, sampler, n_calls, i_mci) class(process_t), intent(inout) :: process class(mci_sampler_t), intent(inout) :: sampler integer, intent(in) :: n_calls, i_mci call process%mci_entry(i_mci)%sampler_test (sampler, n_calls) end subroutine process_sampler_test @ %def process_sampler_test @ The finalizer should be called after all integration passes have been completed. It will, for instance, write a summary of the integration results. [[integrate_dummy]] does a ``dummy'' integration in the sense that nothing is done but just empty integration results appended. <>= procedure :: final_integration => process_final_integration procedure :: integrate_dummy => process_integrate_dummy <>= subroutine process_final_integration (process, i_mci) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci call process%mci_entry(i_mci)%final_integration () end subroutine process_final_integration subroutine process_integrate_dummy (process) class(process_t), intent(inout) :: process type(integration_results_t) :: results integer :: u_log u_log = logfile_unit () call results%init (process%meta%type) call results%display_init (screen = .true., unit = u_log) call results%new_pass () call results%record (1, 0, 0._default, 0._default, 0._default) call results%display_final () end subroutine process_integrate_dummy @ %def process_final_integration @ %def process_integrate_dummy @ <>= procedure :: integrate => process_integrate <>= subroutine process_integrate (process, i_mci, mci_work, & mci_sampler, n_it, n_calls, adapt_grids, adapt_weights, final, & pacify, nlo_type) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci type(mci_work_t), intent(inout) :: mci_work class(mci_sampler_t), intent(inout) :: mci_sampler integer, intent(in) :: n_it, n_calls logical, intent(in), optional :: adapt_grids, adapt_weights logical, intent(in), optional :: final logical, intent(in), optional :: pacify integer, intent(in), optional :: nlo_type associate (mci_entry => process%mci_entry(i_mci)) call mci_entry%integrate (mci_work%mci, mci_sampler, n_it, n_calls, & adapt_grids, adapt_weights, final, pacify, & nlo_type = nlo_type) call mci_entry%results%display_pass (pacify) end associate end subroutine process_integrate @ %def process_integrate @ <>= procedure :: generate_weighted_event => process_generate_weighted_event <>= subroutine process_generate_weighted_event (process, i_mci, mci_work, & mci_sampler, keep_failed_events) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci type(mci_work_t), intent(inout) :: mci_work class(mci_sampler_t), intent(inout) :: mci_sampler logical, intent(in) :: keep_failed_events associate (mci_entry => process%mci_entry(i_mci)) call mci_entry%generate_weighted_event (mci_work%mci, & mci_sampler, keep_failed_events) end associate end subroutine process_generate_weighted_event @ %def process_generate_weighted_event <>= procedure :: generate_unweighted_event => process_generate_unweighted_event <>= subroutine process_generate_unweighted_event (process, i_mci, & mci_work, mci_sampler) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci type(mci_work_t), intent(inout) :: mci_work class(mci_sampler_t), intent(inout) :: mci_sampler associate (mci_entry => process%mci_entry(i_mci)) call mci_entry%generate_unweighted_event & (mci_work%mci, mci_sampler) end associate end subroutine process_generate_unweighted_event @ %def process_generate_unweighted_event @ Display the final results for the sum of all components. This is useful, obviously, only if there is more than one component and not if a combined integration of all components together has been performed. <>= procedure :: display_summed_results => process_display_summed_results <>= subroutine process_display_summed_results (process, pacify) class(process_t), intent(inout) :: process logical, intent(in) :: pacify type(integration_results_t) :: results integer :: u_log u_log = logfile_unit () call results%init (process%meta%type) call results%display_init (screen = .true., unit = u_log) call results%new_pass () call results%record (1, 0, & process%get_integral (), & process%get_error (), & process%get_efficiency (), suppress = pacify) select type (pcm => process%pcm) class is (pcm_nlo_t) !!! Check that Born integral is there if (.not. pcm%settings%combined_integration .and. & process%component_can_be_integrated (1)) then call results%record_correction (process%get_correction (), & process%get_correction_error ()) end if end select call results%display_final () end subroutine process_display_summed_results @ %def process_display_summed_results @ Run LaTeX/Metapost to generate a ps/pdf file for the integration history. We (re)write the driver file -- just in case it has been missed before -- then we compile it. <>= procedure :: display_integration_history => & process_display_integration_history <>= subroutine process_display_integration_history & (process, i_mci, filename, os_data, eff_reset) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci type(string_t), intent(in) :: filename type(os_data_t), intent(in) :: os_data logical, intent(in), optional :: eff_reset call integration_results_write_driver & (process%mci_entry(i_mci)%results, filename, eff_reset) call integration_results_compile_driver & (process%mci_entry(i_mci)%results, filename, os_data) end subroutine process_display_integration_history @ %def subroutine process_display_integration_history @ Write a complete logfile (with hardcoded name based on the process ID). We do not write internal data. <>= procedure :: write_logfile => process_write_logfile <>= subroutine process_write_logfile (process, i_mci, filename) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci type(string_t), intent(in) :: filename type(time_t) :: time integer :: unit, u unit = free_unit () open (unit = unit, file = char (filename), action = "write", & status = "replace") u = given_output_unit (unit) write (u, "(A)") repeat ("#", 79) call process%meta%write (u, .false.) write (u, "(A)") repeat ("#", 79) write (u, "(3x,A,ES17.10)") "Integral = ", & process%mci_entry(i_mci)%get_integral () write (u, "(3x,A,ES17.10)") "Error = ", & process%mci_entry(i_mci)%get_error () write (u, "(3x,A,ES17.10)") "Accuracy = ", & process%mci_entry(i_mci)%get_accuracy () write (u, "(3x,A,ES17.10)") "Chi2 = ", & process%mci_entry(i_mci)%get_chi2 () write (u, "(3x,A,ES17.10)") "Efficiency = ", & process%mci_entry(i_mci)%get_efficiency () call process%mci_entry(i_mci)%get_time (time, 10000) if (time%is_known ()) then write (u, "(3x,A,1x,A)") "T(10k evt) = ", char (time%to_string_dhms ()) else write (u, "(3x,A)") "T(10k evt) = [undefined]" end if call process%mci_entry(i_mci)%results%write (u) write (u, "(A)") repeat ("#", 79) call process%mci_entry(i_mci)%results%write_chain_weights (u) write (u, "(A)") repeat ("#", 79) call process%mci_entry(i_mci)%counter%write (u) write (u, "(A)") repeat ("#", 79) call process%mci_entry(i_mci)%mci%write_log_entry (u) write (u, "(A)") repeat ("#", 79) call process%beam_config%data%write (u) write (u, "(A)") repeat ("#", 79) if (allocated (process%config%ef_cuts)) then write (u, "(3x,A)") "Cut expression:" call process%config%ef_cuts%write (u) else write (u, "(3x,A)") "No cuts used." end if call write_separator (u) if (allocated (process%config%ef_scale)) then write (u, "(3x,A)") "Scale expression:" call process%config%ef_scale%write (u) else write (u, "(3x,A)") "No scale expression was given." end if call write_separator (u) if (allocated (process%config%ef_fac_scale)) then write (u, "(3x,A)") "Factorization scale expression:" call process%config%ef_fac_scale%write (u) else write (u, "(3x,A)") "No factorization scale expression was given." end if call write_separator (u) if (allocated (process%config%ef_ren_scale)) then write (u, "(3x,A)") "Renormalization scale expression:" call process%config%ef_ren_scale%write (u) else write (u, "(3x,A)") "No renormalization scale expression was given." end if call write_separator (u) if (allocated (process%config%ef_weight)) then call write_separator (u) write (u, "(3x,A)") "Weight expression:" call process%config%ef_weight%write (u) else write (u, "(3x,A)") "No weight expression was given." end if write (u, "(A)") repeat ("#", 79) write (u, "(1x,A)") "Summary of quantum-number states:" write (u, "(1x,A)") " + sign: allowed and contributing" write (u, "(1x,A)") " no + : switched off at runtime" call process%write_state_summary (u) write (u, "(A)") repeat ("#", 79) call process%env%write (u, show_var_list=.true., & show_model=.false., show_lib=.false., show_os_data=.false.) write (u, "(A)") repeat ("#", 79) close (u) end subroutine process_write_logfile @ %def process_write_logfile @ Display the quantum-number combinations of the process components, and their current status (allowed or switched off). <>= procedure :: write_state_summary => process_write_state_summary <>= subroutine process_write_state_summary (process, unit) class(process_t), intent(in) :: process integer, intent(in), optional :: unit integer :: i, i_component, u u = given_output_unit (unit) do i = 1, size (process%term) call write_separator (u) i_component = process%term(i)%i_component if (i_component /= 0) then call process%term(i)%write_state_summary & (process%get_core_term(i), unit) end if end do end subroutine process_write_state_summary @ %def process_write_state_summary @ Prepare event generation for the specified MCI entry. This implies, in particular, checking the phase-space file. <>= procedure :: prepare_simulation => process_prepare_simulation <>= subroutine process_prepare_simulation (process, i_mci) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci call process%mci_entry(i_mci)%prepare_simulation () end subroutine process_prepare_simulation @ %def process_prepare_simulation @ \subsubsection{Retrieve process data} Tell whether integral (and error) are known. <>= generic :: has_integral => has_integral_tot, has_integral_mci procedure :: has_integral_tot => process_has_integral_tot procedure :: has_integral_mci => process_has_integral_mci <>= function process_has_integral_mci (process, i_mci) result (flag) logical :: flag class(process_t), intent(in) :: process integer, intent(in) :: i_mci if (allocated (process%mci_entry)) then flag = process%mci_entry(i_mci)%has_integral () else flag = .false. end if end function process_has_integral_mci function process_has_integral_tot (process) result (flag) logical :: flag class(process_t), intent(in) :: process integer :: i, j, i_component if (allocated (process%mci_entry)) then flag = .true. do i = 1, size (process%mci_entry) do j = 1, size (process%mci_entry(i)%i_component) i_component = process%mci_entry(i)%i_component(j) if (process%component_can_be_integrated (i_component)) & flag = flag .and. process%mci_entry(i)%has_integral () end do end do else flag = .false. end if end function process_has_integral_tot @ %def process_has_integral @ Return the current integral and error obtained by the integrator [[i_mci]]. <>= generic :: get_integral => get_integral_tot, get_integral_mci generic :: get_error => get_error_tot, get_error_mci generic :: get_efficiency => get_efficiency_tot, get_efficiency_mci procedure :: get_integral_tot => process_get_integral_tot procedure :: get_integral_mci => process_get_integral_mci procedure :: get_error_tot => process_get_error_tot procedure :: get_error_mci => process_get_error_mci procedure :: get_efficiency_tot => process_get_efficiency_tot procedure :: get_efficiency_mci => process_get_efficiency_mci <>= function process_get_integral_mci (process, i_mci) result (integral) real(default) :: integral class(process_t), intent(in) :: process integer, intent(in) :: i_mci integral = process%mci_entry(i_mci)%get_integral () end function process_get_integral_mci function process_get_error_mci (process, i_mci) result (error) real(default) :: error class(process_t), intent(in) :: process integer, intent(in) :: i_mci error = process%mci_entry(i_mci)%get_error () end function process_get_error_mci function process_get_efficiency_mci (process, i_mci) result (efficiency) real(default) :: efficiency class(process_t), intent(in) :: process integer, intent(in) :: i_mci efficiency = process%mci_entry(i_mci)%get_efficiency () end function process_get_efficiency_mci function process_get_integral_tot (process) result (integral) real(default) :: integral class(process_t), intent(in) :: process integer :: i, j, i_component integral = zero if (allocated (process%mci_entry)) then do i = 1, size (process%mci_entry) do j = 1, size (process%mci_entry(i)%i_component) i_component = process%mci_entry(i)%i_component(j) if (process%component_can_be_integrated(i_component)) & integral = integral + process%mci_entry(i)%get_integral () end do end do end if end function process_get_integral_tot function process_get_error_tot (process) result (error) real(default) :: variance class(process_t), intent(in) :: process real(default) :: error integer :: i, j, i_component variance = zero if (allocated (process%mci_entry)) then do i = 1, size (process%mci_entry) do j = 1, size (process%mci_entry(i)%i_component) i_component = process%mci_entry(i)%i_component(j) if (process%component_can_be_integrated(i_component)) & variance = variance + process%mci_entry(i)%get_error () ** 2 end do end do end if error = sqrt (variance) end function process_get_error_tot function process_get_efficiency_tot (process) result (efficiency) real(default) :: efficiency class(process_t), intent(in) :: process real(default) :: den, eff, int integer :: i, j, i_component den = zero if (allocated (process%mci_entry)) then do i = 1, size (process%mci_entry) do j = 1, size (process%mci_entry(i)%i_component) i_component = process%mci_entry(i)%i_component(j) if (process%component_can_be_integrated(i_component)) then int = process%get_integral (i) if (int > 0) then eff = process%mci_entry(i)%get_efficiency () if (eff > 0) then den = den + int / eff else efficiency = 0 return end if end if end if end do end do end if if (den > 0) then efficiency = process%get_integral () / den else efficiency = 0 end if end function process_get_efficiency_tot @ %def process_get_integral process_get_efficiency @ Let us call the ratio of the NLO and the LO result $\iota = I_{NLO} / I_{LO}$. Then usual error propagation gives \begin{equation*} \sigma_{\iota}^2 = \left(\frac{\partial \iota}{\partial I_{LO}}\right)^2 \sigma_{I_{LO}}^2 + \left(\frac{\partial \iota}{\partial I_{NLO}}\right)^2 \sigma_{I_{NLO}}^2 = \frac{I_{NLO}^2\sigma_{I_{LO}}^2}{I_{LO}^4} + \frac{\sigma_{I_{NLO}}^2}{I_{LO}^2}. \end{equation*} <>= procedure :: get_correction => process_get_correction procedure :: get_correction_error => process_get_correction_error <>= function process_get_correction (process) result (ratio) real(default) :: ratio class(process_t), intent(in) :: process integer :: i_mci, i_component real(default) :: int_born, int_nlo int_nlo = zero int_born = process%mci_entry(1)%get_integral () i_mci = 2 do i_component = 2, size (process%component) if (process%component_can_be_integrated (i_component)) then int_nlo = int_nlo + process%mci_entry(i_mci)%get_integral () i_mci = i_mci + 1 end if end do ratio = int_nlo / int_born * 100 end function process_get_correction function process_get_correction_error (process) result (error) real(default) :: error class(process_t), intent(in) :: process real(default) :: int_born, sum_int_nlo real(default) :: err_born, err2 integer :: i_mci, i_component sum_int_nlo = zero; err2 = zero int_born = process%mci_entry(1)%get_integral () err_born = process%mci_entry(1)%get_error () i_mci = 2 do i_component = 2, size (process%component) if (process%component_can_be_integrated (i_component)) then sum_int_nlo = sum_int_nlo + process%mci_entry(i_mci)%get_integral () err2 = err2 + process%mci_entry(i_mci)%get_error()**2 i_mci = i_mci + 1 end if end do error = sqrt (err2 / int_born**2 + sum_int_nlo**2 * err_born**2 / int_born**4) * 100 end function process_get_correction_error @ %def process_get_correction process_get_correction_error @ <>= procedure :: lab_is_cm => process_lab_is_cm <>= pure function process_lab_is_cm (process) result (lab_is_cm) logical :: lab_is_cm class(process_t), intent(in) :: process lab_is_cm = process%beam_config%lab_is_cm ! This asks beam_config for the frame end function process_lab_is_cm @ %def process_lab_is_cm @ <>= procedure :: get_component_ptr => process_get_component_ptr <>= function process_get_component_ptr (process, i) result (component) type(process_component_t), pointer :: component class(process_t), intent(in), target :: process integer, intent(in) :: i component => process%component(i) end function process_get_component_ptr @ %def process_get_component_ptr @ <>= procedure :: get_qcd => process_get_qcd <>= function process_get_qcd (process) result (qcd) type(qcd_t) :: qcd class(process_t), intent(in) :: process qcd = process%config%get_qcd () end function process_get_qcd @ %def process_get_qcd @ <>= generic :: get_component_type => get_component_type_single procedure :: get_component_type_single => process_get_component_type_single <>= elemental function process_get_component_type_single & (process, i_component) result (comp_type) integer :: comp_type class(process_t), intent(in) :: process integer, intent(in) :: i_component comp_type = process%component(i_component)%component_type end function process_get_component_type_single @ %def process_get_component_type_single @ <>= generic :: get_component_type => get_component_type_all procedure :: get_component_type_all => process_get_component_type_all <>= function process_get_component_type_all & (process) result (comp_type) integer, dimension(:), allocatable :: comp_type class(process_t), intent(in) :: process allocate (comp_type (size (process%component))) comp_type = process%component%component_type end function process_get_component_type_all @ %def process_get_component_type_all @ <>= procedure :: get_component_i_terms => process_get_component_i_terms <>= function process_get_component_i_terms (process, i_component) result (i_term) integer, dimension(:), allocatable :: i_term class(process_t), intent(in) :: process integer, intent(in) :: i_component allocate (i_term (size (process%component(i_component)%i_term))) i_term = process%component(i_component)%i_term end function process_get_component_i_terms @ %def process_get_component_i_terms @ <>= procedure :: get_n_allowed_born => process_get_n_allowed_born <>= function process_get_n_allowed_born (process, i_born) result (n_born) class(process_t), intent(inout) :: process integer, intent(in) :: i_born integer :: n_born n_born = process%term(i_born)%n_allowed end function process_get_n_allowed_born @ %def process_get_n_allowed_born @ Workaround getter. Would be better to remove this. <>= procedure :: get_pcm_ptr => process_get_pcm_ptr <>= function process_get_pcm_ptr (process) result (pcm) class(pcm_t), pointer :: pcm class(process_t), intent(in), target :: process pcm => process%pcm end function process_get_pcm_ptr @ %def process_get_pcm_ptr <>= generic :: component_can_be_integrated => component_can_be_integrated_single generic :: component_can_be_integrated => component_can_be_integrated_all procedure :: component_can_be_integrated_single => process_component_can_be_integrated_single <>= function process_component_can_be_integrated_single (process, i_component) & result (active) logical :: active class(process_t), intent(in) :: process integer, intent(in) :: i_component logical :: combined_integration select type (pcm => process%pcm) type is (pcm_nlo_t) combined_integration = pcm%settings%combined_integration class default combined_integration = .false. end select associate (component => process%component(i_component)) active = component%can_be_integrated () if (combined_integration) & active = active .and. component%component_type <= COMP_MASTER end associate end function process_component_can_be_integrated_single @ %def process_component_can_be_integrated_single @ <>= procedure :: component_can_be_integrated_all => process_component_can_be_integrated_all <>= function process_component_can_be_integrated_all (process) result (val) logical, dimension(:), allocatable :: val class(process_t), intent(in) :: process integer :: i allocate (val (size (process%component))) do i = 1, size (process%component) val(i) = process%component_can_be_integrated (i) end do end function process_component_can_be_integrated_all @ %def process_component_can_be_integrated_all @ <>= procedure :: reset_selected_cores => process_reset_selected_cores <>= pure subroutine process_reset_selected_cores (process) class(process_t), intent(inout) :: process process%pcm%component_selected = .false. end subroutine process_reset_selected_cores @ %def process_reset_selected_cores @ <>= procedure :: select_components => process_select_components <>= pure subroutine process_select_components (process, indices) class(process_t), intent(inout) :: process integer, dimension(:), intent(in) :: indices associate (pcm => process%pcm) pcm%component_selected(indices) = .true. end associate end subroutine process_select_components @ %def process_select_components @ <>= procedure :: component_is_selected => process_component_is_selected <>= pure function process_component_is_selected (process, index) result (val) logical :: val class(process_t), intent(in) :: process integer, intent(in) :: index associate (pcm => process%pcm) val = pcm%component_selected(index) end associate end function process_component_is_selected @ %def process_component_is_selected @ <>= procedure :: get_coupling_powers => process_get_coupling_powers <>= pure subroutine process_get_coupling_powers (process, alpha_power, alphas_power) class(process_t), intent(in) :: process integer, intent(out) :: alpha_power, alphas_power call process%component(1)%config%get_coupling_powers (alpha_power, alphas_power) end subroutine process_get_coupling_powers @ %def process_get_coupling_powers @ <>= procedure :: get_real_component => process_get_real_component <>= function process_get_real_component (process) result (i_real) integer :: i_real class(process_t), intent(in) :: process integer :: i_component type(process_component_def_t), pointer :: config => null () i_real = 0 do i_component = 1, size (process%component) config => process%get_component_def_ptr (i_component) if (config%get_nlo_type () == NLO_REAL) then i_real = i_component exit end if end do end function process_get_real_component @ %def process_get_real_component @ <>= procedure :: extract_active_component_mci => process_extract_active_component_mci <>= function process_extract_active_component_mci (process) result (i_active) integer :: i_active class(process_t), intent(in) :: process integer :: i_mci, j, i_component, n_active call count_n_active () if (n_active /= 1) i_active = 0 contains subroutine count_n_active () n_active = 0 do i_mci = 1, size (process%mci_entry) associate (mci_entry => process%mci_entry(i_mci)) do j = 1, size (mci_entry%i_component) i_component = mci_entry%i_component(j) associate (component => process%component (i_component)) if (component%can_be_integrated ()) then i_active = i_mci n_active = n_active + 1 end if end associate end do end associate end do end subroutine count_n_active end function process_extract_active_component_mci @ %def process_extract_active_component_mci @ <>= procedure :: uses_real_partition => process_uses_real_partition <>= function process_uses_real_partition (process) result (val) logical :: val class(process_t), intent(in) :: process val = any (process%mci_entry%real_partition_type /= REAL_FULL) end function process_uses_real_partition @ %def process_uses_real_partition @ Return the MD5 sums that summarize the process component definitions. These values should be independent of parameters, beam details, expressions, etc. They can be used for checking the integrity of a process when reusing an old event file. <>= procedure :: get_md5sum_prc => process_get_md5sum_prc <>= function process_get_md5sum_prc (process, i_component) result (md5sum) character(32) :: md5sum class(process_t), intent(in) :: process integer, intent(in) :: i_component if (process%component(i_component)%active) then md5sum = process%component(i_component)%config%get_md5sum () else md5sum = "" end if end function process_get_md5sum_prc @ %def process_get_md5sum_prc @ Return the MD5 sums that summarize the state of the MCI integrators. These values should encode all process data, integration and phase space configuration, etc., and the integration results. They can thus be used for checking the integrity of an event-generation setup when reusing an old event file. <>= procedure :: get_md5sum_mci => process_get_md5sum_mci <>= function process_get_md5sum_mci (process, i_mci) result (md5sum) character(32) :: md5sum class(process_t), intent(in) :: process integer, intent(in) :: i_mci md5sum = process%mci_entry(i_mci)%get_md5sum () end function process_get_md5sum_mci @ %def process_get_md5sum_mci @ Return the MD5 sum of the process configuration. This should encode the process setup, data, and expressions, but no integration results. <>= procedure :: get_md5sum_cfg => process_get_md5sum_cfg <>= function process_get_md5sum_cfg (process) result (md5sum) character(32) :: md5sum class(process_t), intent(in) :: process md5sum = process%config%md5sum end function process_get_md5sum_cfg @ %def process_get_md5sum_cfg @ <>= procedure :: get_n_cores => process_get_n_cores <>= function process_get_n_cores (process) result (n) integer :: n class(process_t), intent(in) :: process n = process%pcm%n_cores end function process_get_n_cores @ %def process_get_n_cores @ <>= procedure :: get_base_i_term => process_get_base_i_term <>= function process_get_base_i_term (process, i_component) result (i_term) integer :: i_term class(process_t), intent(in) :: process integer, intent(in) :: i_component i_term = process%component(i_component)%i_term(1) end function process_get_base_i_term @ %def process_get_base_i_term @ <>= procedure :: get_core_term => process_get_core_term <>= function process_get_core_term (process, i_term) result (core) class(prc_core_t), pointer :: core class(process_t), intent(in), target :: process integer, intent(in) :: i_term integer :: i_core i_core = process%term(i_term)%i_core core => process%core_entry(i_core)%get_core_ptr () end function process_get_core_term @ %def process_get_core_term @ <>= procedure :: get_core_ptr => process_get_core_ptr <>= function process_get_core_ptr (process, i_core) result (core) class(prc_core_t), pointer :: core class(process_t), intent(in), target :: process integer, intent(in) :: i_core if (allocated (process%core_entry)) then core => process%core_entry(i_core)%get_core_ptr () else core => null () end if end function process_get_core_ptr @ %def process_get_core_ptr @ <>= procedure :: get_term_ptr => process_get_term_ptr <>= function process_get_term_ptr (process, i) result (term) type(process_term_t), pointer :: term class(process_t), intent(in), target :: process integer, intent(in) :: i term => process%term(i) end function process_get_term_ptr @ %def process_get_term_ptr @ <>= procedure :: get_i_term => process_get_i_term <>= function process_get_i_term (process, i_core) result (i_term) integer :: i_term class(process_t), intent(in) :: process integer, intent(in) :: i_core do i_term = 1, process%get_n_terms () if (process%term(i_term)%i_core == i_core) return end do i_term = -1 end function process_get_i_term @ %def process_get_i_term @ <>= procedure :: get_i_core => process_get_i_core <>= integer function process_get_i_core (process, i_term) result (i_core) class(process_t), intent(in) :: process integer, intent(in) :: i_term i_core = process%term(i_term)%i_core end function process_get_i_core @ %def process_get_i_core @ <>= procedure :: set_i_mci_work => process_set_i_mci_work <>= subroutine process_set_i_mci_work (process, i_mci) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci process%mci_entry(i_mci)%i_mci = i_mci end subroutine process_set_i_mci_work @ %def process_set_i_mci_work @ <>= procedure :: get_i_mci_work => process_get_i_mci_work <>= pure function process_get_i_mci_work (process, i_mci) result (i_mci_work) integer :: i_mci_work class(process_t), intent(in) :: process integer, intent(in) :: i_mci i_mci_work = process%mci_entry(i_mci)%i_mci end function process_get_i_mci_work @ %def process_get_i_mci_work @ <>= procedure :: get_i_sub => process_get_i_sub <>= elemental function process_get_i_sub (process, i_term) result (i_sub) integer :: i_sub class(process_t), intent(in) :: process integer, intent(in) :: i_term i_sub = process%term(i_term)%i_sub end function process_get_i_sub @ %def process_get_i_sub @ <>= procedure :: get_i_term_virtual => process_get_i_term_virtual <>= elemental function process_get_i_term_virtual (process) result (i_term) integer :: i_term class(process_t), intent(in) :: process integer :: i_component i_term = 0 do i_component = 1, size (process%component) if (process%component(i_component)%get_nlo_type () == NLO_VIRTUAL) & i_term = process%component(i_component)%i_term(1) end do end function process_get_i_term_virtual @ %def process_get_i_term_virtual @ <>= generic :: component_is_active => component_is_active_single procedure :: component_is_active_single => process_component_is_active_single <>= elemental function process_component_is_active_single (process, i_comp) result (val) logical :: val class(process_t), intent(in) :: process integer, intent(in) :: i_comp val = process%component(i_comp)%is_active () end function process_component_is_active_single @ %def process_component_is_active_single @ <>= generic :: component_is_active => component_is_active_all procedure :: component_is_active_all => process_component_is_active_all <>= pure function process_component_is_active_all (process) result (val) logical, dimension(:), allocatable :: val class(process_t), intent(in) :: process allocate (val (size (process%component))) val = process%component%is_active () end function process_component_is_active_all @ %def process_component_is_active_all @ \subsection{Default iterations} If the user does not specify the passes and iterations for integration, we should be able to give reasonable defaults. These depend on the process, therefore we implement the following procedures as methods of the process object. The algorithm is not very sophisticated yet, it may be improved by looking at the process in more detail. We investigate only the first process component, assuming that it characterizes the complexity of the process reasonable well. The number of passes is limited to two: one for adaption, one for integration. <>= procedure :: get_n_pass_default => process_get_n_pass_default procedure :: adapt_grids_default => process_adapt_grids_default procedure :: adapt_weights_default => process_adapt_weights_default <>= function process_get_n_pass_default (process) result (n_pass) class(process_t), intent(in) :: process integer :: n_pass integer :: n_eff type(process_component_def_t), pointer :: config config => process%component(1)%config n_eff = config%get_n_tot () - 2 select case (n_eff) case (1) n_pass = 1 case default n_pass = 2 end select end function process_get_n_pass_default function process_adapt_grids_default (process, pass) result (flag) class(process_t), intent(in) :: process integer, intent(in) :: pass logical :: flag integer :: n_eff type(process_component_def_t), pointer :: config config => process%component(1)%config n_eff = config%get_n_tot () - 2 select case (n_eff) case (1) flag = .false. case default select case (pass) case (1); flag = .true. case (2); flag = .false. case default call msg_bug ("adapt grids default: impossible pass index") end select end select end function process_adapt_grids_default function process_adapt_weights_default (process, pass) result (flag) class(process_t), intent(in) :: process integer, intent(in) :: pass logical :: flag integer :: n_eff type(process_component_def_t), pointer :: config config => process%component(1)%config n_eff = config%get_n_tot () - 2 select case (n_eff) case (1) flag = .false. case default select case (pass) case (1); flag = .true. case (2); flag = .false. case default call msg_bug ("adapt weights default: impossible pass index") end select end select end function process_adapt_weights_default @ %def process_get_n_pass_default @ %def process_adapt_grids_default @ %def process_adapt_weights_default @ The number of iterations and calls per iteration depends on the number of outgoing particles. <>= procedure :: get_n_it_default => process_get_n_it_default procedure :: get_n_calls_default => process_get_n_calls_default <>= function process_get_n_it_default (process, pass) result (n_it) class(process_t), intent(in) :: process integer, intent(in) :: pass integer :: n_it integer :: n_eff type(process_component_def_t), pointer :: config config => process%component(1)%config n_eff = config%get_n_tot () - 2 select case (pass) case (1) select case (n_eff) case (1); n_it = 1 case (2); n_it = 3 case (3); n_it = 5 case (4:5); n_it = 10 case (6); n_it = 15 case (7:); n_it = 20 end select case (2) select case (n_eff) case (:3); n_it = 3 case (4:); n_it = 5 end select end select end function process_get_n_it_default function process_get_n_calls_default (process, pass) result (n_calls) class(process_t), intent(in) :: process integer, intent(in) :: pass integer :: n_calls integer :: n_eff type(process_component_def_t), pointer :: config config => process%component(1)%config n_eff = config%get_n_tot () - 2 select case (pass) case (1) select case (n_eff) case (1); n_calls = 100 case (2); n_calls = 1000 case (3); n_calls = 5000 case (4); n_calls = 10000 case (5); n_calls = 20000 case (6:); n_calls = 50000 end select case (2) select case (n_eff) case (:3); n_calls = 10000 case (4); n_calls = 20000 case (5); n_calls = 50000 case (6); n_calls = 100000 case (7:); n_calls = 200000 end select end select end function process_get_n_calls_default @ %def process_get_n_it_default @ %def process_get_n_calls_default @ \subsection{Constant process data} Manually set the Run ID (unit test only). <>= procedure :: set_run_id => process_set_run_id <>= subroutine process_set_run_id (process, run_id) class(process_t), intent(inout) :: process type(string_t), intent(in) :: run_id process%meta%run_id = run_id end subroutine process_set_run_id @ %def process_set_run_id @ The following methods return basic process data that stay constant after initialization. The process and IDs. <>= procedure :: get_id => process_get_id procedure :: get_num_id => process_get_num_id procedure :: get_run_id => process_get_run_id procedure :: get_library_name => process_get_library_name <>= function process_get_id (process) result (id) class(process_t), intent(in) :: process type(string_t) :: id id = process%meta%id end function process_get_id function process_get_num_id (process) result (id) class(process_t), intent(in) :: process integer :: id id = process%meta%num_id end function process_get_num_id function process_get_run_id (process) result (id) class(process_t), intent(in) :: process type(string_t) :: id id = process%meta%run_id end function process_get_run_id function process_get_library_name (process) result (id) class(process_t), intent(in) :: process type(string_t) :: id id = process%meta%lib_name end function process_get_library_name @ %def process_get_id process_get_num_id @ %def process_get_run_id process_get_library_name @ The number of incoming particles. <>= procedure :: get_n_in => process_get_n_in <>= function process_get_n_in (process) result (n) class(process_t), intent(in) :: process integer :: n n = process%config%n_in end function process_get_n_in @ %def process_get_n_in @ The number of MCI data sets. <>= procedure :: get_n_mci => process_get_n_mci <>= function process_get_n_mci (process) result (n) class(process_t), intent(in) :: process integer :: n n = process%config%n_mci end function process_get_n_mci @ %def process_get_n_mci @ The number of process components, total. <>= procedure :: get_n_components => process_get_n_components <>= function process_get_n_components (process) result (n) class(process_t), intent(in) :: process integer :: n n = process%meta%n_components end function process_get_n_components @ %def process_get_n_components @ The number of process terms, total. <>= procedure :: get_n_terms => process_get_n_terms <>= function process_get_n_terms (process) result (n) class(process_t), intent(in) :: process integer :: n n = process%config%n_terms end function process_get_n_terms @ %def process_get_n_terms @ Return the indices of the components that belong to a specific MCI entry. <>= procedure :: get_i_component => process_get_i_component <>= subroutine process_get_i_component (process, i_mci, i_component) class(process_t), intent(in) :: process integer, intent(in) :: i_mci integer, dimension(:), intent(out), allocatable :: i_component associate (mci_entry => process%mci_entry(i_mci)) allocate (i_component (size (mci_entry%i_component))) i_component = mci_entry%i_component end associate end subroutine process_get_i_component @ %def process_get_i_component @ Return the ID of a specific component. <>= procedure :: get_component_id => process_get_component_id <>= function process_get_component_id (process, i_component) result (id) class(process_t), intent(in) :: process integer, intent(in) :: i_component type(string_t) :: id id = process%meta%component_id(i_component) end function process_get_component_id @ %def process_get_component_id @ Return a pointer to the definition of a specific component. <>= procedure :: get_component_def_ptr => process_get_component_def_ptr <>= function process_get_component_def_ptr (process, i_component) result (ptr) type(process_component_def_t), pointer :: ptr class(process_t), intent(in) :: process integer, intent(in) :: i_component ptr => process%config%process_def%get_component_def_ptr (i_component) end function process_get_component_def_ptr @ %def process_get_component_def_ptr @ These procedures extract and restore (by transferring the allocation) the process core. This is useful for changing process parameters from outside this module. <>= procedure :: extract_core => process_extract_core procedure :: restore_core => process_restore_core <>= subroutine process_extract_core (process, i_term, core) class(process_t), intent(inout) :: process integer, intent(in) :: i_term class(prc_core_t), intent(inout), allocatable :: core integer :: i_core i_core = process%term(i_term)%i_core call move_alloc (from = process%core_entry(i_core)%core, to = core) end subroutine process_extract_core subroutine process_restore_core (process, i_term, core) class(process_t), intent(inout) :: process integer, intent(in) :: i_term class(prc_core_t), intent(inout), allocatable :: core integer :: i_core i_core = process%term(i_term)%i_core call move_alloc (from = core, to = process%core_entry(i_core)%core) end subroutine process_restore_core @ %def process_extract_core @ %def process_restore_core @ The block of process constants. <>= procedure :: get_constants => process_get_constants <>= function process_get_constants (process, i_core) result (data) type(process_constants_t) :: data class(process_t), intent(in) :: process integer, intent(in) :: i_core data = process%core_entry(i_core)%core%data end function process_get_constants @ %def process_get_constants @ <>= procedure :: get_config => process_get_config <>= function process_get_config (process) result (config) type(process_config_data_t) :: config class(process_t), intent(in) :: process config = process%config end function process_get_config @ %def process_get_config @ Construct an MD5 sum for the constant data, including the NLO type. For the NLO type [[NLO_MISMATCH]], we pretend that this was [[NLO_SUBTRACTION]] instead. TODO wk 2018: should not depend explicitly on NLO data. <>= procedure :: get_md5sum_constants => process_get_md5sum_constants <>= function process_get_md5sum_constants (process, i_component, & type_string, nlo_type) result (this_md5sum) character(32) :: this_md5sum class(process_t), intent(in) :: process integer, intent(in) :: i_component type(string_t), intent(in) :: type_string integer, intent(in) :: nlo_type type(process_constants_t) :: data integer :: unit call process%env%fill_process_constants (process%meta%id, i_component, data) unit = data%fill_unit_for_md5sum (.false.) write (unit, '(A)') char(type_string) select case (nlo_type) case (NLO_MISMATCH) write (unit, '(I0)') NLO_SUBTRACTION case default write (unit, '(I0)') nlo_type end select rewind (unit) this_md5sum = md5sum (unit) close (unit) end function process_get_md5sum_constants @ %def process_get_md5sum_constants @ Return the set of outgoing flavors that are associated with a particular term. We deduce this from the effective interaction. <>= procedure :: get_term_flv_out => process_get_term_flv_out <>= subroutine process_get_term_flv_out (process, i_term, flv) class(process_t), intent(in), target :: process integer, intent(in) :: i_term type(flavor_t), dimension(:,:), allocatable, intent(out) :: flv type(interaction_t), pointer :: int int => process%term(i_term)%int_eff if (.not. associated (int)) int => process%term(i_term)%int call interaction_get_flv_out (int, flv) end subroutine process_get_term_flv_out @ %def process_get_term_flv_out @ Return true if there is any unstable particle in any of the process terms. We decide this based on the provided model instance, not the one that is stored in the process object. <>= procedure :: contains_unstable => process_contains_unstable <>= function process_contains_unstable (process, model) result (flag) class(process_t), intent(in) :: process class(model_data_t), intent(in), target :: model logical :: flag integer :: i_term type(flavor_t), dimension(:,:), allocatable :: flv flag = .false. do i_term = 1, process%get_n_terms () call process%get_term_flv_out (i_term, flv) call flv%set_model (model) flag = .not. all (flv%is_stable ()) deallocate (flv) if (flag) return end do end function process_contains_unstable @ %def process_contains_unstable @ The nominal process energy. <>= procedure :: get_sqrts => process_get_sqrts <>= function process_get_sqrts (process) result (sqrts) class(process_t), intent(in) :: process real(default) :: sqrts sqrts = process%beam_config%data%get_sqrts () end function process_get_sqrts @ %def process_get_sqrts @ The lab-frame beam energy/energies.. <>= procedure :: get_energy => process_get_energy <>= function process_get_energy (process) result (e) class(process_t), intent(in) :: process real(default), dimension(:), allocatable :: e e = process%beam_config%data%get_energy () end function process_get_energy @ %def process_get_energy @ The beam polarization in case of simple degrees. <>= procedure :: get_polarization => process_get_polarization <>= function process_get_polarization (process) result (pol) class(process_t), intent(in) :: process real(default), dimension(2) :: pol pol = process%beam_config%data%get_polarization () end function process_get_polarization @ %def process_get_polarization @ <>= procedure :: get_meta => process_get_meta <>= function process_get_meta (process) result (meta) type(process_metadata_t) :: meta class(process_t), intent(in) :: process meta = process%meta end function process_get_meta @ %def process_get_meta <>= procedure :: has_matrix_element => process_has_matrix_element <>= function process_has_matrix_element (process, i, is_term_index) result (active) logical :: active class(process_t), intent(in) :: process integer, intent(in), optional :: i logical, intent(in), optional :: is_term_index integer :: i_component logical :: is_term is_term = .false. if (present (i)) then if (present (is_term_index)) is_term = is_term_index if (is_term) then i_component = process%term(i)%i_component else i_component = i end if active = process%component(i_component)%active else active = any (process%component%active) end if end function process_has_matrix_element @ %def process_has_matrix_element @ Pointer to the beam data object. <>= procedure :: get_beam_data_ptr => process_get_beam_data_ptr <>= function process_get_beam_data_ptr (process) result (beam_data) class(process_t), intent(in), target :: process type(beam_data_t), pointer :: beam_data beam_data => process%beam_config%data end function process_get_beam_data_ptr @ %def process_get_beam_data_ptr @ <>= procedure :: get_beam_config => process_get_beam_config <>= function process_get_beam_config (process) result (beam_config) type(process_beam_config_t) :: beam_config class(process_t), intent(in) :: process beam_config = process%beam_config end function process_get_beam_config @ %def process_get_beam_config @ <>= procedure :: get_beam_config_ptr => process_get_beam_config_ptr <>= function process_get_beam_config_ptr (process) result (beam_config) type(process_beam_config_t), pointer :: beam_config class(process_t), intent(in), target :: process beam_config => process%beam_config end function process_get_beam_config_ptr @ %def process_get_beam_config_ptr @ Get the PDF set currently in use, if any. <>= procedure :: get_pdf_set => process_get_pdf_set <>= function process_get_pdf_set (process) result (pdf_set) class(process_t), intent(in) :: process integer :: pdf_set pdf_set = process%beam_config%get_pdf_set () end function process_get_pdf_set @ %def process_get_pdf_set @ <>= procedure :: pcm_contains_pdfs => process_pcm_contains_pdfs <>= function process_pcm_contains_pdfs (process) result (has_pdfs) logical :: has_pdfs class(process_t), intent(in) :: process has_pdfs = process%pcm%has_pdfs end function process_pcm_contains_pdfs @ %def process_pcm_contains_pdfs @ Get the beam spectrum file currently in use, if any. <>= procedure :: get_beam_file => process_get_beam_file <>= function process_get_beam_file (process) result (file) class(process_t), intent(in) :: process type(string_t) :: file file = process%beam_config%get_beam_file () end function process_get_beam_file @ %def process_get_beam_file @ Pointer to the process variable list. <>= procedure :: get_var_list_ptr => process_get_var_list_ptr <>= function process_get_var_list_ptr (process) result (ptr) class(process_t), intent(in), target :: process type(var_list_t), pointer :: ptr ptr => process%env%get_var_list_ptr () end function process_get_var_list_ptr @ %def process_get_var_list_ptr @ Pointer to the common model. <>= procedure :: get_model_ptr => process_get_model_ptr <>= function process_get_model_ptr (process) result (ptr) class(process_t), intent(in) :: process class(model_data_t), pointer :: ptr ptr => process%config%model end function process_get_model_ptr @ %def process_get_model_ptr @ Use the embedded RNG factory to spawn a new random-number generator instance. (This modifies the state of the factory.) <>= procedure :: make_rng => process_make_rng <>= subroutine process_make_rng (process, rng) class(process_t), intent(inout) :: process class(rng_t), intent(out), allocatable :: rng if (allocated (process%rng_factory)) then call process%rng_factory%make (rng) else call msg_bug ("Process: make rng: factory not allocated") end if end subroutine process_make_rng @ %def process_make_rng @ \subsection{Compute an amplitude} Each process variant should allow for computing an amplitude value directly, without generating a process instance. The process component is selected by the index [[i]]. The term within the process component is selected by [[j]]. The momentum combination is transferred as the array [[p]]. The function sets the specific quantum state via the indices of a flavor [[f]], helicity [[h]], and color [[c]] combination. Each index refers to the list of flavor, helicity, and color states, respectively, as stored in the process data. Optionally, we may set factorization and renormalization scale. If unset, the partonic c.m.\ energy is inserted. The function checks arguments for validity. For invalid arguments (quantum states), we return zero. <>= procedure :: compute_amplitude => process_compute_amplitude <>= function process_compute_amplitude & (process, i_core, i, j, p, f, h, c, fac_scale, ren_scale, alpha_qcd_forced) & result (amp) class(process_t), intent(in), target :: process integer, intent(in) :: i_core integer, intent(in) :: i, j type(vector4_t), dimension(:), intent(in) :: p integer, intent(in) :: f, h, c real(default), intent(in), optional :: fac_scale, ren_scale real(default), intent(in), allocatable, optional :: alpha_qcd_forced real(default) :: fscale, rscale real(default), allocatable :: aqcd_forced complex(default) :: amp class(prc_core_t), pointer :: core amp = 0 if (0 < i .and. i <= process%meta%n_components) then if (process%component(i)%active) then associate (core => process%core_entry(i_core)%core) associate (data => core%data) if (size (p) == data%n_in + data%n_out & .and. 0 < f .and. f <= data%n_flv & .and. 0 < h .and. h <= data%n_hel & .and. 0 < c .and. c <= data%n_col) then if (present (fac_scale)) then fscale = fac_scale else fscale = sum (p(data%n_in+1:)) ** 1 end if if (present (ren_scale)) then rscale = ren_scale else rscale = fscale end if if (present (alpha_qcd_forced)) then if (allocated (alpha_qcd_forced)) & allocate (aqcd_forced, source = alpha_qcd_forced) end if amp = core%compute_amplitude (j, p, f, h, c, & fscale, rscale, aqcd_forced) end if end associate end associate else amp = 0 end if end if end function process_compute_amplitude @ %def process_compute_amplitude @ Sanity check for the process library. We abort the program if it has changed after process initialization. <>= procedure :: check_library_sanity => process_check_library_sanity <>= subroutine process_check_library_sanity (process) class(process_t), intent(in) :: process call process%env%check_lib_sanity (process%meta) end subroutine process_check_library_sanity @ %def process_check_library_sanity @ Reset the association to a process library. <>= procedure :: reset_library_ptr => process_reset_library_ptr <>= subroutine process_reset_library_ptr (process) class(process_t), intent(inout) :: process call process%env%reset_lib_ptr () end subroutine process_reset_library_ptr @ %def process_reset_library_ptr @ <>= procedure :: set_component_type => process_set_component_type <>= subroutine process_set_component_type (process, i_component, i_type) class(process_t), intent(inout) :: process integer, intent(in) :: i_component, i_type process%component(i_component)%component_type = i_type end subroutine process_set_component_type @ %def process_set_component_type @ <>= procedure :: set_counter_mci_entry => process_set_counter_mci_entry <>= subroutine process_set_counter_mci_entry (process, i_mci, counter) class(process_t), intent(inout) :: process integer, intent(in) :: i_mci type(process_counter_t), intent(in) :: counter process%mci_entry(i_mci)%counter = counter end subroutine process_set_counter_mci_entry @ %def process_set_counter_mci_entry @ This is for suppression of numerical noise in the integration results stored in the [[process_mci_entry]] type. As the error and efficiency enter the MD5 sum, we recompute it. <>= procedure :: pacify => process_pacify <>= subroutine process_pacify (process, efficiency_reset, error_reset) class(process_t), intent(inout) :: process logical, intent(in), optional :: efficiency_reset, error_reset logical :: eff_reset, err_reset integer :: i eff_reset = .false. err_reset = .false. if (present (efficiency_reset)) eff_reset = efficiency_reset if (present (error_reset)) err_reset = error_reset if (allocated (process%mci_entry)) then do i = 1, size (process%mci_entry) call process%mci_entry(i)%results%pacify (efficiency_reset) if (allocated (process%mci_entry(i)%mci)) then associate (mci => process%mci_entry(i)%mci) if (process%mci_entry(i)%mci%error_known & .and. err_reset) & mci%error = 0 if (process%mci_entry(i)%mci%efficiency_known & .and. eff_reset) & mci%efficiency = 1 call mci%pacify (efficiency_reset, error_reset) call mci%compute_md5sum () end associate end if end do end if end subroutine process_pacify @ %def process_pacify @ The following methods are used only in the unit tests; the access process internals directly that would otherwise be hidden. <>= procedure :: test_allocate_sf_channels procedure :: test_set_component_sf_channel procedure :: test_get_mci_ptr <>= subroutine test_allocate_sf_channels (process, n) class(process_t), intent(inout) :: process integer, intent(in) :: n call process%beam_config%allocate_sf_channels (n) end subroutine test_allocate_sf_channels subroutine test_set_component_sf_channel (process, c) class(process_t), intent(inout) :: process integer, dimension(:), intent(in) :: c call process%component(1)%phs_config%set_sf_channel (c) end subroutine test_set_component_sf_channel subroutine test_get_mci_ptr (process, mci) class(process_t), intent(in), target :: process class(mci_t), intent(out), pointer :: mci mci => process%mci_entry(1)%mci end subroutine test_get_mci_ptr @ %def test_allocate_sf_channels @ %def test_set_component_sf_channel @ %def test_get_mci_ptr @ <>= procedure :: init_mci_work => process_init_mci_work <>= subroutine process_init_mci_work (process, mci_work, i) class(process_t), intent(in), target :: process type(mci_work_t), intent(out) :: mci_work integer, intent(in) :: i call mci_work%init (process%mci_entry(i)) end subroutine process_init_mci_work @ %def process_init_mci_work @ Prepare the process core with type [[test_me]], or otherwise the externally provided [[type_string]] version. The toy dispatchers as a procedure argument come handy, knowing that we need to support only the [[test_me]] and [[template]] matrix-element types. <>= procedure :: setup_test_cores => process_setup_test_cores <>= subroutine process_setup_test_cores (process, type_string) class(process_t), intent(inout) :: process class(prc_core_t), allocatable :: core type(string_t), intent(in), optional :: type_string if (present (type_string)) then select case (char (type_string)) case ("template") call process%setup_cores (dispatch_template_core) case ("test_me") call process%setup_cores (dispatch_test_me_core) case default call msg_bug ("process setup test cores: unsupported type string") end select else call process%setup_cores (dispatch_test_me_core) end if end subroutine process_setup_test_cores subroutine dispatch_test_me_core (core, core_def, model, & helicity_selection, qcd, use_color_factors, has_beam_pol) use prc_test_core, only: test_t class(prc_core_t), allocatable, intent(inout) :: core class(prc_core_def_t), intent(in) :: core_def class(model_data_t), intent(in), target, optional :: model type(helicity_selection_t), intent(in), optional :: helicity_selection type(qcd_t), intent(in), optional :: qcd logical, intent(in), optional :: use_color_factors logical, intent(in), optional :: has_beam_pol allocate (test_t :: core) end subroutine dispatch_test_me_core subroutine dispatch_template_core (core, core_def, model, & helicity_selection, qcd, use_color_factors, has_beam_pol) use prc_template_me, only: prc_template_me_t class(prc_core_t), allocatable, intent(inout) :: core class(prc_core_def_t), intent(in) :: core_def class(model_data_t), intent(in), target, optional :: model type(helicity_selection_t), intent(in), optional :: helicity_selection type(qcd_t), intent(in), optional :: qcd logical, intent(in), optional :: use_color_factors logical, intent(in), optional :: has_beam_pol allocate (prc_template_me_t :: core) select type (core) type is (prc_template_me_t) call core%set_parameters (model) end select end subroutine dispatch_template_core @ %def process_setup_test_cores @ <>= procedure :: get_connected_states => process_get_connected_states <>= function process_get_connected_states (process, i_component, & connected_terms) result (connected) type(connected_state_t), dimension(:), allocatable :: connected class(process_t), intent(in) :: process integer, intent(in) :: i_component type(connected_state_t), dimension(:), intent(in) :: connected_terms integer :: i, i_conn integer :: n_conn n_conn = 0 do i = 1, process%get_n_terms () if (process%term(i)%i_component == i_component) then n_conn = n_conn + 1 end if end do allocate (connected (n_conn)) i_conn = 1 do i = 1, process%get_n_terms () if (process%term(i)%i_component == i_component) then connected (i_conn) = connected_terms(i) i_conn = i_conn + 1 end if end do end function process_get_connected_states @ %def process_get_connected_states @ \subsection{NLO specifics} These subroutines (and the NLO specific properties they work on) could potentially be moved to [[pcm_nlo_t]] and used more generically in [[process_t]] with an appropriate interface in [[pcm_t]] TODO wk 2018: This is used only by event initialization, which deals with an incomplete process object. <>= procedure :: init_nlo_settings => process_init_nlo_settings <>= subroutine process_init_nlo_settings (process, var_list) class(process_t), intent(inout) :: process type(var_list_t), intent(in), target :: var_list select type (pcm => process%pcm) type is (pcm_nlo_t) call pcm%init_nlo_settings (var_list) if (debug_active (D_SUBTRACTION) .or. debug_active (D_VIRTUAL)) & call pcm%settings%write () class default call msg_fatal ("Attempt to set nlo_settings with a non-NLO pcm!") end select end subroutine process_init_nlo_settings @ %def process_init_nlo_settings @ <>= generic :: get_nlo_type_component => get_nlo_type_component_single procedure :: get_nlo_type_component_single => process_get_nlo_type_component_single <>= elemental function process_get_nlo_type_component_single (process, i_component) result (val) integer :: val class(process_t), intent(in) :: process integer, intent(in) :: i_component val = process%component(i_component)%get_nlo_type () end function process_get_nlo_type_component_single @ %def process_get_nlo_type_component_single @ <>= generic :: get_nlo_type_component => get_nlo_type_component_all procedure :: get_nlo_type_component_all => process_get_nlo_type_component_all <>= pure function process_get_nlo_type_component_all (process) result (val) integer, dimension(:), allocatable :: val class(process_t), intent(in) :: process allocate (val (size (process%component))) val = process%component%get_nlo_type () end function process_get_nlo_type_component_all @ %def process_get_nlo_type_component_all @ <>= procedure :: is_nlo_calculation => process_is_nlo_calculation <>= function process_is_nlo_calculation (process) result (nlo) logical :: nlo class(process_t), intent(in) :: process select type (pcm => process%pcm) type is (pcm_nlo_t) nlo = .true. class default nlo = .false. end select end function process_is_nlo_calculation @ %def process_is_nlo_calculation @ <>= procedure :: get_negative_sf => process_get_negative_sf <>= function process_get_negative_sf (process) result (neg_sf) logical :: neg_sf class(process_t), intent(in) :: process neg_sf = process%config%process_def%get_negative_sf () end function process_get_negative_sf @ %def process_get_negative_sf @ <>= procedure :: is_combined_nlo_integration & => process_is_combined_nlo_integration <>= function process_is_combined_nlo_integration (process) result (combined) logical :: combined class(process_t), intent(in) :: process select type (pcm => process%pcm) type is (pcm_nlo_t) combined = pcm%settings%combined_integration class default combined = .false. end select end function process_is_combined_nlo_integration @ %def process_is_combined_nlo_integration @ <>= procedure :: component_is_real_finite => process_component_is_real_finite <>= pure function process_component_is_real_finite (process, i_component) & result (val) logical :: val class(process_t), intent(in) :: process integer, intent(in) :: i_component val = process%component(i_component)%component_type == COMP_REAL_FIN end function process_component_is_real_finite @ %def process_component_is_real_finite @ Return nlo data of a process component <>= procedure :: get_component_nlo_type => process_get_component_nlo_type <>= elemental function process_get_component_nlo_type (process, i_component) & result (nlo_type) integer :: nlo_type class(process_t), intent(in) :: process integer, intent(in) :: i_component nlo_type = process%component(i_component)%config%get_nlo_type () end function process_get_component_nlo_type @ %def process_get_component_nlo_type @ Return a pointer to the core that belongs to a component. <>= procedure :: get_component_core_ptr => process_get_component_core_ptr <>= function process_get_component_core_ptr (process, i_component) result (core) class(process_t), intent(in), target :: process integer, intent(in) :: i_component class(prc_core_t), pointer :: core integer :: i_core i_core = process%pcm%get_i_core(i_component) core => process%core_entry(i_core)%core end function process_get_component_core_ptr @ %def process_get_component_core_ptr @ <>= procedure :: get_component_associated_born & => process_get_component_associated_born <>= function process_get_component_associated_born (process, i_component) & result (i_born) class(process_t), intent(in) :: process integer, intent(in) :: i_component integer :: i_born i_born = process%component(i_component)%config%get_associated_born () end function process_get_component_associated_born @ %def process_get_component_associated_born @ <>= procedure :: get_first_real_component => process_get_first_real_component <>= function process_get_first_real_component (process) result (i_real) integer :: i_real class(process_t), intent(in) :: process i_real = process%component(1)%config%get_associated_real () end function process_get_first_real_component @ %def process_get_first_real_component @ <>= procedure :: get_first_real_term => process_get_first_real_term <>= function process_get_first_real_term (process) result (i_real) integer :: i_real class(process_t), intent(in) :: process integer :: i_component, i_term i_component = process%component(1)%config%get_associated_real () i_real = 0 do i_term = 1, size (process%term) if (process%term(i_term)%i_component == i_component) then i_real = i_term exit end if end do if (i_real == 0) call msg_fatal ("Did not find associated real term!") end function process_get_first_real_term @ %def process_get_first_real_term @ <>= procedure :: get_associated_real_fin => process_get_associated_real_fin <>= elemental function process_get_associated_real_fin (process, i_component) result (i_real) integer :: i_real class(process_t), intent(in) :: process integer, intent(in) :: i_component i_real = process%component(i_component)%config%get_associated_real_fin () end function process_get_associated_real_fin @ %def process_get_associated_real_fin @ <>= procedure :: select_i_term => process_select_i_term <>= pure function process_select_i_term (process, i_mci) result (i_term) integer :: i_term class(process_t), intent(in) :: process integer, intent(in) :: i_mci integer :: i_component, i_sub i_component = process%mci_entry(i_mci)%i_component(1) i_term = process%component(i_component)%i_term(1) i_sub = process%term(i_term)%i_sub if (i_sub > 0) & i_term = process%term(i_sub)%i_term_global end function process_select_i_term @ %def process_select_i_term @ Would be better to do this at the level of the writer of the core but one has to bring NLO information there. <>= procedure :: prepare_any_external_code & => process_prepare_any_external_code <>= subroutine process_prepare_any_external_code (process) class(process_t), intent(inout), target :: process integer :: i if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, & "process_prepare_external_code") associate (pcm => process%pcm) do i = 1, pcm%n_cores call pcm%prepare_any_external_code ( & process%core_entry(i), i, & process%get_library_name (), & process%config%model, & process%env%get_var_list_ptr ()) end do end associate end subroutine process_prepare_any_external_code @ %def process_prepare_any_external_code @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process config} <<[[process_config.f90]]>>= <> module process_config <> <> use format_utils, only: write_separator use io_units use md5 use os_interface use diagnostics use sf_base use sf_mappings use mappings, only: mapping_defaults_t use phs_forests, only: phs_parameters_t use sm_qcd use physics_defs use integration_results use model_data use models use interactions use quantum_numbers use flavors use helicities use colors use rng_base use state_matrices use process_libraries use process_constants use prc_core use prc_external use prc_openloops, only: prc_openloops_t use prc_threshold, only: prc_threshold_t use beams use dispatch_beams, only: dispatch_qcd use mci_base use beam_structures use phs_base use variables use expr_base use blha_olp_interfaces, only: prc_blha_t <> <> <> <> contains <> end module process_config @ %def process_config @ Identifiers for the NLO setup. <>= integer, parameter, public :: COMP_DEFAULT = 0 integer, parameter, public :: COMP_REAL_FIN = 1 integer, parameter, public :: COMP_MASTER = 2 integer, parameter, public :: COMP_VIRT = 3 integer, parameter, public :: COMP_REAL = 4 integer, parameter, public :: COMP_REAL_SING = 5 integer, parameter, public :: COMP_MISMATCH = 6 integer, parameter, public :: COMP_PDF = 7 integer, parameter, public :: COMP_SUB = 8 integer, parameter, public :: COMP_RESUM = 9 @ \subsection{Output selection flags} We declare a number of identifiers for write methods, so they only displays selected parts. The identifiers can be supplied to the [[vlist]] array argument of the standard F2008 derived-type writer call. <>= integer, parameter, public :: F_PACIFY = 1 integer, parameter, public :: F_SHOW_VAR_LIST = 11 integer, parameter, public :: F_SHOW_EXPRESSIONS = 12 integer, parameter, public :: F_SHOW_LIB = 13 integer, parameter, public :: F_SHOW_MODEL = 14 integer, parameter, public :: F_SHOW_QCD = 15 integer, parameter, public :: F_SHOW_OS_DATA = 16 integer, parameter, public :: F_SHOW_RNG = 17 integer, parameter, public :: F_SHOW_BEAMS = 18 @ %def SHOW_VAR_LIST @ %def SHOW_EXPRESSIONS @ This is a simple function that returns true if a flag value is present in [[v_list]], but not its negative. If neither is present, it returns [[default]]. <>= public :: flagged <>= function flagged (v_list, id, def) result (flag) logical :: flag integer, dimension(:), intent(in) :: v_list integer, intent(in) :: id logical, intent(in), optional :: def logical :: default_result default_result = .false.; if (present (def)) default_result = def if (default_result) then flag = all (v_list /= -id) else flag = all (v_list /= -id) .and. any (v_list == id) end if end function flagged @ %def flagged @ Related: if flag is set (unset), append [[value]] (its negative) to the [[v_list]], respectively. [[v_list]] must be allocated. <>= public :: set_flag <>= subroutine set_flag (v_list, value, flag) integer, dimension(:), intent(inout), allocatable :: v_list integer, intent(in) :: value logical, intent(in), optional :: flag if (present (flag)) then if (flag) then v_list = [v_list, value] else v_list = [v_list, -value] end if end if end subroutine set_flag @ %def set_flag @ \subsection{Generic configuration data} This information concerns physical and technical properties of the process. It is fixed upon initialization, using data from the process specification and the variable list. The number [[n_in]] is the number of incoming beam particles, simultaneously the number of incoming partons, 1 for a decay and 2 for a scattering process. (The number of outgoing partons may depend on the process component.) The number [[n_components]] is the number of components that constitute the current process. The number [[n_terms]] is the number of distinct contributions to the scattering matrix that constitute the current process. Each component may generate several terms. The number [[n_mci]] is the number of independent MC integration configurations that this process uses. Distinct process components that share a MCI configuration may be combined pointwise. (Nevertheless, a given MC variable set may correspond to several ``nearby'' kinematical configurations.) This is also the number of distinct sampling-function results that this process can generate. Process components that use distinct variable sets are added only once after an integration pass has completed. The [[model]] pointer identifies the physics model and its parameters. This is a pointer to an external object. Various [[parse_node_t]] objects are taken from the SINDARIN input. They encode expressions for evaluating cuts and scales. The workspaces for evaluating those expressions are set up in the [[effective_state]] subobjects. Note that these are really pointers, so the actual nodes are not stored inside the process object. The [[md5sum]] is taken and used to verify the process configuration when re-reading data from file. <>= public :: process_config_data_t <>= type :: process_config_data_t class(process_def_t), pointer :: process_def => null () integer :: n_in = 0 integer :: n_components = 0 integer :: n_terms = 0 integer :: n_mci = 0 type(string_t) :: model_name class(model_data_t), pointer :: model => null () type(qcd_t) :: qcd class(expr_factory_t), allocatable :: ef_cuts class(expr_factory_t), allocatable :: ef_scale class(expr_factory_t), allocatable :: ef_fac_scale class(expr_factory_t), allocatable :: ef_ren_scale class(expr_factory_t), allocatable :: ef_weight character(32) :: md5sum = "" contains <> end type process_config_data_t @ %def process_config_data_t @ Here, we may compress the expressions for cuts etc. <>= procedure :: write => process_config_data_write <>= subroutine process_config_data_write (config, u, counters, model, expressions) class(process_config_data_t), intent(in) :: config integer, intent(in) :: u logical, intent(in) :: counters logical, intent(in) :: model logical, intent(in) :: expressions write (u, "(1x,A)") "Configuration data:" if (counters) then write (u, "(3x,A,I0)") "Number of incoming particles = ", & config%n_in write (u, "(3x,A,I0)") "Number of process components = ", & config%n_components write (u, "(3x,A,I0)") "Number of process terms = ", & config%n_terms write (u, "(3x,A,I0)") "Number of MCI configurations = ", & config%n_mci end if if (associated (config%model)) then write (u, "(3x,A,A)") "Model = ", char (config%model_name) if (model) then call write_separator (u) call config%model%write (u) call write_separator (u) end if else write (u, "(3x,A,A,A)") "Model = ", char (config%model_name), & " [not associated]" end if call config%qcd%write (u, show_md5sum = .false.) call write_separator (u) if (expressions) then if (allocated (config%ef_cuts)) then call write_separator (u) write (u, "(3x,A)") "Cut expression:" call config%ef_cuts%write (u) end if if (allocated (config%ef_scale)) then call write_separator (u) write (u, "(3x,A)") "Scale expression:" call config%ef_scale%write (u) end if if (allocated (config%ef_fac_scale)) then call write_separator (u) write (u, "(3x,A)") "Factorization scale expression:" call config%ef_fac_scale%write (u) end if if (allocated (config%ef_ren_scale)) then call write_separator (u) write (u, "(3x,A)") "Renormalization scale expression:" call config%ef_ren_scale%write (u) end if if (allocated (config%ef_weight)) then call write_separator (u) write (u, "(3x,A)") "Weight expression:" call config%ef_weight%write (u) end if else call write_separator (u) write (u, "(3x,A)") "Expressions (cut, scales, weight): [not shown]" end if if (config%md5sum /= "") then call write_separator (u) write (u, "(3x,A,A,A)") "MD5 sum (config) = '", config%md5sum, "'" end if end subroutine process_config_data_write @ %def process_config_data_write @ Initialize. We use information from the process metadata and from the process library, given the process ID. We also store the currently active OS data set. The model pointer references the model data within the [[env]] record. That should be an instance of the global model. We initialize the QCD object, unless the environment information is unavailable (unit tests). The RNG factory object is imported by moving the allocation. <>= procedure :: init => process_config_data_init <>= subroutine process_config_data_init (config, meta, env) class(process_config_data_t), intent(out) :: config type(process_metadata_t), intent(in) :: meta type(process_environment_t), intent(in) :: env config%process_def => env%lib%get_process_def_ptr (meta%id) config%n_in = config%process_def%get_n_in () config%n_components = size (meta%component_id) config%model => env%get_model_ptr () config%model_name = config%model%get_name () if (env%got_var_list ()) then call dispatch_qcd & (config%qcd, env%get_var_list_ptr (), env%get_os_data ()) end if end subroutine process_config_data_init @ %def process_config_data_init @ Current implementation: nothing to finalize. <>= procedure :: final => process_config_data_final <>= subroutine process_config_data_final (config) class(process_config_data_t), intent(inout) :: config end subroutine process_config_data_final @ %def process_config_data_final @ Return a copy of the QCD data block. <>= procedure :: get_qcd => process_config_data_get_qcd <>= function process_config_data_get_qcd (config) result (qcd) class(process_config_data_t), intent(in) :: config type(qcd_t) :: qcd qcd = config%qcd end function process_config_data_get_qcd @ %def process_config_data_get_qcd @ Compute the MD5 sum of the configuration data. This encodes, in particular, the model and the expressions for cut, scales, weight, etc. It should not contain the IDs and number of components, etc., since the MD5 sum should be useful for integrating individual components. This is done only once. If the MD5 sum is nonempty, the calculation is skipped. <>= procedure :: compute_md5sum => process_config_data_compute_md5sum <>= subroutine process_config_data_compute_md5sum (config) class(process_config_data_t), intent(inout) :: config integer :: u if (config%md5sum == "") then u = free_unit () open (u, status = "scratch", action = "readwrite") call config%write (u, counters = .false., & model = .true., expressions = .true.) rewind (u) config%md5sum = md5sum (u) close (u) end if end subroutine process_config_data_compute_md5sum @ %def process_config_data_compute_md5sum @ <>= procedure :: get_md5sum => process_config_data_get_md5sum <>= pure function process_config_data_get_md5sum (config) result (md5) character(32) :: md5 class(process_config_data_t), intent(in) :: config md5 = config%md5sum end function process_config_data_get_md5sum @ %def process_config_data_get_md5sum @ \subsection{Environment} This record stores a snapshot of the process environment at the point where the process object is created. Model and variable list are implemented as pointer, so they always have the [[target]] attribute. For unit-testing purposes, setting the var list is optional. If not set, the pointer is null. <>= public :: process_environment_t <>= type :: process_environment_t private type(model_t), pointer :: model => null () type(var_list_t), pointer :: var_list => null () logical :: var_list_is_set = .false. type(process_library_t), pointer :: lib => null () type(beam_structure_t) :: beam_structure type(os_data_t) :: os_data contains <> end type process_environment_t @ %def process_environment_t @ Model and local var list are snapshots and need a finalizer. <>= procedure :: final => process_environment_final <>= subroutine process_environment_final (env) class(process_environment_t), intent(inout) :: env if (associated (env%model)) then call env%model%final () deallocate (env%model) end if if (associated (env%var_list)) then call env%var_list%final (follow_link=.true.) deallocate (env%var_list) end if end subroutine process_environment_final @ %def process_environment_final @ Output, DTIO compatible. <>= procedure :: write => process_environment_write procedure :: write_formatted => process_environment_write_formatted ! generic :: write (formatted) => write_formatted <>= subroutine process_environment_write (env, unit, & show_var_list, show_model, show_lib, show_beams, show_os_data) class(process_environment_t), intent(in) :: env integer, intent(in), optional :: unit logical, intent(in), optional :: show_var_list logical, intent(in), optional :: show_model logical, intent(in), optional :: show_lib logical, intent(in), optional :: show_beams logical, intent(in), optional :: show_os_data integer :: u, iostat integer, dimension(:), allocatable :: v_list character(0) :: iomsg u = given_output_unit (unit) allocate (v_list (0)) call set_flag (v_list, F_SHOW_VAR_LIST, show_var_list) call set_flag (v_list, F_SHOW_MODEL, show_model) call set_flag (v_list, F_SHOW_LIB, show_lib) call set_flag (v_list, F_SHOW_BEAMS, show_beams) call set_flag (v_list, F_SHOW_OS_DATA, show_os_data) call env%write_formatted (u, "LISTDIRECTED", v_list, iostat, iomsg) end subroutine process_environment_write @ %def process_environment_write @ DTIO standard write. <>= subroutine process_environment_write_formatted & (dtv, unit, iotype, v_list, iostat, iomsg) class(process_environment_t), intent(in) :: dtv integer, intent(in) :: unit character(*), intent(in) :: iotype integer, dimension(:), intent(in) :: v_list integer, intent(out) :: iostat character(*), intent(inout) :: iomsg associate (env => dtv) if (flagged (v_list, F_SHOW_VAR_LIST, .true.)) then write (unit, "(1x,A)") "Variable list:" if (associated (env%var_list)) then call write_separator (unit) call env%var_list%write (unit) else write (unit, "(3x,A)") "[not allocated]" end if call write_separator (unit) end if if (flagged (v_list, F_SHOW_MODEL, .true.)) then write (unit, "(1x,A)") "Model:" if (associated (env%model)) then call write_separator (unit) call env%model%write (unit) else write (unit, "(3x,A)") "[not allocated]" end if call write_separator (unit) end if if (flagged (v_list, F_SHOW_LIB, .true.)) then write (unit, "(1x,A)") "Process library:" if (associated (env%lib)) then call write_separator (unit) call env%lib%write (unit) else write (unit, "(3x,A)") "[not allocated]" end if end if if (flagged (v_list, F_SHOW_BEAMS, .true.)) then call write_separator (unit) call env%beam_structure%write (unit) end if if (flagged (v_list, F_SHOW_OS_DATA, .true.)) then write (unit, "(1x,A)") "Operating-system data:" call write_separator (unit) call env%os_data%write (unit) end if end associate iostat = 0 end subroutine process_environment_write_formatted @ %def process_environment_write_formatted @ Initialize: Make a snapshot of the provided model. Make a link to the current process library. Also make a snapshot of the variable list, if provided. If none is provided, there is an empty variable list nevertheless, so a pointer lookup does not return null. If no beam structure is provided, the beam-structure member is empty and will yield a number of zero beams when queried. <>= procedure :: init => process_environment_init <>= subroutine process_environment_init & (env, model, lib, os_data, var_list, beam_structure) class(process_environment_t), intent(out) :: env type(model_t), intent(in), target :: model type(process_library_t), intent(in), target :: lib type(os_data_t), intent(in) :: os_data type(var_list_t), intent(in), target, optional :: var_list type(beam_structure_t), intent(in), optional :: beam_structure allocate (env%model) call env%model%init_instance (model) env%lib => lib env%os_data = os_data allocate (env%var_list) if (present (var_list)) then call env%var_list%init_snapshot (var_list, follow_link=.true.) env%var_list_is_set = .true. end if if (present (beam_structure)) then env%beam_structure = beam_structure end if end subroutine process_environment_init @ %def process_environment_init @ Indicate whether a variable list has been provided upon initialization. <>= procedure :: got_var_list => process_environment_got_var_list <>= function process_environment_got_var_list (env) result (flag) class(process_environment_t), intent(in) :: env logical :: flag flag = env%var_list_is_set end function process_environment_got_var_list @ %def process_environment_got_var_list @ Return a pointer to the variable list. <>= procedure :: get_var_list_ptr => process_environment_get_var_list_ptr <>= function process_environment_get_var_list_ptr (env) result (var_list) class(process_environment_t), intent(in) :: env type(var_list_t), pointer :: var_list var_list => env%var_list end function process_environment_get_var_list_ptr @ %def process_environment_get_var_list_ptr @ Return a pointer to the model, if it exists. <>= procedure :: get_model_ptr => process_environment_get_model_ptr <>= function process_environment_get_model_ptr (env) result (model) class(process_environment_t), intent(in) :: env type(model_t), pointer :: model model => env%model end function process_environment_get_model_ptr @ %def process_environment_get_model_ptr @ Return the process library pointer. <>= procedure :: get_lib_ptr => process_environment_get_lib_ptr <>= function process_environment_get_lib_ptr (env) result (lib) class(process_environment_t), intent(inout) :: env type(process_library_t), pointer :: lib lib => env%lib end function process_environment_get_lib_ptr @ %def process_environment_get_lib_ptr @ Clear the process library pointer, in case the library is deleted. <>= procedure :: reset_lib_ptr => process_environment_reset_lib_ptr <>= subroutine process_environment_reset_lib_ptr (env) class(process_environment_t), intent(inout) :: env env%lib => null () end subroutine process_environment_reset_lib_ptr @ %def process_environment_reset_lib_ptr @ Check whether the process library has changed, in case the library is recompiled, etc. <>= procedure :: check_lib_sanity => process_environment_check_lib_sanity <>= subroutine process_environment_check_lib_sanity (env, meta) class(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta if (associated (env%lib)) then if (env%lib%get_update_counter () /= meta%lib_update_counter) then call msg_fatal ("Process '" // char (meta%id) & // "': library has been recompiled after integration") end if end if end subroutine process_environment_check_lib_sanity @ %def process_environment_check_lib_sanity @ Fill the [[data]] block using the appropriate process-library access entry. <>= procedure :: fill_process_constants => & process_environment_fill_process_constants <>= subroutine process_environment_fill_process_constants & (env, id, i_component, data) class(process_environment_t), intent(in) :: env type(string_t), intent(in) :: id integer, intent(in) :: i_component type(process_constants_t), intent(out) :: data call env%lib%fill_constants (id, i_component, data) end subroutine process_environment_fill_process_constants @ %def process_environment_fill_process_constants @ Return the entire beam structure. <>= procedure :: get_beam_structure => process_environment_get_beam_structure <>= function process_environment_get_beam_structure (env) result (beam_structure) class(process_environment_t), intent(in) :: env type(beam_structure_t) :: beam_structure beam_structure = env%beam_structure end function process_environment_get_beam_structure @ %def process_environment_get_beam_structure @ Check the beam structure for PDFs. <>= procedure :: has_pdfs => process_environment_has_pdfs <>= function process_environment_has_pdfs (env) result (flag) class(process_environment_t), intent(in) :: env logical :: flag flag = env%beam_structure%has_pdf () end function process_environment_has_pdfs @ %def process_environment_has_pdfs @ Check the beam structure for polarized beams. <>= procedure :: has_polarized_beams => process_environment_has_polarized_beams <>= function process_environment_has_polarized_beams (env) result (flag) class(process_environment_t), intent(in) :: env logical :: flag flag = env%beam_structure%has_polarized_beams () end function process_environment_has_polarized_beams @ %def process_environment_has_polarized_beams @ Return a copy of the OS data block. <>= procedure :: get_os_data => process_environment_get_os_data <>= function process_environment_get_os_data (env) result (os_data) class(process_environment_t), intent(in) :: env type(os_data_t) :: os_data os_data = env%os_data end function process_environment_get_os_data @ %def process_environment_get_os_data @ \subsection{Metadata} This information describes the process. It is fixed upon initialization. The [[id]] string is the name of the process object, as given by the user. The matrix element generator will use this string for naming Fortran procedures and types, so it should qualify as a Fortran name. The [[num_id]] is meaningful if nonzero. It is used for communication with external programs or file standards which do not support string IDs. The [[run_id]] string distinguishes among several runs for the same process. It identifies process instances with respect to adapted integration grids and similar run-specific data. The run ID is kept when copying processes for creating instances, however, so it does not distinguish event samples. The [[lib_name]] identifies the process library where the process definition and the process driver are located. The [[lib_index]] is the index of entry in the process library that corresponds to the current process. The [[component_id]] array identifies the individual process components. The [[component_description]] is an array of human-readable strings that characterize the process components, for instance [[a, b => c, d]]. The [[active]] mask array marks those components which are active. The others are skipped. <>= public :: process_metadata_t <>= type :: process_metadata_t integer :: type = PRC_UNKNOWN type(string_t) :: id integer :: num_id = 0 type(string_t) :: run_id type(string_t), allocatable :: lib_name integer :: lib_update_counter = 0 integer :: lib_index = 0 integer :: n_components = 0 type(string_t), dimension(:), allocatable :: component_id type(string_t), dimension(:), allocatable :: component_description logical, dimension(:), allocatable :: active contains <> end type process_metadata_t @ %def process_metadata_t @ Output: ID and run ID. We write the variable list only upon request. <>= procedure :: write => process_metadata_write <>= subroutine process_metadata_write (meta, u, screen) class(process_metadata_t), intent(in) :: meta integer, intent(in) :: u logical, intent(in) :: screen integer :: i select case (meta%type) case (PRC_UNKNOWN) if (screen) then write (msg_buffer, "(A)") "Process [undefined]" else write (u, "(1x,A)") "Process [undefined]" end if return case (PRC_DECAY) if (screen) then write (msg_buffer, "(A,1x,A,A,A)") "Process [decay]:", & "'", char (meta%id), "'" else write (u, "(1x,A)", advance="no") "Process [decay]:" end if case (PRC_SCATTERING) if (screen) then write (msg_buffer, "(A,1x,A,A,A)") "Process [scattering]:", & "'", char (meta%id), "'" else write (u, "(1x,A)", advance="no") "Process [scattering]:" end if case default call msg_bug ("process_write: undefined process type") end select if (screen) then call msg_message () else write (u, "(1x,A,A,A)") "'", char (meta%id), "'" end if if (meta%num_id /= 0) then if (screen) then write (msg_buffer, "(2x,A,I0)") "ID (num) = ", meta%num_id call msg_message () else write (u, "(3x,A,I0)") "ID (num) = ", meta%num_id end if end if if (screen) then if (meta%run_id /= "") then write (msg_buffer, "(2x,A,A,A)") "Run ID = '", & char (meta%run_id), "'" call msg_message () end if else write (u, "(3x,A,A,A)") "Run ID = '", char (meta%run_id), "'" end if if (allocated (meta%lib_name)) then if (screen) then write (msg_buffer, "(2x,A,A,A)") "Library name = '", & char (meta%lib_name), "'" call msg_message () else write (u, "(3x,A,A,A)") "Library name = '", & char (meta%lib_name), "'" end if else if (screen) then write (msg_buffer, "(2x,A)") "Library name = [not associated]" call msg_message () else write (u, "(3x,A)") "Library name = [not associated]" end if end if if (screen) then write (msg_buffer, "(2x,A,I0)") "Process index = ", meta%lib_index call msg_message () else write (u, "(3x,A,I0)") "Process index = ", meta%lib_index end if if (allocated (meta%component_id)) then if (screen) then if (any (meta%active)) then write (msg_buffer, "(2x,A)") "Process components:" else write (msg_buffer, "(2x,A)") "Process components: [none]" end if call msg_message () else write (u, "(3x,A)") "Process components:" end if do i = 1, size (meta%component_id) if (.not. meta%active(i)) cycle if (screen) then write (msg_buffer, "(4x,I0,9A)") i, ": '", & char (meta%component_id (i)), "': ", & char (meta%component_description (i)) call msg_message () else write (u, "(5x,I0,9A)") i, ": '", & char (meta%component_id (i)), "': ", & char (meta%component_description (i)) end if end do end if if (screen) then write (msg_buffer, "(A)") repeat ("-", 72) call msg_message () else call write_separator (u) end if end subroutine process_metadata_write @ %def process_metadata_write @ Short output: list components. <>= procedure :: show => process_metadata_show <>= subroutine process_metadata_show (meta, u, model_name) class(process_metadata_t), intent(in) :: meta integer, intent(in) :: u type(string_t), intent(in) :: model_name integer :: i select case (meta%type) case (PRC_UNKNOWN) write (u, "(A)") "Process: [undefined]" return case default write (u, "(A)", advance="no") "Process:" end select write (u, "(1x,A)", advance="no") char (meta%id) select case (meta%num_id) case (0) case default write (u, "(1x,'(',I0,')')", advance="no") meta%num_id end select select case (char (model_name)) case ("") case default write (u, "(1x,'[',A,']')", advance="no") char (model_name) end select write (u, *) if (allocated (meta%component_id)) then do i = 1, size (meta%component_id) if (meta%active(i)) then write (u, "(2x,I0,':',1x,A)") i, & char (meta%component_description (i)) end if end do end if end subroutine process_metadata_show @ %def process_metadata_show @ Initialize. Find process ID and run ID. Also find the process ID in the process library and retrieve some metadata from there. <>= procedure :: init => process_metadata_init <>= subroutine process_metadata_init (meta, id, lib, var_list) class(process_metadata_t), intent(out) :: meta type(string_t), intent(in) :: id type(process_library_t), intent(in), target :: lib type(var_list_t), intent(in) :: var_list select case (lib%get_n_in (id)) case (1); meta%type = PRC_DECAY case (2); meta%type = PRC_SCATTERING case default call msg_bug ("Process '" // char (id) // "': impossible n_in") end select meta%id = id meta%run_id = var_list%get_sval (var_str ("$run_id")) allocate (meta%lib_name) meta%lib_name = lib%get_name () meta%lib_update_counter = lib%get_update_counter () if (lib%contains (id)) then meta%lib_index = lib%get_entry_index (id) meta%num_id = lib%get_num_id (id) call lib%get_component_list (id, meta%component_id) meta%n_components = size (meta%component_id) call lib%get_component_description_list & (id, meta%component_description) allocate (meta%active (meta%n_components), source = .true.) else call msg_fatal ("Process library does not contain process '" & // char (id) // "'") end if if (.not. lib%is_active ()) then call msg_bug ("Process init: inactive library not handled yet") end if end subroutine process_metadata_init @ %def process_metadata_init @ Mark a component as inactive. <>= procedure :: deactivate_component => process_metadata_deactivate_component <>= subroutine process_metadata_deactivate_component (meta, i) class(process_metadata_t), intent(inout) :: meta integer, intent(in) :: i call msg_message ("Process component '" & // char (meta%component_id(i)) // "': matrix element vanishes") meta%active(i) = .false. end subroutine process_metadata_deactivate_component @ %def process_metadata_deactivate_component @ \subsection{Phase-space configuration} A process can have a number of independent phase-space configuration entries, depending on the process definition and evaluation algorithm. Each entry holds various configuration-parameter data and the actual [[phs_config_t]] record, which can vary in concrete type. <>= public :: process_phs_config_t <>= type :: process_phs_config_t type(phs_parameters_t) :: phs_par type(mapping_defaults_t) :: mapping_defs class(phs_config_t), allocatable :: phs_config contains <> end type process_phs_config_t @ %def process_phs_config_t @ Output, DTIO compatible. <>= procedure :: write => process_phs_config_write procedure :: write_formatted => process_phs_config_write_formatted ! generic :: write (formatted) => write_formatted <>= subroutine process_phs_config_write (phs_config, unit) class(process_phs_config_t), intent(in) :: phs_config integer, intent(in), optional :: unit integer :: u, iostat integer, dimension(:), allocatable :: v_list character(0) :: iomsg u = given_output_unit (unit) allocate (v_list (0)) call phs_config%write_formatted (u, "LISTDIRECTED", v_list, iostat, iomsg) end subroutine process_phs_config_write @ %def process_phs_config_write @ DTIO standard write. <>= subroutine process_phs_config_write_formatted & (dtv, unit, iotype, v_list, iostat, iomsg) class(process_phs_config_t), intent(in) :: dtv integer, intent(in) :: unit character(*), intent(in) :: iotype integer, dimension(:), intent(in) :: v_list integer, intent(out) :: iostat character(*), intent(inout) :: iomsg associate (phs_config => dtv) write (unit, "(1x, A)") "Phase-space configuration entry:" call phs_config%phs_par%write (unit) call phs_config%mapping_defs%write (unit) end associate iostat = 0 end subroutine process_phs_config_write_formatted @ %def process_phs_config_write_formatted @ \subsection{Beam configuration} The object [[data]] holds all details about the initial beam configuration. The allocatable array [[sf]] holds the structure-function configuration blocks. There are [[n_strfun]] entries in the structure-function chain (not counting the initial beam object). We maintain [[n_channel]] independent parameterizations of this chain. If this is greater than zero, we need a multi-channel sampling algorithm, where for each point one channel is selected to generate kinematics. The number of parameters that are required for generating a structure-function chain is [[n_sfpar]]. The flag [[azimuthal_dependence]] tells whether the process setup is symmetric about the beam axis in the c.m.\ system. This implies that there is no transversal beam polarization. The flag [[lab_is_cm]] is obvious. <>= public :: process_beam_config_t <>= type :: process_beam_config_t type(beam_data_t) :: data integer :: n_strfun = 0 integer :: n_channel = 1 integer :: n_sfpar = 0 type(sf_config_t), dimension(:), allocatable :: sf type(sf_channel_t), dimension(:), allocatable :: sf_channel logical :: azimuthal_dependence = .false. logical :: lab_is_cm = .true. character(32) :: md5sum = "" logical :: sf_trace = .false. type(string_t) :: sf_trace_file contains <> end type process_beam_config_t @ %def process_beam_config_t @ Here we write beam data only if they are actually used. The [[verbose]] flag is passed to the beam-data writer. <>= procedure :: write => process_beam_config_write <>= subroutine process_beam_config_write (object, unit, verbose) class(process_beam_config_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: verbose integer :: u, i, c u = given_output_unit (unit) call object%data%write (u, verbose = verbose) if (object%data%initialized) then write (u, "(3x,A,L1)") "Azimuthal dependence = ", & object%azimuthal_dependence write (u, "(3x,A,L1)") "Lab frame is c.m. frame = ", & object%lab_is_cm if (object%md5sum /= "") then write (u, "(3x,A,A,A)") "MD5 sum (beams/strf) = '", & object%md5sum, "'" end if if (allocated (object%sf)) then do i = 1, size (object%sf) call object%sf(i)%write (u) end do if (any_sf_channel_has_mapping (object%sf_channel)) then write (u, "(1x,A,L1)") "Structure-function mappings per channel:" do c = 1, object%n_channel write (u, "(3x,I0,':')", advance="no") c call object%sf_channel(c)%write (u) end do end if end if end if end subroutine process_beam_config_write @ %def process_beam_config_write @ The beam data have a finalizer. We assume that there is none for the structure-function data. <>= procedure :: final => process_beam_config_final <>= subroutine process_beam_config_final (object) class(process_beam_config_t), intent(inout) :: object call object%data%final () end subroutine process_beam_config_final @ %def process_beam_config_final @ Initialize the beam setup with a given beam structure object. <>= procedure :: init_beam_structure => process_beam_config_init_beam_structure <>= subroutine process_beam_config_init_beam_structure & (beam_config, beam_structure, sqrts, model, decay_rest_frame) class(process_beam_config_t), intent(out) :: beam_config type(beam_structure_t), intent(in) :: beam_structure logical, intent(in), optional :: decay_rest_frame real(default), intent(in) :: sqrts class(model_data_t), intent(in), target :: model call beam_config%data%init_structure (beam_structure, & sqrts, model, decay_rest_frame) beam_config%lab_is_cm = beam_config%data%lab_is_cm end subroutine process_beam_config_init_beam_structure @ %def process_beam_config_init_beam_structure @ Initialize the beam setup for a scattering process with specified flavor combination, other properties taken from the beam structure object (if any). <>= procedure :: init_scattering => process_beam_config_init_scattering <>= subroutine process_beam_config_init_scattering & (beam_config, flv_in, sqrts, beam_structure) class(process_beam_config_t), intent(out) :: beam_config type(flavor_t), dimension(2), intent(in) :: flv_in real(default), intent(in) :: sqrts type(beam_structure_t), intent(in), optional :: beam_structure if (present (beam_structure)) then if (beam_structure%polarized ()) then call beam_config%data%init_sqrts (sqrts, flv_in, & beam_structure%get_smatrix (), beam_structure%get_pol_f ()) else call beam_config%data%init_sqrts (sqrts, flv_in) end if else call beam_config%data%init_sqrts (sqrts, flv_in) end if end subroutine process_beam_config_init_scattering @ %def process_beam_config_init_scattering @ Initialize the beam setup for a decay process with specified flavor, other properties taken from the beam structure object (if present). For a cascade decay, we set [[rest_frame]] to false, indicating a event-wise varying momentum. The beam data itself are initialized for the particle at rest. <>= procedure :: init_decay => process_beam_config_init_decay <>= subroutine process_beam_config_init_decay & (beam_config, flv_in, rest_frame, beam_structure) class(process_beam_config_t), intent(out) :: beam_config type(flavor_t), dimension(1), intent(in) :: flv_in logical, intent(in), optional :: rest_frame type(beam_structure_t), intent(in), optional :: beam_structure if (present (beam_structure)) then if (beam_structure%polarized ()) then call beam_config%data%init_decay (flv_in, & beam_structure%get_smatrix (), beam_structure%get_pol_f (), & rest_frame = rest_frame) else call beam_config%data%init_decay (flv_in, rest_frame = rest_frame) end if else call beam_config%data%init_decay (flv_in, & rest_frame = rest_frame) end if beam_config%lab_is_cm = beam_config%data%lab_is_cm end subroutine process_beam_config_init_decay @ %def process_beam_config_init_decay @ Print an informative message. <>= procedure :: startup_message => process_beam_config_startup_message <>= subroutine process_beam_config_startup_message & (beam_config, unit, beam_structure) class(process_beam_config_t), intent(in) :: beam_config integer, intent(in), optional :: unit type(beam_structure_t), intent(in), optional :: beam_structure integer :: u u = free_unit () open (u, status="scratch", action="readwrite") if (present (beam_structure)) then call beam_structure%write (u) end if call beam_config%data%write (u) rewind (u) do read (u, "(1x,A)", end=1) msg_buffer call msg_message () end do 1 continue close (u) end subroutine process_beam_config_startup_message @ %def process_beam_config_startup_message @ Allocate the structure-function array. <>= procedure :: init_sf_chain => process_beam_config_init_sf_chain <>= subroutine process_beam_config_init_sf_chain & (beam_config, sf_config, sf_trace_file) class(process_beam_config_t), intent(inout) :: beam_config type(sf_config_t), dimension(:), intent(in) :: sf_config type(string_t), intent(in), optional :: sf_trace_file integer :: i beam_config%n_strfun = size (sf_config) allocate (beam_config%sf (beam_config%n_strfun)) do i = 1, beam_config%n_strfun associate (sf => sf_config(i)) call beam_config%sf(i)%init (sf%i, sf%data) if (.not. sf%data%is_generator ()) then beam_config%n_sfpar = beam_config%n_sfpar + sf%data%get_n_par () end if end associate end do if (present (sf_trace_file)) then beam_config%sf_trace = .true. beam_config%sf_trace_file = sf_trace_file end if end subroutine process_beam_config_init_sf_chain @ %def process_beam_config_init_sf_chain @ Allocate the structure-function mapping channel array, given the requested number of channels. <>= procedure :: allocate_sf_channels => process_beam_config_allocate_sf_channels <>= subroutine process_beam_config_allocate_sf_channels (beam_config, n_channel) class(process_beam_config_t), intent(inout) :: beam_config integer, intent(in) :: n_channel beam_config%n_channel = n_channel call allocate_sf_channels (beam_config%sf_channel, & n_channel = n_channel, & n_strfun = beam_config%n_strfun) end subroutine process_beam_config_allocate_sf_channels @ %def process_beam_config_allocate_sf_channels @ Set a structure-function mapping channel for an array of structure-function entries, for a single channel. (The default is no mapping.) <>= procedure :: set_sf_channel => process_beam_config_set_sf_channel <>= subroutine process_beam_config_set_sf_channel (beam_config, c, sf_channel) class(process_beam_config_t), intent(inout) :: beam_config integer, intent(in) :: c type(sf_channel_t), intent(in) :: sf_channel beam_config%sf_channel(c) = sf_channel end subroutine process_beam_config_set_sf_channel @ %def process_beam_config_set_sf_channel @ Print an informative startup message. <>= procedure :: sf_startup_message => process_beam_config_sf_startup_message <>= subroutine process_beam_config_sf_startup_message & (beam_config, sf_string, unit) class(process_beam_config_t), intent(in) :: beam_config type(string_t), intent(in) :: sf_string integer, intent(in), optional :: unit if (beam_config%n_strfun > 0) then call msg_message ("Beam structure: " // char (sf_string), unit = unit) write (msg_buffer, "(A,3(1x,I0,1x,A))") & "Beam structure:", & beam_config%n_channel, "channels,", & beam_config%n_sfpar, "dimensions" call msg_message (unit = unit) if (beam_config%sf_trace) then call msg_message ("Beam structure: tracing & &values in '" // char (beam_config%sf_trace_file) // "'") end if end if end subroutine process_beam_config_sf_startup_message @ %def process_beam_config_startup_message @ Return the PDF set currently in use, if any. This should be unique, so we scan the structure functions until we get a nonzero number. (This implies that if the PDF set is not unique (e.g., proton and photon structure used together), this does not work correctly.) <>= procedure :: get_pdf_set => process_beam_config_get_pdf_set <>= function process_beam_config_get_pdf_set (beam_config) result (pdf_set) class(process_beam_config_t), intent(in) :: beam_config integer :: pdf_set integer :: i pdf_set = 0 if (allocated (beam_config%sf)) then do i = 1, size (beam_config%sf) pdf_set = beam_config%sf(i)%get_pdf_set () if (pdf_set /= 0) return end do end if end function process_beam_config_get_pdf_set @ %def process_beam_config_get_pdf_set @ Return the beam file. <>= procedure :: get_beam_file => process_beam_config_get_beam_file <>= function process_beam_config_get_beam_file (beam_config) result (file) class(process_beam_config_t), intent(in) :: beam_config type(string_t) :: file integer :: i file = "" if (allocated (beam_config%sf)) then do i = 1, size (beam_config%sf) file = beam_config%sf(i)%get_beam_file () if (file /= "") return end do end if end function process_beam_config_get_beam_file @ %def process_beam_config_get_beam_file @ Compute the MD5 sum for the complete beam setup. We rely on the default output of [[write]] to contain all relevant data. This is done only once, when the MD5 sum is still empty. <>= procedure :: compute_md5sum => process_beam_config_compute_md5sum <>= subroutine process_beam_config_compute_md5sum (beam_config) class(process_beam_config_t), intent(inout) :: beam_config integer :: u if (beam_config%md5sum == "") then u = free_unit () open (u, status = "scratch", action = "readwrite") call beam_config%write (u, verbose=.true.) rewind (u) beam_config%md5sum = md5sum (u) close (u) end if end subroutine process_beam_config_compute_md5sum @ %def process_beam_config_compute_md5sum @ <>= procedure :: get_md5sum => process_beam_config_get_md5sum <>= pure function process_beam_config_get_md5sum (beam_config) result (md5) character(32) :: md5 class(process_beam_config_t), intent(in) :: beam_config md5 = beam_config%md5sum end function process_beam_config_get_md5sum @ %def process_beam_config_get_md5sum @ <>= procedure :: has_structure_function => process_beam_config_has_structure_function <>= pure function process_beam_config_has_structure_function (beam_config) result (has_sf) logical :: has_sf class(process_beam_config_t), intent(in) :: beam_config has_sf = beam_config%n_strfun > 0 end function process_beam_config_has_structure_function @ %def process_beam_config_has_structure_function @ \subsection{Process components} A process component is an individual contribution to a process (scattering or decay) which needs not be physical. The sum over all components should be physical. The [[index]] indentifies this component within its parent process. The actual process component is stored in the [[core]] subobject. We use a polymorphic subobject instead of an extension of [[process_component_t]], because the individual entries in the array of process components can have different types. In short, [[process_component_t]] is a wrapper for the actual process variants. If the [[active]] flag is false, we should skip this component. This happens if the associated process has vanishing matrix element. The index array [[i_term]] points to the individual terms generated by this component. The indices refer to the parent process. The index [[i_mci]] is the index of the MC integrator and parameter set which are associated to this process component. <>= public :: process_component_t <>= type :: process_component_t type(process_component_def_t), pointer :: config => null () integer :: index = 0 logical :: active = .false. integer, dimension(:), allocatable :: i_term integer :: i_mci = 0 class(phs_config_t), allocatable :: phs_config character(32) :: md5sum_phs = "" integer :: component_type = COMP_DEFAULT contains <> end type process_component_t @ %def process_component_t @ Finalizer. The MCI template may (potentially) need a finalizer. The process configuration finalizer may include closing an open scratch file. <>= procedure :: final => process_component_final <>= subroutine process_component_final (object) class(process_component_t), intent(inout) :: object if (allocated (object%phs_config)) then call object%phs_config%final () end if end subroutine process_component_final @ %def process_component_final @ The meaning of [[verbose]] depends on the process variant. <>= procedure :: write => process_component_write <>= subroutine process_component_write (object, unit) class(process_component_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) if (associated (object%config)) then write (u, "(1x,A,I0)") "Component #", object%index call object%config%write (u) if (object%md5sum_phs /= "") then write (u, "(3x,A,A,A)") "MD5 sum (phs) = '", & object%md5sum_phs, "'" end if else write (u, "(1x,A)") "Process component: [not allocated]" end if if (.not. object%active) then write (u, "(1x,A)") "[Inactive]" return end if write (u, "(1x,A)") "Referenced data:" if (allocated (object%i_term)) then write (u, "(3x,A,999(1x,I0))") "Terms =", & object%i_term else write (u, "(3x,A)") "Terms = [undefined]" end if if (object%i_mci /= 0) then write (u, "(3x,A,I0)") "MC dataset = ", object%i_mci else write (u, "(3x,A)") "MC dataset = [undefined]" end if if (allocated (object%phs_config)) then call object%phs_config%write (u) end if end subroutine process_component_write @ %def process_component_write @ Initialize the component. <>= procedure :: init => process_component_init <>= subroutine process_component_init (component, & i_component, env, meta, config, & active, & phs_config_template) class(process_component_t), intent(out) :: component integer, intent(in) :: i_component type(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta type(process_config_data_t), intent(in) :: config logical, intent(in) :: active class(phs_config_t), intent(in), allocatable :: phs_config_template type(process_constants_t) :: data component%index = i_component component%config => & config%process_def%get_component_def_ptr (i_component) component%active = active if (component%active) then allocate (component%phs_config, source = phs_config_template) call env%fill_process_constants (meta%id, i_component, data) call component%phs_config%init (data, config%model) end if end subroutine process_component_init @ %def process_component_init @ <>= procedure :: is_active => process_component_is_active <>= elemental function process_component_is_active (component) result (active) logical :: active class(process_component_t), intent(in) :: component active = component%active end function process_component_is_active @ %def process_component_is_active @ Finalize the phase-space configuration. <>= procedure :: configure_phs => process_component_configure_phs <>= subroutine process_component_configure_phs & (component, sqrts, beam_config, rebuild, & ignore_mismatch, subdir) class(process_component_t), intent(inout) :: component real(default), intent(in) :: sqrts type(process_beam_config_t), intent(in) :: beam_config logical, intent(in), optional :: rebuild logical, intent(in), optional :: ignore_mismatch type(string_t), intent(in), optional :: subdir logical :: no_strfun integer :: nlo_type no_strfun = beam_config%n_strfun == 0 nlo_type = component%config%get_nlo_type () call component%phs_config%configure (sqrts, & azimuthal_dependence = beam_config%azimuthal_dependence, & sqrts_fixed = no_strfun, & lab_is_cm = beam_config%lab_is_cm .and. no_strfun, & rebuild = rebuild, ignore_mismatch = ignore_mismatch, & nlo_type = nlo_type, & subdir = subdir) end subroutine process_component_configure_phs @ %def process_component_configure_phs @ The process component possesses two MD5 sums: the checksum of the component definition, which should be available when the component is initialized, and the phase-space MD5 sum, which is available after configuration. <>= procedure :: compute_md5sum => process_component_compute_md5sum <>= subroutine process_component_compute_md5sum (component) class(process_component_t), intent(inout) :: component component%md5sum_phs = component%phs_config%get_md5sum () end subroutine process_component_compute_md5sum @ %def process_component_compute_md5sum @ Match phase-space channels with structure-function channels, where applicable. This calls a method of the [[phs_config]] phase-space implementation. <>= procedure :: collect_channels => process_component_collect_channels <>= subroutine process_component_collect_channels (component, coll) class(process_component_t), intent(inout) :: component type(phs_channel_collection_t), intent(inout) :: coll call component%phs_config%collect_channels (coll) end subroutine process_component_collect_channels @ %def process_component_collect_channels @ <>= procedure :: get_config => process_component_get_config <>= function process_component_get_config (component) & result (config) type(process_component_def_t) :: config class(process_component_t), intent(in) :: component config = component%config end function process_component_get_config @ %def process_component_get_config @ <>= procedure :: get_md5sum => process_component_get_md5sum <>= pure function process_component_get_md5sum (component) result (md5) type(string_t) :: md5 class(process_component_t), intent(in) :: component md5 = component%config%get_md5sum () // component%md5sum_phs end function process_component_get_md5sum @ %def process_component_get_md5sum @ Return the number of phase-space parameters. <>= procedure :: get_n_phs_par => process_component_get_n_phs_par <>= function process_component_get_n_phs_par (component) result (n_par) class(process_component_t), intent(in) :: component integer :: n_par n_par = component%phs_config%get_n_par () end function process_component_get_n_phs_par @ %def process_component_get_n_phs_par @ <>= procedure :: get_phs_config => process_component_get_phs_config <>= subroutine process_component_get_phs_config (component, phs_config) class(process_component_t), intent(in), target :: component class(phs_config_t), intent(out), pointer :: phs_config phs_config => component%phs_config end subroutine process_component_get_phs_config @ %def process_component_get_phs_config @ <>= procedure :: get_nlo_type => process_component_get_nlo_type <>= elemental function process_component_get_nlo_type (component) result (nlo_type) integer :: nlo_type class(process_component_t), intent(in) :: component nlo_type = component%config%get_nlo_type () end function process_component_get_nlo_type @ %def process_component_get_nlo_type @ <>= procedure :: needs_mci_entry => process_component_needs_mci_entry <>= function process_component_needs_mci_entry (component, combined_integration) result (value) logical :: value class(process_component_t), intent(in) :: component logical, intent(in), optional :: combined_integration value = component%active if (present (combined_integration)) then if (combined_integration) & value = value .and. component%component_type <= COMP_MASTER end if end function process_component_needs_mci_entry @ %def process_component_needs_mci_entry @ <>= procedure :: can_be_integrated => process_component_can_be_integrated <>= elemental function process_component_can_be_integrated (component) result (active) logical :: active class(process_component_t), intent(in) :: component active = component%config%can_be_integrated () end function process_component_can_be_integrated @ %def process_component_can_be_integrated @ \subsection{Process terms} For straightforward tree-level calculations, each process component corresponds to a unique elementary interaction. However, in the case of NLO calculations with subtraction terms, a process component may split into several separate contributions to the scattering, which are qualified by interactions with distinct kinematics and particle content. We represent their configuration as [[process_term_t]] objects, the actual instances will be introduced below as [[term_instance_t]]. In any case, the process term contains an elementary interaction with a definite quantum-number and momentum content. The index [[i_term_global]] identifies the term relative to the process. The index [[i_component]] identifies the process component which generates this term, relative to the parent process. The index [[i_term]] identifies the term relative to the process component (not the process). The [[data]] subobject holds all process constants. The number of allowed flavor/helicity/color combinations is stored as [[n_allowed]]. This is the total number of independent entries in the density matrix. For each combination, the index of the flavor, helicity, and color state is stored in the arrays [[flv]], [[hel]], and [[col]], respectively. The flag [[rearrange]] is true if we need to rearrange the particles of the hard interaction, to obtain the effective parton state. The interaction [[int]] holds the quantum state for the (resolved) hard interaction, the parent-child relations of the particles, and their momenta. The momenta are not filled yet; this is postponed to copies of [[int]] which go into the process instances. If recombination is in effect, we should allocate [[int_eff]] to describe the rearranged partonic state. This type is public only for use in a unit test. <>= public :: process_term_t <>= type :: process_term_t integer :: i_term_global = 0 integer :: i_component = 0 integer :: i_term = 0 integer :: i_sub = 0 integer :: i_core = 0 integer :: n_allowed = 0 type(process_constants_t) :: data real(default) :: alpha_s = 0 integer, dimension(:), allocatable :: flv, hel, col integer :: n_sub, n_sub_color, n_sub_spin type(interaction_t) :: int type(interaction_t), pointer :: int_eff => null () contains <> end type process_term_t @ %def process_term_t @ For the output, we skip the process constants and the tables of allowed quantum numbers. Those can also be read off from the interaction object. <>= procedure :: write => process_term_write <>= subroutine process_term_write (term, unit) class(process_term_t), intent(in) :: term integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) write (u, "(1x,A,I0)") "Term #", term%i_term_global write (u, "(3x,A,I0)") "Process component index = ", & term%i_component write (u, "(3x,A,I0)") "Term index w.r.t. component = ", & term%i_term call write_separator (u) write (u, "(1x,A)") "Hard interaction:" call write_separator (u) call term%int%basic_write (u) end subroutine process_term_write @ %def process_term_write @ Write an account of all quantum number states and their current status. <>= procedure :: write_state_summary => process_term_write_state_summary <>= subroutine process_term_write_state_summary (term, core, unit) class(process_term_t), intent(in) :: term class(prc_core_t), intent(in) :: core integer, intent(in), optional :: unit integer :: u, i, f, h, c type(state_iterator_t) :: it character :: sgn u = given_output_unit (unit) write (u, "(1x,A,I0)") "Term #", term%i_term_global call it%init (term%int%get_state_matrix_ptr ()) do while (it%is_valid ()) i = it%get_me_index () f = term%flv(i) h = term%hel(i) if (allocated (term%col)) then c = term%col(i) else c = 1 end if if (core%is_allowed (term%i_term, f, h, c)) then sgn = "+" else sgn = " " end if write (u, "(1x,A1,1x,I0,2x)", advance="no") sgn, i call quantum_numbers_write (it%get_quantum_numbers (), u) write (u, *) call it%advance () end do end subroutine process_term_write_state_summary @ %def process_term_write_state_summary @ Finalizer: the [[int]] and potentially [[int_eff]] components have a finalizer that we must call. <>= procedure :: final => process_term_final <>= subroutine process_term_final (term) class(process_term_t), intent(inout) :: term call term%int%final () end subroutine process_term_final @ %def process_term_final @ Initialize the term. We copy the process constants from the [[core]] object and set up the [[int]] hard interaction accordingly. The [[alpha_s]] value is useful for writing external event records. This is the constant value which may be overridden by a event-specific running value. If the model does not contain the strong coupling, the value is zero. The [[rearrange]] part is commented out; this or something equivalent could become relevant for NLO algorithms. <>= procedure :: init => process_term_init <>= subroutine process_term_init & (term, i_term_global, i_component, i_term, core, model, & nlo_type, use_beam_pol, subtraction_method, & has_pdfs, n_emitters) class(process_term_t), intent(inout), target :: term integer, intent(in) :: i_term_global integer, intent(in) :: i_component integer, intent(in) :: i_term class(prc_core_t), intent(inout) :: core class(model_data_t), intent(in), target :: model integer, intent(in), optional :: nlo_type logical, intent(in), optional :: use_beam_pol type(string_t), intent(in), optional :: subtraction_method logical, intent(in), optional :: has_pdfs integer, intent(in), optional :: n_emitters class(modelpar_data_t), pointer :: alpha_s_ptr logical :: use_internal_color term%i_term_global = i_term_global term%i_component = i_component term%i_term = i_term call core%get_constants (term%data, i_term) alpha_s_ptr => model%get_par_data_ptr (var_str ("alphas")) if (associated (alpha_s_ptr)) then term%alpha_s = alpha_s_ptr%get_real () else term%alpha_s = -1 end if use_internal_color = .false. if (present (subtraction_method)) & use_internal_color = (char (subtraction_method) == 'omega') & .or. (char (subtraction_method) == 'threshold') call term%setup_interaction (core, model, nlo_type = nlo_type, & pol_beams = use_beam_pol, use_internal_color = use_internal_color, & has_pdfs = has_pdfs, n_emitters = n_emitters) end subroutine process_term_init @ %def process_term_init @ We fetch the process constants which determine the quantum numbers and use those to create the interaction. The interaction contains incoming and outgoing particles, no virtuals. The incoming particles are parents of the outgoing ones. Keeping previous \whizard\ conventions, we invert the color assignment (but not flavor or helicity) for the incoming particles. When the color-flow square matrix is evaluated, this inversion is done again, so in the color-flow sequence we get the color assignments of the matrix element. \textbf{Why are these four subtraction entries for structure-function aware interactions?} Taking the soft or collinear limit of the real-emission matrix element, the behavior of the parton energy fractions has to be taken into account. In the pure real case, $x_\oplus$ and $x_\ominus$ are given by \begin{equation*} x_\oplus = \frac{\bar{x}_\oplus}{\sqrt{1-\xi}} \sqrt{\frac{2 - \xi(1-y)}{2 - \xi(1+y)}}, \quad x_\ominus = \frac{\bar{x}_\ominus}{\sqrt{1-\xi}} \sqrt{\frac{2 - \xi(1+y)}{2 - \xi(1-y)}}. \end{equation*} In the soft limit, $\xi \to 0$, this yields $x_\oplus = \bar{x}_\oplus$ and $x_\ominus = \bar{x}_\ominus$. In the collinear limit, $y \to 1$, it is $x_\oplus = \bar{x}_\oplus / (1 - \xi)$ and $x_\ominus = \bar{x}_\ominus$. Likewise, in the anti-collinear limit $y \to -1$, the inverse relation holds. We therefore have to distinguish four cases with the PDF assignments $f(x_\oplus) \cdot f(x_\ominus)$, $f(\bar{x}_\oplus) \cdot f(\bar{x}_\ominus)$, $f\left(\bar{x}_\oplus / (1-\xi)\right) \cdot f(\bar{x}_\ominus)$ and $f(\bar{x}_\oplus) \cdot f\left(\bar{x}_\ominus / (1-\xi)\right)$. The [[n_emitters]] optional argument is provided by the caller if this term requires spin-correlated matrix elements, and thus involves additional subtractions. <>= procedure :: setup_interaction => process_term_setup_interaction <>= subroutine process_term_setup_interaction (term, core, model, & nlo_type, pol_beams, has_pdfs, use_internal_color, n_emitters) class(process_term_t), intent(inout) :: term class(prc_core_t), intent(inout) :: core class(model_data_t), intent(in), target :: model logical, intent(in), optional :: pol_beams logical, intent(in), optional :: has_pdfs integer, intent(in), optional :: nlo_type logical, intent(in), optional :: use_internal_color integer, intent(in), optional :: n_emitters integer :: n, n_tot type(flavor_t), dimension(:), allocatable :: flv type(color_t), dimension(:), allocatable :: col type(helicity_t), dimension(:), allocatable :: hel type(quantum_numbers_t), dimension(:), allocatable :: qn logical :: is_pol, use_color integer :: nlo_t, n_sub is_pol = .false.; if (present (pol_beams)) is_pol = pol_beams nlo_t = BORN; if (present (nlo_type)) nlo_t = nlo_type n_tot = term%data%n_in + term%data%n_out call count_number_of_states () term%n_allowed = n call compute_n_sub (n_emitters, has_pdfs) call fill_quantum_numbers () call term%int%basic_init & (term%data%n_in, 0, term%data%n_out, set_relations = .true.) select type (core) class is (prc_blha_t) call setup_states_blha_olp () type is (prc_threshold_t) call setup_states_threshold () class is (prc_external_t) call setup_states_other_prc_external () class default call setup_states_omega () end select call term%int%freeze () contains subroutine count_number_of_states () integer :: f, h, c n = 0 select type (core) class is (prc_external_t) do f = 1, term%data%n_flv do h = 1, term%data%n_hel do c = 1, term%data%n_col n = n + 1 end do end do end do class default !!! Omega and all test cores do f = 1, term%data%n_flv do h = 1, term%data%n_hel do c = 1, term%data%n_col if (core%is_allowed (term%i_term, f, h, c)) n = n + 1 end do end do end do end select end subroutine count_number_of_states subroutine compute_n_sub (n_emitters, has_pdfs) integer, intent(in), optional :: n_emitters logical, intent(in), optional :: has_pdfs logical :: can_have_sub integer :: n_sub_color, n_sub_spin use_color = .false.; if (present (use_internal_color)) & use_color = use_internal_color can_have_sub = nlo_t == NLO_VIRTUAL .or. & (nlo_t == NLO_REAL .and. term%i_term_global == term%i_sub) .or. & nlo_t == NLO_MISMATCH n_sub_color = 0; n_sub_spin = 0 if (can_have_sub) then if (.not. use_color) n_sub_color = n_tot * (n_tot - 1) / 2 if (nlo_t == NLO_REAL) then if (present (n_emitters)) then n_sub_spin = 6 * n_emitters end if end if end if n_sub = n_sub_color + n_sub_spin !!! For the virtual subtraction we also need the finite virtual contribution !!! corresponding to the $\epsilon^0$-pole if (nlo_t == NLO_VIRTUAL) n_sub = n_sub + 1 if (present (has_pdfs)) then if (has_pdfs & .and. ((nlo_t == NLO_REAL .and. can_have_sub) & .or. nlo_t == NLO_DGLAP)) then !!! necessary dummy, needs refactoring, !!! c.f. [[term_instance_evaluate_interaction_userdef_tree]] n_sub = n_sub + n_beams_rescaled end if end if term%n_sub = n_sub term%n_sub_color = n_sub_color term%n_sub_spin = n_sub_spin end subroutine compute_n_sub subroutine fill_quantum_numbers () integer :: nn logical :: can_have_sub select type (core) class is (prc_external_t) can_have_sub = nlo_t == NLO_VIRTUAL .or. & (nlo_t == NLO_REAL .and. term%i_term_global == term%i_sub) .or. & nlo_t == NLO_MISMATCH .or. nlo_t == NLO_DGLAP if (can_have_sub) then nn = (n_sub + 1) * n else nn = n end if class default nn = n end select allocate (term%flv (nn), term%col (nn), term%hel (nn)) allocate (flv (n_tot), col (n_tot), hel (n_tot)) allocate (qn (n_tot)) end subroutine fill_quantum_numbers subroutine setup_states_blha_olp () integer :: s, f, c, h, i i = 0 associate (data => term%data) do s = 0, n_sub do f = 1, data%n_flv do h = 1, data%n_hel do c = 1, data%n_col i = i + 1 term%flv(i) = f term%hel(i) = h !!! Dummy-initialization of color term%col(i) = c call flv%init (data%flv_state (:,f), model) call color_init_from_array (col, & data%col_state(:,:,c), data%ghost_flag(:,c)) call col(1:data%n_in)%invert () if (is_pol) then select type (core) type is (prc_openloops_t) call hel%init (data%hel_state (:,h)) call qn%init (flv, hel, col, s) class default call msg_fatal ("Polarized beams only supported by OpenLoops") end select else call qn%init (flv, col, s) end if call qn%tag_hard_process () call term%int%add_state (qn) end do end do end do end do end associate end subroutine setup_states_blha_olp subroutine setup_states_threshold () integer :: s, f, c, h, i i = 0 n_sub = 0; if (nlo_t == NLO_VIRTUAL) n_sub = 1 associate (data => term%data) do s = 0, n_sub do f = 1, term%data%n_flv do h = 1, data%n_hel do c = 1, data%n_col i = i + 1 term%flv(i) = f term%hel(i) = h !!! Dummy-initialization of color term%col(i) = 1 call flv%init (term%data%flv_state (:,f), model) if (is_pol) then call hel%init (data%hel_state (:,h)) call qn%init (flv, hel, s) else call qn%init (flv, s) end if call qn%tag_hard_process () call term%int%add_state (qn) end do end do end do end do end associate end subroutine setup_states_threshold subroutine setup_states_other_prc_external () integer :: s, f, i, c, h if (is_pol) & call msg_fatal ("Polarized beams only supported by OpenLoops") i = 0 !!! n_sub = 0; if (nlo_t == NLO_VIRTUAL) n_sub = 1 associate (data => term%data) do s = 0, n_sub do f = 1, data%n_flv do h = 1, data%n_hel do c = 1, data%n_col i = i + 1 term%flv(i) = f term%hel(i) = h !!! Dummy-initialization of color term%col(i) = c call flv%init (data%flv_state (:,f), model) call color_init_from_array (col, & data%col_state(:,:,c), data%ghost_flag(:,c)) call col(1:data%n_in)%invert () call qn%init (flv, col, s) call qn%tag_hard_process () call term%int%add_state (qn) end do end do end do end do end associate end subroutine setup_states_other_prc_external subroutine setup_states_omega () integer :: f, h, c, i i = 0 associate (data => term%data) do f = 1, data%n_flv do h = 1, data%n_hel do c = 1, data%n_col if (core%is_allowed (term%i_term, f, h, c)) then i = i + 1 term%flv(i) = f term%hel(i) = h term%col(i) = c call flv%init (data%flv_state(:,f), model) call color_init_from_array (col, & data%col_state(:,:,c), & data%ghost_flag(:,c)) call col(:data%n_in)%invert () call hel%init (data%hel_state(:,h)) call qn%init (flv, col, hel) call qn%tag_hard_process () call term%int%add_state (qn) end if end do end do end do end associate end subroutine setup_states_omega end subroutine process_term_setup_interaction @ %def process_term_setup_interaction @ <>= procedure :: get_process_constants => process_term_get_process_constants <>= subroutine process_term_get_process_constants & (term, prc_constants) class(process_term_t), intent(inout) :: term type(process_constants_t), intent(out) :: prc_constants prc_constants = term%data end subroutine process_term_get_process_constants @ %def process_term_get_process_constants @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process call statistics} Very simple object for statistics. Could be moved to a more basic chapter. <<[[process_counter.f90]]>>= <> module process_counter use io_units <> <> <> <> contains <> end module process_counter @ %def process_counter @ This object can record process calls, categorized by evaluation status. It is a part of the [[mci_entry]] component below. <>= public :: process_counter_t <>= type :: process_counter_t integer :: total = 0 integer :: failed_kinematics = 0 integer :: failed_cuts = 0 integer :: has_passed = 0 integer :: evaluated = 0 integer :: complete = 0 contains <> end type process_counter_t @ %def process_counter_t @ Here are the corresponding numeric codes: <>= integer, parameter, public :: STAT_UNDEFINED = 0 integer, parameter, public :: STAT_INITIAL = 1 integer, parameter, public :: STAT_ACTIVATED = 2 integer, parameter, public :: STAT_BEAM_MOMENTA = 3 integer, parameter, public :: STAT_FAILED_KINEMATICS = 4 integer, parameter, public :: STAT_SEED_KINEMATICS = 5 integer, parameter, public :: STAT_HARD_KINEMATICS = 6 integer, parameter, public :: STAT_EFF_KINEMATICS = 7 integer, parameter, public :: STAT_FAILED_CUTS = 8 integer, parameter, public :: STAT_PASSED_CUTS = 9 integer, parameter, public :: STAT_EVALUATED_TRACE = 10 integer, parameter, public :: STAT_EVENT_COMPLETE = 11 @ %def STAT_UNDEFINED STAT_INITIAL STAT_ACTIVATED @ %def STAT_BEAM_MOMENTA STAT_FAILED_KINEMATICS @ %def STAT_SEED_KINEMATICS STAT_HARD_KINEMATICS STAT_EFF_KINEMATICS @ %def STAT_EVALUATED_TRACE STAT_EVENT_COMPLETE @ Output. <>= procedure :: write => process_counter_write <>= subroutine process_counter_write (object, unit) class(process_counter_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u u = given_output_unit (unit) if (object%total > 0) then write (u, "(1x,A)") "Call statistics (current run):" write (u, "(3x,A,I0)") "total = ", object%total write (u, "(3x,A,I0)") "failed kin. = ", object%failed_kinematics write (u, "(3x,A,I0)") "failed cuts = ", object%failed_cuts write (u, "(3x,A,I0)") "passed cuts = ", object%has_passed write (u, "(3x,A,I0)") "evaluated = ", object%evaluated else write (u, "(1x,A)") "Call statistics (current run): [no calls]" end if end subroutine process_counter_write @ %def process_counter_write @ Reset. Just enforce default initialization. <>= procedure :: reset => process_counter_reset <>= subroutine process_counter_reset (counter) class(process_counter_t), intent(out) :: counter counter%total = 0 counter%failed_kinematics = 0 counter%failed_cuts = 0 counter%has_passed = 0 counter%evaluated = 0 counter%complete = 0 end subroutine process_counter_reset @ %def process_counter_reset @ We record an event according to the lowest status code greater or equal to the actual status. This is actually done by the process instance; the process object just copies the instance counter. <>= procedure :: record => process_counter_record <>= subroutine process_counter_record (counter, status) class(process_counter_t), intent(inout) :: counter integer, intent(in) :: status if (status <= STAT_FAILED_KINEMATICS) then counter%failed_kinematics = counter%failed_kinematics + 1 else if (status <= STAT_FAILED_CUTS) then counter%failed_cuts = counter%failed_cuts + 1 else if (status <= STAT_PASSED_CUTS) then counter%has_passed = counter%has_passed + 1 else counter%evaluated = counter%evaluated + 1 end if counter%total = counter%total + 1 end subroutine process_counter_record @ %def process_counter_record @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Multi-channel integration} <<[[process_mci.f90]]>>= <> module process_mci <> <> <> use io_units use diagnostics use physics_defs use md5 use cputime use rng_base use mci_base use variables use integration_results use process_libraries use phs_base use process_counter use process_config <> <> <> <> contains <> end module process_mci @ %def process_mci \subsection{Process MCI entry} The [[process_mci_entry_t]] block contains, for each process component that is integrated independently, the configuration data for its MC input parameters. Each input parameter set is handled by a [[mci_t]] integrator. The MC input parameter set is broken down into the parameters required by the structure-function chain and the parameters required by the phase space of the elementary process. The MD5 sum collects all information about the associated processes that may affect the integration. It does not contain the MCI object itself or integration results. MC integration is organized in passes. Each pass may consist of several iterations, and for each iteration there is a number of calls. We store explicitly the values that apply to the current pass. Previous values are archived in the [[results]] object. The [[counter]] receives the counter statistics from the associated process instance, for diagnostics. The [[results]] object records results, broken down in passes and iterations. <>= public :: process_mci_entry_t <>= type :: process_mci_entry_t integer :: i_mci = 0 integer, dimension(:), allocatable :: i_component integer :: process_type = PRC_UNKNOWN integer :: n_par = 0 integer :: n_par_sf = 0 integer :: n_par_phs = 0 character(32) :: md5sum = "" integer :: pass = 0 integer :: n_it = 0 integer :: n_calls = 0 logical :: activate_timer = .false. real(default) :: error_threshold = 0 class(mci_t), allocatable :: mci type(process_counter_t) :: counter type(integration_results_t) :: results logical :: negative_weights = .false. logical :: combined_integration = .false. integer :: real_partition_type = REAL_FULL contains <> end type process_mci_entry_t @ %def process_mci_entry_t @ Finalizer for the [[mci]] component. <>= procedure :: final => process_mci_entry_final <>= subroutine process_mci_entry_final (object) class(process_mci_entry_t), intent(inout) :: object if (allocated (object%mci)) call object%mci%final () end subroutine process_mci_entry_final @ %def process_mci_entry_final @ Output. Write pass/iteration information only if set (the pass index is nonzero). Write the MCI block only if it exists (for some self-tests it does not). Write results only if there are any. <>= procedure :: write => process_mci_entry_write <>= subroutine process_mci_entry_write (object, unit, pacify) class(process_mci_entry_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: pacify integer :: u u = given_output_unit (unit) write (u, "(3x,A,I0)") "Associated components = ", object%i_component write (u, "(3x,A,I0)") "MC input parameters = ", object%n_par write (u, "(3x,A,I0)") "MC parameters (SF) = ", object%n_par_sf write (u, "(3x,A,I0)") "MC parameters (PHS) = ", object%n_par_phs if (object%pass > 0) then write (u, "(3x,A,I0)") "Current pass = ", object%pass write (u, "(3x,A,I0)") "Number of iterations = ", object%n_it write (u, "(3x,A,I0)") "Number of calls = ", object%n_calls end if if (object%md5sum /= "") then write (u, "(3x,A,A,A)") "MD5 sum (components) = '", object%md5sum, "'" end if if (allocated (object%mci)) then call object%mci%write (u) end if call object%counter%write (u) if (object%results%exist ()) then call object%results%write (u, suppress = pacify) call object%results%write_chain_weights (u) end if end subroutine process_mci_entry_write @ %def process_mci_entry_write @ Configure the MCI entry. This is intent(inout) since some specific settings may be done before this. The actual [[mci_t]] object is an instance of the [[mci_template]] argument, which determines the concrete types. In a unit-test context, the [[mci_template]] argument may be unallocated. We obtain the number of channels and the number of parameters, separately for the structure-function chain and for the associated process component. We assume that the phase-space object has already been configured. We assume that there is only one process component directly associated with a MCI entry. <>= procedure :: configure => process_mci_entry_configure <>= subroutine process_mci_entry_configure (mci_entry, mci_template, & process_type, i_mci, i_component, component, & n_sfpar, rng_factory) class(process_mci_entry_t), intent(inout) :: mci_entry class(mci_t), intent(in), allocatable :: mci_template integer, intent(in) :: process_type integer, intent(in) :: i_mci integer, intent(in) :: i_component type(process_component_t), intent(in), target :: component integer, intent(in) :: n_sfpar class(rng_factory_t), intent(inout) :: rng_factory class(rng_t), allocatable :: rng associate (phs_config => component%phs_config) mci_entry%i_mci = i_mci call mci_entry%create_component_list (i_component, component%get_config ()) mci_entry%n_par_sf = n_sfpar mci_entry%n_par_phs = phs_config%get_n_par () mci_entry%n_par = mci_entry%n_par_sf + mci_entry%n_par_phs mci_entry%process_type = process_type if (allocated (mci_template)) then allocate (mci_entry%mci, source = mci_template) call mci_entry%mci%record_index (mci_entry%i_mci) call mci_entry%mci%set_dimensions & (mci_entry%n_par, phs_config%get_n_channel ()) call mci_entry%mci%declare_flat_dimensions & (phs_config%get_flat_dimensions ()) if (phs_config%provides_equivalences) then call mci_entry%mci%declare_equivalences & (phs_config%channel, mci_entry%n_par_sf) end if if (phs_config%provides_chains) then call mci_entry%mci%declare_chains (phs_config%chain) end if call rng_factory%make (rng) call mci_entry%mci%import_rng (rng) end if call mci_entry%results%init (process_type) end associate end subroutine process_mci_entry_configure @ %def process_mci_entry_configure @ <>= integer, parameter, public :: REAL_FULL = 0 integer, parameter, public :: REAL_SINGULAR = 1 integer, parameter, public :: REAL_FINITE = 2 @ <>= procedure :: create_component_list => & process_mci_entry_create_component_list <>= subroutine process_mci_entry_create_component_list (mci_entry, & i_component, component_config) class (process_mci_entry_t), intent(inout) :: mci_entry integer, intent(in) :: i_component type(process_component_def_t), intent(in) :: component_config integer, dimension(:), allocatable :: i_list integer :: n integer, save :: i_rfin_offset = 0 if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, "process_mci_entry_create_component_list") if (mci_entry%combined_integration) then if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, & "mci_entry%real_partition_type", mci_entry%real_partition_type) n = get_n_components (mci_entry%real_partition_type) allocate (i_list (n)) select case (mci_entry%real_partition_type) case (REAL_FULL) i_list = component_config%get_association_list () allocate (mci_entry%i_component (size (i_list))) mci_entry%i_component = i_list case (REAL_SINGULAR) i_list = component_config%get_association_list (ASSOCIATED_REAL_FIN) allocate (mci_entry%i_component (size(i_list))) mci_entry%i_component = i_list case (REAL_FINITE) allocate (mci_entry%i_component (1)) mci_entry%i_component(1) = & component_config%get_associated_real_fin () + i_rfin_offset i_rfin_offset = i_rfin_offset + 1 end select else allocate (mci_entry%i_component (1)) mci_entry%i_component(1) = i_component end if contains function get_n_components (real_partition_type) result (n_components) integer :: n_components integer, intent(in) :: real_partition_type select case (real_partition_type) case (REAL_FULL) n_components = size (component_config%get_association_list ()) case (REAL_SINGULAR) n_components = size (component_config%get_association_list & (ASSOCIATED_REAL_FIN)) end select if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, "n_components", n_components) end function get_n_components end subroutine process_mci_entry_create_component_list @ %def process_mci_entry_create_component_list @ Set some additional parameters. <>= procedure :: set_parameters => process_mci_entry_set_parameters <>= subroutine process_mci_entry_set_parameters (mci_entry, var_list) class(process_mci_entry_t), intent(inout) :: mci_entry type(var_list_t), intent(in) :: var_list integer :: integration_results_verbosity real(default) :: error_threshold integration_results_verbosity = & var_list%get_ival (var_str ("integration_results_verbosity")) error_threshold = & var_list%get_rval (var_str ("error_threshold")) mci_entry%activate_timer = & var_list%get_lval (var_str ("?integration_timer")) call mci_entry%results%set_verbosity (integration_results_verbosity) call mci_entry%results%set_error_threshold (error_threshold) end subroutine process_mci_entry_set_parameters @ %def process_mci_entry_set_parameters @ Compute an MD5 sum that summarizes all information that could influence integration results, for the associated process components. We take the process-configuration MD5 sum which represents parameters, cuts, etc., the MD5 sums for the process component definitions and their phase space objects (which should be configured), and the beam configuration MD5 sum. (The QCD setup is included in the process configuration data MD5 sum.) Done only once, when the MD5 sum is still empty. <>= procedure :: compute_md5sum => process_mci_entry_compute_md5sum <>= subroutine process_mci_entry_compute_md5sum (mci_entry, & config, component, beam_config) class(process_mci_entry_t), intent(inout) :: mci_entry type(process_config_data_t), intent(in) :: config type(process_component_t), dimension(:), intent(in) :: component type(process_beam_config_t), intent(in) :: beam_config type(string_t) :: buffer integer :: i if (mci_entry%md5sum == "") then buffer = config%get_md5sum () // beam_config%get_md5sum () do i = 1, size (component) if (component(i)%is_active ()) then buffer = buffer // component(i)%get_md5sum () end if end do mci_entry%md5sum = md5sum (char (buffer)) end if if (allocated (mci_entry%mci)) then call mci_entry%mci%set_md5sum (mci_entry%md5sum) end if end subroutine process_mci_entry_compute_md5sum @ %def process_mci_entry_compute_md5sum @ Test the MCI sampler by calling it a given number of time, discarding the results. The instance should be initialized. The [[mci_entry]] is [[intent(inout)]] because the integrator contains the random-number state. <>= procedure :: sampler_test => process_mci_entry_sampler_test <>= subroutine process_mci_entry_sampler_test (mci_entry, mci_sampler, n_calls) class(process_mci_entry_t), intent(inout) :: mci_entry class(mci_sampler_t), intent(inout), target :: mci_sampler integer, intent(in) :: n_calls call mci_entry%mci%sampler_test (mci_sampler, n_calls) end subroutine process_mci_entry_sampler_test @ %def process_mci_entry_sampler_test @ Integrate. The [[integrate]] method counts as an integration pass; the pass count is increased by one. We transfer the pass parameters (number of iterations and number of calls) to the actual integration routine. The [[mci_entry]] is [[intent(inout)]] because the integrator contains the random-number state. Note: The results are written to screen and to logfile. This behavior is hardcoded. <>= procedure :: integrate => process_mci_entry_integrate procedure :: final_integration => process_mci_entry_final_integration <>= subroutine process_mci_entry_integrate (mci_entry, mci_instance, & mci_sampler, n_it, n_calls, & adapt_grids, adapt_weights, final, pacify, & nlo_type) class(process_mci_entry_t), intent(inout) :: mci_entry class(mci_instance_t), intent(inout) :: mci_instance class(mci_sampler_t), intent(inout) :: mci_sampler integer, intent(in) :: n_it integer, intent(in) :: n_calls logical, intent(in), optional :: adapt_grids logical, intent(in), optional :: adapt_weights logical, intent(in), optional :: final, pacify integer, intent(in), optional :: nlo_type integer :: u_log u_log = logfile_unit () mci_entry%pass = mci_entry%pass + 1 mci_entry%n_it = n_it mci_entry%n_calls = n_calls if (mci_entry%pass == 1) & call mci_entry%mci%startup_message (n_calls = n_calls) call mci_entry%mci%set_timer (active = mci_entry%activate_timer) call mci_entry%results%display_init (screen = .true., unit = u_log) call mci_entry%results%new_pass () if (present (nlo_type)) then select case (nlo_type) case (NLO_VIRTUAL, NLO_REAL, NLO_MISMATCH, NLO_DGLAP) mci_instance%negative_weights = .true. end select end if call mci_entry%mci%add_pass (adapt_grids, adapt_weights, final) call mci_entry%mci%start_timer () call mci_entry%mci%integrate (mci_instance, mci_sampler, n_it, & n_calls, mci_entry%results, pacify = pacify) call mci_entry%mci%stop_timer () if (signal_is_pending ()) return end subroutine process_mci_entry_integrate subroutine process_mci_entry_final_integration (mci_entry) class(process_mci_entry_t), intent(inout) :: mci_entry call mci_entry%results%display_final () call mci_entry%time_message () end subroutine process_mci_entry_final_integration @ %def process_mci_entry_integrate @ %def process_mci_entry_final_integration @ If appropriate, issue an informative message about the expected time for an event sample. <>= procedure :: get_time => process_mci_entry_get_time procedure :: time_message => process_mci_entry_time_message <>= subroutine process_mci_entry_get_time (mci_entry, time, sample) class(process_mci_entry_t), intent(in) :: mci_entry type(time_t), intent(out) :: time integer, intent(in) :: sample real(default) :: time_last_pass, efficiency, calls time_last_pass = mci_entry%mci%get_time () calls = mci_entry%results%get_n_calls () efficiency = mci_entry%mci%get_efficiency () if (time_last_pass > 0 .and. calls > 0 .and. efficiency > 0) then time = nint (time_last_pass / calls / efficiency * sample) end if end subroutine process_mci_entry_get_time subroutine process_mci_entry_time_message (mci_entry) class(process_mci_entry_t), intent(in) :: mci_entry type(time_t) :: time integer :: sample sample = 10000 call mci_entry%get_time (time, sample) if (time%is_known ()) then call msg_message ("Time estimate for generating 10000 events: " & // char (time%to_string_dhms ())) end if end subroutine process_mci_entry_time_message @ %def process_mci_entry_time_message @ Prepare event generation. (For the test integrator, this does nothing. It is relevant for the VAMP integrator.) <>= procedure :: prepare_simulation => process_mci_entry_prepare_simulation <>= subroutine process_mci_entry_prepare_simulation (mci_entry) class(process_mci_entry_t), intent(inout) :: mci_entry call mci_entry%mci%prepare_simulation () end subroutine process_mci_entry_prepare_simulation @ %def process_mci_entry_prepare_simulation @ Generate an event. The instance should be initialized, otherwise event generation is directed by the [[mci]] integrator subobject. The integrator instance is contained in a [[mci_work]] subobject of the process instance, which simultaneously serves as the sampler object. (We avoid the anti-aliasing rules if we assume that the sampling itself does not involve the integrator instance contained in the process instance.) Regarding weighted events, we only take events which are valid, which means that they have valid kinematics and have passed cuts. Therefore, we have a rejection loop. For unweighted events, the unweighting routine should already take care of this. The [[keep_failed]] flag determines whether events which failed cuts are nevertheless produced, to be recorded with zero weight. Alternatively, failed events are dropped, and this fact is recorded by the counter [[n_dropped]]. <>= procedure :: generate_weighted_event => & process_mci_entry_generate_weighted_event procedure :: generate_unweighted_event => & process_mci_entry_generate_unweighted_event <>= subroutine process_mci_entry_generate_weighted_event (mci_entry, & mci_instance, mci_sampler, keep_failed) class(process_mci_entry_t), intent(inout) :: mci_entry class(mci_instance_t), intent(inout) :: mci_instance class(mci_sampler_t), intent(inout) :: mci_sampler logical, intent(in) :: keep_failed logical :: generate_new generate_new = .true. call mci_instance%reset_n_event_dropped () REJECTION: do while (generate_new) call mci_entry%mci%generate_weighted_event (mci_instance, mci_sampler) if (signal_is_pending ()) return if (.not. mci_sampler%is_valid()) then if (keep_failed) then generate_new = .false. else call mci_instance%record_event_dropped () generate_new = .true. end if else generate_new = .false. end if end do REJECTION end subroutine process_mci_entry_generate_weighted_event subroutine process_mci_entry_generate_unweighted_event (mci_entry, mci_instance, mci_sampler) class(process_mci_entry_t), intent(inout) :: mci_entry class(mci_instance_t), intent(inout) :: mci_instance class(mci_sampler_t), intent(inout) :: mci_sampler call mci_entry%mci%generate_unweighted_event (mci_instance, mci_sampler) end subroutine process_mci_entry_generate_unweighted_event @ %def process_mci_entry_generate_weighted_event @ %def process_mci_entry_generate_unweighted_event @ Extract results. <>= procedure :: has_integral => process_mci_entry_has_integral procedure :: get_integral => process_mci_entry_get_integral procedure :: get_error => process_mci_entry_get_error procedure :: get_accuracy => process_mci_entry_get_accuracy procedure :: get_chi2 => process_mci_entry_get_chi2 procedure :: get_efficiency => process_mci_entry_get_efficiency <>= function process_mci_entry_has_integral (mci_entry) result (flag) class(process_mci_entry_t), intent(in) :: mci_entry logical :: flag flag = mci_entry%results%exist () end function process_mci_entry_has_integral function process_mci_entry_get_integral (mci_entry) result (integral) class(process_mci_entry_t), intent(in) :: mci_entry real(default) :: integral integral = mci_entry%results%get_integral () end function process_mci_entry_get_integral function process_mci_entry_get_error (mci_entry) result (error) class(process_mci_entry_t), intent(in) :: mci_entry real(default) :: error error = mci_entry%results%get_error () end function process_mci_entry_get_error function process_mci_entry_get_accuracy (mci_entry) result (accuracy) class(process_mci_entry_t), intent(in) :: mci_entry real(default) :: accuracy accuracy = mci_entry%results%get_accuracy () end function process_mci_entry_get_accuracy function process_mci_entry_get_chi2 (mci_entry) result (chi2) class(process_mci_entry_t), intent(in) :: mci_entry real(default) :: chi2 chi2 = mci_entry%results%get_chi2 () end function process_mci_entry_get_chi2 function process_mci_entry_get_efficiency (mci_entry) result (efficiency) class(process_mci_entry_t), intent(in) :: mci_entry real(default) :: efficiency efficiency = mci_entry%results%get_efficiency () end function process_mci_entry_get_efficiency @ %def process_mci_entry_get_integral process_mci_entry_get_error @ %def process_mci_entry_get_accuracy process_mci_entry_get_chi2 @ %def process_mci_entry_get_efficiency @ Return the MCI checksum. This may be the one used for configuration, but may also incorporate results, if they change the state of the integrator (adaptation). <>= procedure :: get_md5sum => process_mci_entry_get_md5sum <>= pure function process_mci_entry_get_md5sum (entry) result (md5sum) class(process_mci_entry_t), intent(in) :: entry character(32) :: md5sum md5sum = entry%mci%get_md5sum () end function process_mci_entry_get_md5sum @ %def process_mci_entry_get_md5sum @ \subsection{MC parameter set and MCI instance} For each process component that is associated with a multi-channel integration (MCI) object, the [[mci_work_t]] object contains the currently active parameter set. It also holds the implementation of the [[mci_instance_t]] that the integrator needs for doing its work. <>= public :: mci_work_t <>= type :: mci_work_t type(process_mci_entry_t), pointer :: config => null () real(default), dimension(:), allocatable :: x class(mci_instance_t), pointer :: mci => null () type(process_counter_t) :: counter logical :: keep_failed_events = .false. integer :: n_event_dropped = 0 contains <> end type mci_work_t @ %def mci_work_t @ First write configuration data, then the current values. <>= procedure :: write => mci_work_write <>= subroutine mci_work_write (mci_work, unit, testflag) class(mci_work_t), intent(in) :: mci_work integer, intent(in), optional :: unit logical, intent(in), optional :: testflag integer :: u, i u = given_output_unit (unit) write (u, "(1x,A,I0,A)") "Active MCI instance #", & mci_work%config%i_mci, " =" write (u, "(2x)", advance="no") do i = 1, mci_work%config%n_par write (u, "(1x,F7.5)", advance="no") mci_work%x(i) if (i == mci_work%config%n_par_sf) & write (u, "(1x,'|')", advance="no") end do write (u, *) if (associated (mci_work%mci)) then call mci_work%mci%write (u, pacify = testflag) call mci_work%counter%write (u) end if end subroutine mci_work_write @ %def mci_work_write @ The [[mci]] component may require finalization. <>= procedure :: final => mci_work_final <>= subroutine mci_work_final (mci_work) class(mci_work_t), intent(inout) :: mci_work if (associated (mci_work%mci)) then call mci_work%mci%final () deallocate (mci_work%mci) end if end subroutine mci_work_final @ %def mci_work_final @ Initialize with the maximum length that we will need. Contents are not initialized. The integrator inside the [[mci_entry]] object is responsible for allocating and initializing its own instance, which is referred to by a pointer in the [[mci_work]] object. <>= procedure :: init => mci_work_init <>= subroutine mci_work_init (mci_work, mci_entry) class(mci_work_t), intent(out) :: mci_work type(process_mci_entry_t), intent(in), target :: mci_entry mci_work%config => mci_entry allocate (mci_work%x (mci_entry%n_par)) if (allocated (mci_entry%mci)) then call mci_entry%mci%allocate_instance (mci_work%mci) call mci_work%mci%init (mci_entry%mci) end if end subroutine mci_work_init @ %def mci_work_init @ Set parameters explicitly, either all at once, or separately for the structure-function and process parts. <>= procedure :: set => mci_work_set procedure :: set_x_strfun => mci_work_set_x_strfun procedure :: set_x_process => mci_work_set_x_process <>= subroutine mci_work_set (mci_work, x) class(mci_work_t), intent(inout) :: mci_work real(default), dimension(:), intent(in) :: x mci_work%x = x end subroutine mci_work_set subroutine mci_work_set_x_strfun (mci_work, x) class(mci_work_t), intent(inout) :: mci_work real(default), dimension(:), intent(in) :: x mci_work%x(1 : mci_work%config%n_par_sf) = x end subroutine mci_work_set_x_strfun subroutine mci_work_set_x_process (mci_work, x) class(mci_work_t), intent(inout) :: mci_work real(default), dimension(:), intent(in) :: x mci_work%x(mci_work%config%n_par_sf + 1 : mci_work%config%n_par) = x end subroutine mci_work_set_x_process @ %def mci_work_set @ %def mci_work_set_x_strfun @ %def mci_work_set_x_process @ Return the array of active components, i.e., those that correspond to the currently selected MC parameter set. <>= procedure :: get_active_components => mci_work_get_active_components <>= function mci_work_get_active_components (mci_work) result (i_component) class(mci_work_t), intent(in) :: mci_work integer, dimension(:), allocatable :: i_component allocate (i_component (size (mci_work%config%i_component))) i_component = mci_work%config%i_component end function mci_work_get_active_components @ %def mci_work_get_active_components @ Return the active parameters as a simple array with correct length. Do this separately for the structure-function parameters and the process parameters. <>= procedure :: get_x_strfun => mci_work_get_x_strfun procedure :: get_x_process => mci_work_get_x_process <>= pure function mci_work_get_x_strfun (mci_work) result (x) class(mci_work_t), intent(in) :: mci_work real(default), dimension(mci_work%config%n_par_sf) :: x x = mci_work%x(1 : mci_work%config%n_par_sf) end function mci_work_get_x_strfun pure function mci_work_get_x_process (mci_work) result (x) class(mci_work_t), intent(in) :: mci_work real(default), dimension(mci_work%config%n_par_phs) :: x x = mci_work%x(mci_work%config%n_par_sf + 1 : mci_work%config%n_par) end function mci_work_get_x_process @ %def mci_work_get_x_strfun @ %def mci_work_get_x_process @ Initialize and finalize event generation for the specified MCI entry. This also resets the counter. <>= procedure :: init_simulation => mci_work_init_simulation procedure :: final_simulation => mci_work_final_simulation <>= subroutine mci_work_init_simulation (mci_work, safety_factor, keep_failed_events) class(mci_work_t), intent(inout) :: mci_work real(default), intent(in), optional :: safety_factor logical, intent(in), optional :: keep_failed_events call mci_work%mci%init_simulation (safety_factor) call mci_work%counter%reset () if (present (keep_failed_events)) & mci_work%keep_failed_events = keep_failed_events end subroutine mci_work_init_simulation subroutine mci_work_final_simulation (mci_work) class(mci_work_t), intent(inout) :: mci_work call mci_work%mci%final_simulation () end subroutine mci_work_final_simulation @ %def mci_work_init_simulation @ %def mci_work_final_simulation @ Counter. <>= procedure :: reset_counter => mci_work_reset_counter procedure :: record_call => mci_work_record_call procedure :: get_counter => mci_work_get_counter <>= subroutine mci_work_reset_counter (mci_work) class(mci_work_t), intent(inout) :: mci_work call mci_work%counter%reset () end subroutine mci_work_reset_counter subroutine mci_work_record_call (mci_work, status) class(mci_work_t), intent(inout) :: mci_work integer, intent(in) :: status call mci_work%counter%record (status) end subroutine mci_work_record_call pure function mci_work_get_counter (mci_work) result (counter) class(mci_work_t), intent(in) :: mci_work type(process_counter_t) :: counter counter = mci_work%counter end function mci_work_get_counter @ %def mci_work_reset_counter @ %def mci_work_record_call @ %def mci_work_get_counter @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process component manager} <<[[pcm.f90]]>>= <> module pcm <> <> <> use constants, only: zero, two use diagnostics use lorentz use io_units, only: free_unit use os_interface use process_constants, only: process_constants_t use physics_defs use model_data, only: model_data_t use models, only: model_t use interactions, only: interaction_t use quantum_numbers, only: quantum_numbers_t, quantum_numbers_mask_t use flavors, only: flavor_t use variables, only: var_list_t use nlo_data, only: nlo_settings_t use mci_base, only: mci_t use phs_base, only: phs_config_t use mappings, only: mapping_defaults_t use phs_forests, only: phs_parameters_t use phs_fks, only: isr_kinematics_t, real_kinematics_t use phs_fks, only: phs_identifier_t use dispatch_fks, only: dispatch_fks_s use fks_regions, only: region_data_t use nlo_data, only: fks_template_t use phs_fks, only: phs_fks_generator_t use phs_fks, only: dalitz_plot_t use phs_fks, only: phs_fks_config_t, get_filtered_resonance_histories use dispatch_phase_space, only: dispatch_phs use process_libraries, only: process_component_def_t use real_subtraction, only: real_subtraction_t, soft_mismatch_t use real_subtraction, only: FIXED_ORDER_EVENTS, POWHEG use real_subtraction, only: real_partition_t, powheg_damping_simple_t use real_subtraction, only: real_partition_fixed_order_t use virtual, only: virtual_t use dglap_remnant, only: dglap_remnant_t use prc_threshold, only: threshold_def_t use resonances, only: resonance_history_t, resonance_history_set_t use nlo_data, only: FKS_DEFAULT, FKS_RESONANCES use blha_config, only: blha_master_t use blha_olp_interfaces, only: prc_blha_t use pcm_base use process_config use process_mci, only: process_mci_entry_t use process_mci, only: REAL_SINGULAR, REAL_FINITE <> <> <> contains <> end module pcm @ %def pcm @ \subsection{Default process component manager} This is the configuration object which has the duty of allocating the corresponding instance. The default version is trivial. <>= public :: pcm_default_t <>= type, extends (pcm_t) :: pcm_default_t contains <> end type pcm_default_t @ %def pcm_default_t <>= procedure :: allocate_instance => pcm_default_allocate_instance <>= subroutine pcm_default_allocate_instance (pcm, instance) class(pcm_default_t), intent(in) :: pcm class(pcm_instance_t), intent(inout), allocatable :: instance allocate (pcm_instance_default_t :: instance) end subroutine pcm_default_allocate_instance @ %def pcm_default_allocate_instance @ Finalizer: apply to core manager. <>= procedure :: final => pcm_default_final <>= subroutine pcm_default_final (pcm) class(pcm_default_t), intent(inout) :: pcm end subroutine pcm_default_final @ %def pcm_default_final @ <>= procedure :: is_nlo => pcm_default_is_nlo <>= function pcm_default_is_nlo (pcm) result (is_nlo) logical :: is_nlo class(pcm_default_t), intent(in) :: pcm is_nlo = .false. end function pcm_default_is_nlo @ %def pcm_default_is_nlo @ Initialize configuration data, using environment variables. <>= procedure :: init => pcm_default_init <>= subroutine pcm_default_init (pcm, env, meta) class(pcm_default_t), intent(out) :: pcm type(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta pcm%has_pdfs = env%has_pdfs () call pcm%set_blha_defaults & (env%has_polarized_beams (), env%get_var_list_ptr ()) pcm%os_data = env%get_os_data () end subroutine pcm_default_init @ %def pcm_default_init @ <>= type, extends (pcm_instance_t) :: pcm_instance_default_t contains <> end type pcm_instance_default_t @ %def pcm_instance_default_t @ <>= procedure :: final => pcm_instance_default_final <>= subroutine pcm_instance_default_final (pcm_instance) class(pcm_instance_default_t), intent(inout) :: pcm_instance end subroutine pcm_instance_default_final @ %def pcm_instance_default_final @ \subsection{Implementations for the default manager} Categorize components. Nothing to do here, all components are of Born type. <>= procedure :: categorize_components => pcm_default_categorize_components <>= subroutine pcm_default_categorize_components (pcm, config) class(pcm_default_t), intent(inout) :: pcm type(process_config_data_t), intent(in) :: config end subroutine pcm_default_categorize_components @ %def pcm_default_categorize_components @ \subsubsection{Phase-space configuration} Default setup for tree processes: a single phase-space configuration that is valid for all components. <>= procedure :: init_phs_config => pcm_default_init_phs_config <>= subroutine pcm_default_init_phs_config & (pcm, phs_entry, meta, env, phs_par, mapping_defs) class(pcm_default_t), intent(inout) :: pcm type(process_phs_config_t), & dimension(:), allocatable, intent(out) :: phs_entry type(process_metadata_t), intent(in) :: meta type(process_environment_t), intent(in) :: env type(mapping_defaults_t), intent(in) :: mapping_defs type(phs_parameters_t), intent(in) :: phs_par allocate (phs_entry (1)) allocate (pcm%i_phs_config (pcm%n_components), source=1) call dispatch_phs (phs_entry(1)%phs_config, & env%get_var_list_ptr (), & env%get_os_data (), & meta%id, & mapping_defs, phs_par) end subroutine pcm_default_init_phs_config @ %def pcm_default_init_phs_config @ \subsubsection{Core management} The default component manager assigns one core per component. We allocate and configure the core objects, using the process-component configuration data. <>= procedure :: allocate_cores => pcm_default_allocate_cores <>= subroutine pcm_default_allocate_cores (pcm, config, core_entry) class(pcm_default_t), intent(inout) :: pcm type(process_config_data_t), intent(in) :: config type(core_entry_t), dimension(:), allocatable, intent(out) :: core_entry type(process_component_def_t), pointer :: component_def integer :: i allocate (pcm%i_core (pcm%n_components), source = 0) pcm%n_cores = pcm%n_components allocate (core_entry (pcm%n_cores)) do i = 1, pcm%n_cores pcm%i_core(i) = i core_entry(i)%i_component = i component_def => config%process_def%get_component_def_ptr (i) core_entry(i)%core_def => component_def%get_core_def_ptr () core_entry(i)%active = component_def%can_be_integrated () end do end subroutine pcm_default_allocate_cores @ %def pcm_default_allocate_cores @ Extra code is required for certain core types (threshold) or if BLHA uses an external OLP (Born only, this case) for getting its matrix elements. <>= procedure :: prepare_any_external_code => & pcm_default_prepare_any_external_code <>= subroutine pcm_default_prepare_any_external_code & (pcm, core_entry, i_core, libname, model, var_list) class(pcm_default_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry integer, intent(in) :: i_core type(string_t), intent(in) :: libname type(model_data_t), intent(in), target :: model type(var_list_t), intent(in) :: var_list if (core_entry%active) then associate (core => core_entry%core) if (core%needs_external_code ()) then call core%prepare_external_code & (core%data%flv_state, & var_list, pcm%os_data, libname, model, i_core, .false.) end if call core%set_equivalent_flv_hel_indices () end associate end if end subroutine pcm_default_prepare_any_external_code @ %def pcm_default_prepare_any_external_code @ Allocate and configure the BLHA record for a specific core, assuming that the core type requires it. In the default case, this is a Born configuration. <>= procedure :: setup_blha => pcm_default_setup_blha <>= subroutine pcm_default_setup_blha (pcm, core_entry) class(pcm_default_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry allocate (core_entry%blha_config, source = pcm%blha_defaults) call core_entry%blha_config%set_born () end subroutine pcm_default_setup_blha @ %def pcm_default_setup_blha @ Apply the configuration, using [[pcm]] data. <>= procedure :: prepare_blha_core => pcm_default_prepare_blha_core <>= subroutine pcm_default_prepare_blha_core (pcm, core_entry, model) class(pcm_default_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry class(model_data_t), intent(in), target :: model integer :: n_in integer :: n_legs integer :: n_flv integer :: n_hel select type (core => core_entry%core) class is (prc_blha_t) associate (blha_config => core_entry%blha_config) n_in = core%data%n_in n_legs = core%data%get_n_tot () n_flv = core%data%n_flv n_hel = blha_config%get_n_hel (core%data%flv_state (1:n_in,1), model) call core%init_blha (blha_config, n_in, n_legs, n_flv, n_hel) call core%init_driver (pcm%os_data) end associate end select end subroutine pcm_default_prepare_blha_core @ %def pcm_default_prepare_blha_core @ Read the method settings from the variable list and store them in the BLHA master. This version: no NLO flag. <>= procedure :: set_blha_methods => pcm_default_set_blha_methods <>= subroutine pcm_default_set_blha_methods (pcm, blha_master, var_list) class(pcm_default_t), intent(inout) :: pcm type(blha_master_t), intent(inout) :: blha_master type(var_list_t), intent(in) :: var_list call blha_master%set_methods (.false., var_list) end subroutine pcm_default_set_blha_methods @ %def pcm_default_set_blha_methods @ Produce the LO and NLO flavor-state tables (as far as available), as appropriate for BLHA configuration. The default version looks at the first process core only, to get the Born data. (Multiple cores are thus unsupported.) The NLO flavor table is left unallocated. <>= procedure :: get_blha_flv_states => pcm_default_get_blha_flv_states <>= subroutine pcm_default_get_blha_flv_states & (pcm, core_entry, flv_born, flv_real) class(pcm_default_t), intent(in) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry integer, dimension(:,:), allocatable, intent(out) :: flv_born integer, dimension(:,:), allocatable, intent(out) :: flv_real flv_born = core_entry(1)%core%data%flv_state end subroutine pcm_default_get_blha_flv_states @ %def pcm_default_get_blha_flv_states @ Allocate and configure the MCI (multi-channel integrator) records. There is one record per active process component. Second procedure: call the MCI dispatcher with default-setup arguments. <>= procedure :: setup_mci => pcm_default_setup_mci procedure :: call_dispatch_mci => pcm_default_call_dispatch_mci <>= subroutine pcm_default_setup_mci (pcm, mci_entry) class(pcm_default_t), intent(inout) :: pcm type(process_mci_entry_t), & dimension(:), allocatable, intent(out) :: mci_entry class(mci_t), allocatable :: mci_template integer :: i, i_mci pcm%n_mci = count (pcm%component_active) allocate (pcm%i_mci (pcm%n_components), source = 0) i_mci = 0 do i = 1, pcm%n_components if (pcm%component_active(i)) then i_mci = i_mci + 1 pcm%i_mci(i) = i_mci end if end do allocate (mci_entry (pcm%n_mci)) end subroutine pcm_default_setup_mci subroutine pcm_default_call_dispatch_mci (pcm, & dispatch_mci, var_list, process_id, mci_template) class(pcm_default_t), intent(inout) :: pcm procedure(dispatch_mci_proc) :: dispatch_mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id class(mci_t), allocatable, intent(out) :: mci_template call dispatch_mci (mci_template, var_list, process_id) end subroutine pcm_default_call_dispatch_mci @ %def pcm_default_setup_mci @ %def pcm_default_call_dispatch_mci @ Nothing left to do for the default algorithm. <>= procedure :: complete_setup => pcm_default_complete_setup <>= subroutine pcm_default_complete_setup (pcm, core_entry, component, model) class(pcm_default_t), intent(inout) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry type(process_component_t), dimension(:), intent(inout) :: component type(model_t), intent(in), target :: model end subroutine pcm_default_complete_setup @ %def pcm_default_complete_setup @ \subsubsection{Component management} Initialize a single component. We require all process-configuration blocks, and specific templates for the phase-space and integrator configuration. We also provide the current component index [[i]] and the [[active]] flag. In the default mode, all components are marked as master components. <>= procedure :: init_component => pcm_default_init_component <>= subroutine pcm_default_init_component & (pcm, component, i, active, & phs_config, env, meta, config) class(pcm_default_t), intent(in) :: pcm type(process_component_t), intent(out) :: component integer, intent(in) :: i logical, intent(in) :: active class(phs_config_t), allocatable, intent(in) :: phs_config type(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta type(process_config_data_t), intent(in) :: config call component%init (i, & env, meta, config, & active, & phs_config) component%component_type = COMP_MASTER end subroutine pcm_default_init_component @ %def pcm_default_init_component @ \subsection{NLO process component manager} The NLO-aware version of the process-component manager. This is the configuration object, which has the duty of allocating the corresponding instance. This is the nontrivial NLO version. <>= public :: pcm_nlo_t <>= type, extends (pcm_t) :: pcm_nlo_t type(string_t) :: id logical :: combined_integration = .false. logical :: vis_fks_regions = .false. integer, dimension(:), allocatable :: nlo_type integer, dimension(:), allocatable :: nlo_type_core integer, dimension(:), allocatable :: component_type integer :: i_born = 0 integer :: i_real = 0 integer :: i_sub = 0 type(nlo_settings_t) :: settings type(region_data_t) :: region_data logical :: use_real_partition = .false. logical :: use_real_singular = .false. real(default) :: real_partition_scale = 0 class(real_partition_t), allocatable :: real_partition type(dalitz_plot_t) :: dalitz_plot type(quantum_numbers_t), dimension(:,:), allocatable :: qn_real, qn_born contains <> end type pcm_nlo_t @ %def pcm_nlo_t @ Initialize configuration data, using environment variables. <>= procedure :: init => pcm_nlo_init <>= subroutine pcm_nlo_init (pcm, env, meta) class(pcm_nlo_t), intent(out) :: pcm type(process_metadata_t), intent(in) :: meta type(process_environment_t), intent(in) :: env type(var_list_t), pointer :: var_list type(fks_template_t) :: fks_template pcm%id = meta%id pcm%has_pdfs = env%has_pdfs () var_list => env%get_var_list_ptr () call dispatch_fks_s (fks_template, var_list) call pcm%settings%init (var_list, fks_template) pcm%combined_integration = & var_list%get_lval (var_str ('?combined_nlo_integration')) select case (char (var_list%get_sval (var_str ("$real_partition_mode")))) case ("default", "off") pcm%use_real_partition = .false. pcm%use_real_singular = .false. case ("all", "on", "singular") pcm%use_real_partition = .true. pcm%use_real_singular = .true. case ("finite") pcm%use_real_partition = .true. pcm%use_real_singular = .false. case default call msg_fatal ("The real partition mode can only be " // & "default, off, all, on, singular or finite.") end select pcm%real_partition_scale = & var_list%get_rval (var_str ("real_partition_scale")) pcm%vis_fks_regions = & var_list%get_lval (var_str ("?vis_fks_regions")) call pcm%set_blha_defaults & (env%has_polarized_beams (), env%get_var_list_ptr ()) pcm%os_data = env%get_os_data () end subroutine pcm_nlo_init @ %def pcm_nlo_init @ Init/rewrite NLO settings without the FKS template. <>= procedure :: init_nlo_settings => pcm_nlo_init_nlo_settings <>= subroutine pcm_nlo_init_nlo_settings (pcm, var_list) class(pcm_nlo_t), intent(inout) :: pcm type(var_list_t), intent(in), target :: var_list call pcm%settings%init (var_list) end subroutine pcm_nlo_init_nlo_settings @ %def pcm_nlo_init_nlo_settings @ As appropriate for the NLO/FKS algorithm, the category defined by the process, is called [[nlo_type]]. We refine this by setting the component category [[component_type]] separately. The component types [[COMP_MISMATCH]], [[COMP_PDF]], [[COMP_SUB]] are set only if the algorithm uses combined integration. Otherwise, they are set to [[COMP_DEFAULT]]. The component type [[COMP_REAL]] is further distinguished between [[COMP_REAL_SING]] or [[COMP_REAL_FIN]], if the algorithm uses real partitions. The former acts as a reference component for the latter, and we always assume that it is the first real component. Each component is assigned its own core. Exceptions: the finite-real component gets the same core as the singular-real component. The mismatch component gets the same core as the subtraction component. TODO wk 2018: this convention for real components can be improved. Check whether all component types should be assigned, not just for combined integration. <>= procedure :: categorize_components => pcm_nlo_categorize_components <>= subroutine pcm_nlo_categorize_components (pcm, config) class(pcm_nlo_t), intent(inout) :: pcm type(process_config_data_t), intent(in) :: config type(process_component_def_t), pointer :: component_def integer :: i allocate (pcm%nlo_type (pcm%n_components), source = COMPONENT_UNDEFINED) allocate (pcm%component_type (pcm%n_components), source = COMP_DEFAULT) do i = 1, pcm%n_components component_def => config%process_def%get_component_def_ptr (i) pcm%nlo_type(i) = component_def%get_nlo_type () if (pcm%combined_integration) then select case (pcm%nlo_type(i)) case (BORN) pcm%i_born = i pcm%component_type(i) = COMP_MASTER case (NLO_REAL) pcm%component_type(i) = COMP_REAL case (NLO_VIRTUAL) pcm%component_type(i) = COMP_VIRT case (NLO_MISMATCH) pcm%component_type(i) = COMP_MISMATCH case (NLO_DGLAP) pcm%component_type(i) = COMP_PDF case (NLO_SUBTRACTION) pcm%component_type(i) = COMP_SUB pcm%i_sub = i end select else select case (pcm%nlo_type(i)) case (BORN) pcm%i_born = i pcm%component_type(i) = COMP_MASTER case (NLO_REAL) pcm%component_type(i) = COMP_REAL case (NLO_VIRTUAL) pcm%component_type(i) = COMP_VIRT case (NLO_MISMATCH) pcm%component_type(i) = COMP_MISMATCH case (NLO_SUBTRACTION) pcm%i_sub = i end select end if end do call refine_real_type ( & pack ([(i, i=1, pcm%n_components)], & pcm%component_type==COMP_REAL)) contains subroutine refine_real_type (i_real) integer, dimension(:), intent(in) :: i_real pcm%i_real = i_real(1) if (pcm%use_real_partition) then pcm%component_type (i_real(1)) = COMP_REAL_SING pcm%component_type (i_real(2:)) = COMP_REAL_FIN end if end subroutine refine_real_type end subroutine pcm_nlo_categorize_components @ %def pcm_nlo_categorize_components @ \subsubsection{Phase-space initial configuration} Setup for the NLO/PHS processes: two phase-space configurations, (1) Born/wood, (2) real correction/FKS. All components use either one of these two configurations. TODO wk 2018: The [[first_real_component]] identifier is really ugly. Nothing should rely on the ordering. <>= procedure :: init_phs_config => pcm_nlo_init_phs_config <>= subroutine pcm_nlo_init_phs_config & (pcm, phs_entry, meta, env, phs_par, mapping_defs) class(pcm_nlo_t), intent(inout) :: pcm type(process_phs_config_t), & dimension(:), allocatable, intent(out) :: phs_entry type(process_metadata_t), intent(in) :: meta type(process_environment_t), intent(in) :: env type(mapping_defaults_t), intent(in) :: mapping_defs type(phs_parameters_t), intent(in) :: phs_par integer :: i logical :: first_real_component allocate (phs_entry (2)) call dispatch_phs (phs_entry(1)%phs_config, & env%get_var_list_ptr (), & env%get_os_data (), & meta%id, & mapping_defs, phs_par, & var_str ("wood")) call dispatch_phs (phs_entry(2)%phs_config, & env%get_var_list_ptr (), & env%get_os_data (), & meta%id, & mapping_defs, phs_par, & var_str ("fks")) allocate (pcm%i_phs_config (pcm%n_components), source=0) first_real_component = .true. do i = 1, pcm%n_components select case (pcm%nlo_type(i)) case (BORN, NLO_VIRTUAL, NLO_SUBTRACTION) pcm%i_phs_config(i) = 1 case (NLO_REAL) if (pcm%use_real_partition) then if (pcm%use_real_singular) then if (first_real_component) then pcm%i_phs_config(i) = 2 first_real_component = .false. else pcm%i_phs_config(i) = 1 end if else pcm%i_phs_config(i) = 1 end if else pcm%i_phs_config(i) = 2 end if case (NLO_MISMATCH, NLO_DGLAP, GKS) pcm%i_phs_config(i) = 2 end select end do end subroutine pcm_nlo_init_phs_config @ %def pcm_nlo_init_phs_config @ \subsubsection{Core management} Allocate the core (matrix-element interface) objects that we will need for evaluation. Every component gets an associated core, except for the real-finite and mismatch components (if any). Those components are associated with their previous corresponding real-singular and subtraction cores, respectively. After cores are allocated, configure the region-data block that is maintained by the NLO process-component manager. <>= procedure :: allocate_cores => pcm_nlo_allocate_cores <>= subroutine pcm_nlo_allocate_cores (pcm, config, core_entry) class(pcm_nlo_t), intent(inout) :: pcm type(process_config_data_t), intent(in) :: config type(core_entry_t), dimension(:), allocatable, intent(out) :: core_entry type(process_component_def_t), pointer :: component_def integer :: i, i_core allocate (pcm%i_core (pcm%n_components), source = 0) pcm%n_cores = pcm%n_components & - count (pcm%component_type(:) == COMP_REAL_FIN) & - count (pcm%component_type(:) == COMP_MISMATCH) allocate (core_entry (pcm%n_cores)) allocate (pcm%nlo_type_core (pcm%n_cores), source = BORN) i_core = 0 do i = 1, pcm%n_components select case (pcm%component_type(i)) case default i_core = i_core + 1 pcm%i_core(i) = i_core pcm%nlo_type_core(i_core) = pcm%nlo_type(i) core_entry(i_core)%i_component = i component_def => config%process_def%get_component_def_ptr (i) core_entry(i_core)%core_def => component_def%get_core_def_ptr () select case (pcm%nlo_type(i)) case default core_entry(i)%active = component_def%can_be_integrated () case (NLO_REAL, NLO_SUBTRACTION) core_entry(i)%active = .true. end select case (COMP_REAL_FIN) pcm%i_core(i) = pcm%i_core(pcm%i_real) case (COMP_MISMATCH) pcm%i_core(i) = pcm%i_core(pcm%i_sub) end select end do end subroutine pcm_nlo_allocate_cores @ %def pcm_nlo_allocate_cores @ Extra code is required for certain core types (threshold) or if BLHA uses an external OLP for getting its matrix elements. OMega matrix elements, by definition, do not need extra code. NLO-virtual or subtraction matrix elements always need extra code. More precisely: for the Born and virtual matrix element, the extra code is accessed only if the component is active. The radiation (real) and the subtraction corrections (singular and finite), extra code is accessed in any case. The flavor state is taken from the [[region_data]] table in the [[pcm]] record. We use the Born and real flavor-state tables as appropriate. <>= procedure :: prepare_any_external_code => & pcm_nlo_prepare_any_external_code <>= subroutine pcm_nlo_prepare_any_external_code & (pcm, core_entry, i_core, libname, model, var_list) class(pcm_nlo_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry integer, intent(in) :: i_core type(string_t), intent(in) :: libname type(model_data_t), intent(in), target :: model type(var_list_t), intent(in) :: var_list integer, dimension(:,:), allocatable :: flv_born, flv_real integer :: i call pcm%region_data%get_all_flv_states (flv_born, flv_real) if (core_entry%active) then associate (core => core_entry%core) if (core%needs_external_code ()) then select case (pcm%nlo_type (core_entry%i_component)) case default call core%data%set_flv_state (flv_born) case (NLO_REAL) call core%data%set_flv_state (flv_real) end select call core%prepare_external_code & (core%data%flv_state, & var_list, pcm%os_data, libname, model, i_core, .true.) end if call core%set_equivalent_flv_hel_indices () end associate end if end subroutine pcm_nlo_prepare_any_external_code @ %def pcm_nlo_prepare_any_external_code @ Allocate and configure the BLHA record for a specific core, assuming that the core type requires it. The configuration depends on the NLO type of the core. <>= procedure :: setup_blha => pcm_nlo_setup_blha <>= subroutine pcm_nlo_setup_blha (pcm, core_entry) class(pcm_nlo_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry allocate (core_entry%blha_config, source = pcm%blha_defaults) select case (pcm%nlo_type(core_entry%i_component)) case (BORN) call core_entry%blha_config%set_born () case (NLO_REAL) call core_entry%blha_config%set_real_trees () case (NLO_VIRTUAL) call core_entry%blha_config%set_loop () case (NLO_SUBTRACTION) call core_entry%blha_config%set_subtraction () call core_entry%blha_config%set_internal_color_correlations () case (NLO_DGLAP) call core_entry%blha_config%set_dglap () end select end subroutine pcm_nlo_setup_blha @ %def pcm_nlo_setup_blha @ After phase-space configuration data and core entries are available, we fill tables and compute the remaining NLO data that will steer the integration and subtraction algorithm. There are three parts: recognize a threshold-type process core (if it exists), prepare the region-data tables (always), and prepare for real partitioning (if requested). The real-component phase space acts as the source for resonance-history information, required for the region data. <>= procedure :: complete_setup => pcm_nlo_complete_setup <>= subroutine pcm_nlo_complete_setup (pcm, core_entry, component, model) class(pcm_nlo_t), intent(inout) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry type(process_component_t), dimension(:), intent(inout) :: component type(model_t), intent(in), target :: model integer :: i call pcm%handle_threshold_core (core_entry) call pcm%setup_region_data & (core_entry, component(pcm%i_real)%phs_config, model) call pcm%setup_real_partition () end subroutine pcm_nlo_complete_setup @ %def pcm_nlo_complete_setup @ Apply the BLHA configuration to a core object, using the region data from [[pcm]] for determining the particle content. <>= procedure :: prepare_blha_core => pcm_nlo_prepare_blha_core <>= subroutine pcm_nlo_prepare_blha_core (pcm, core_entry, model) class(pcm_nlo_t), intent(in) :: pcm type(core_entry_t), intent(inout) :: core_entry class(model_data_t), intent(in), target :: model integer :: n_in integer :: n_legs integer :: n_flv integer :: n_hel select type (core => core_entry%core) class is (prc_blha_t) associate (blha_config => core_entry%blha_config) n_in = core%data%n_in select case (pcm%nlo_type(core_entry%i_component)) case (NLO_REAL) n_legs = pcm%region_data%get_n_legs_real () n_flv = pcm%region_data%get_n_flv_real () case default n_legs = pcm%region_data%get_n_legs_born () n_flv = pcm%region_data%get_n_flv_born () end select n_hel = blha_config%get_n_hel (core%data%flv_state (1:n_in,1), model) call core%init_blha (blha_config, n_in, n_legs, n_flv, n_hel) call core%init_driver (pcm%os_data) end associate end select end subroutine pcm_nlo_prepare_blha_core @ %def pcm_nlo_prepare_blha_core @ Read the method settings from the variable list and store them in the BLHA master. This version: NLO flag set. <>= procedure :: set_blha_methods => pcm_nlo_set_blha_methods <>= subroutine pcm_nlo_set_blha_methods (pcm, blha_master, var_list) class(pcm_nlo_t), intent(inout) :: pcm type(blha_master_t), intent(inout) :: blha_master type(var_list_t), intent(in) :: var_list call blha_master%set_methods (.true., var_list) call pcm%blha_defaults%set_loop_method (blha_master) end subroutine pcm_nlo_set_blha_methods @ %def pcm_nlo_set_blha_methods @ Produce the LO and NLO flavor-state tables (as far as available), as appropriate for BLHA configuration. The NLO version copies the tables from the region data inside [[pcm]]. The core array is not needed. <>= procedure :: get_blha_flv_states => pcm_nlo_get_blha_flv_states <>= subroutine pcm_nlo_get_blha_flv_states & (pcm, core_entry, flv_born, flv_real) class(pcm_nlo_t), intent(in) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry integer, dimension(:,:), allocatable, intent(out) :: flv_born integer, dimension(:,:), allocatable, intent(out) :: flv_real call pcm%region_data%get_all_flv_states (flv_born, flv_real) end subroutine pcm_nlo_get_blha_flv_states @ %def pcm_nlo_get_blha_flv_states @ Allocate and configure the MCI (multi-channel integrator) records. The relation depends on the [[combined_integration]] setting. If we integrate components separately, each component gets its own record, except for the subtraction component. If we do the combination, there is one record for the master (Born) component and a second one for the real-finite component, if present. Each entry acquires some NLO-specific initialization. Generic configuration follows later. Second procedure: call the MCI dispatcher with NLO-setup arguments. <>= procedure :: setup_mci => pcm_nlo_setup_mci procedure :: call_dispatch_mci => pcm_nlo_call_dispatch_mci <>= subroutine pcm_nlo_setup_mci (pcm, mci_entry) class(pcm_nlo_t), intent(inout) :: pcm type(process_mci_entry_t), & dimension(:), allocatable, intent(out) :: mci_entry class(mci_t), allocatable :: mci_template integer :: i, i_mci if (pcm%combined_integration) then pcm%n_mci = 1 & + count (pcm%component_active(:) & & .and. pcm%component_type(:) == COMP_REAL_FIN) allocate (pcm%i_mci (pcm%n_components), source = 0) do i = 1, pcm%n_components if (pcm%component_active(i)) then select case (pcm%component_type(i)) case (COMP_MASTER) pcm%i_mci(i) = 1 case (COMP_REAL_FIN) pcm%i_mci(i) = 2 end select end if end do else pcm%n_mci = count (pcm%component_active(:) & & .and. pcm%nlo_type(:) /= NLO_SUBTRACTION) allocate (pcm%i_mci (pcm%n_components), source = 0) i_mci = 0 do i = 1, pcm%n_components if (pcm%component_active(i)) then select case (pcm%nlo_type(i)) case default i_mci = i_mci + 1 pcm%i_mci(i) = i_mci case (NLO_SUBTRACTION) end select end if end do end if allocate (mci_entry (pcm%n_mci)) mci_entry(:)%combined_integration = pcm%combined_integration if (pcm%use_real_partition) then do i = 1, pcm%n_components i_mci = pcm%i_mci(i) if (i_mci > 0) then select case (pcm%component_type(i)) case (COMP_REAL_FIN) mci_entry(i_mci)%real_partition_type = REAL_FINITE case default mci_entry(i_mci)%real_partition_type = REAL_SINGULAR end select end if end do end if end subroutine pcm_nlo_setup_mci subroutine pcm_nlo_call_dispatch_mci (pcm, & dispatch_mci, var_list, process_id, mci_template) class(pcm_nlo_t), intent(inout) :: pcm procedure(dispatch_mci_proc) :: dispatch_mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id class(mci_t), allocatable, intent(out) :: mci_template call dispatch_mci (mci_template, var_list, process_id, is_nlo = .true.) end subroutine pcm_nlo_call_dispatch_mci @ %def pcm_nlo_setup_mci @ %def pcm_nlo_call_dispatch_mci @ Check for a threshold core and adjust the configuration accordingly, before singular region data are considered. <>= procedure :: handle_threshold_core => pcm_nlo_handle_threshold_core <>= subroutine pcm_nlo_handle_threshold_core (pcm, core_entry) class(pcm_nlo_t), intent(inout) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry integer :: i do i = 1, size (core_entry) select type (core => core_entry(i)%core_def) type is (threshold_def_t) pcm%settings%factorization_mode = FACTORIZATION_THRESHOLD return end select end do end subroutine pcm_nlo_handle_threshold_core @ %def pcm_nlo_handle_threshold_core @ Configure the singular-region tables based on the process data for the Born and Real (singular) cores, using also the appropriate FKS phase-space configuration object. In passing, we may create a table of resonance histories that are relevant for the singular-region configuration. TODO wk 2018: check whether [[phs_entry]] needs to be intent(inout). <>= procedure :: setup_region_data => pcm_nlo_setup_region_data <>= subroutine pcm_nlo_setup_region_data (pcm, core_entry, phs_config, model) class(pcm_nlo_t), intent(inout) :: pcm type(core_entry_t), dimension(:), intent(in) :: core_entry class(phs_config_t), intent(inout) :: phs_config type(model_t), intent(in), target :: model type(process_constants_t) :: data_born, data_real integer, dimension (:,:), allocatable :: flavor_born, flavor_real type(resonance_history_t), dimension(:), allocatable :: resonance_histories type(var_list_t), pointer :: var_list logical :: success data_born = core_entry(pcm%i_core(pcm%i_born))%core%data data_real = core_entry(pcm%i_core(pcm%i_real))%core%data call data_born%get_flv_state (flavor_born) call data_real%get_flv_state (flavor_real) call pcm%region_data%init & (data_born%n_in, model, flavor_born, flavor_real, & pcm%settings%nlo_correction_type) associate (template => pcm%settings%fks_template) if (template%mapping_type == FKS_RESONANCES) then select type (phs_config) type is (phs_fks_config_t) call get_filtered_resonance_histories (phs_config, & data_born%n_in, flavor_born, model, & template%excluded_resonances, & resonance_histories, success) end select if (.not. success) template%mapping_type = FKS_DEFAULT end if call pcm%region_data%setup_fks_mappings (template, data_born%n_in) !!! Check again, mapping_type might have changed if (template%mapping_type == FKS_RESONANCES) then call pcm%region_data%set_resonance_mappings (resonance_histories) call pcm%region_data%init_resonance_information () pcm%settings%use_resonance_mappings = .true. end if end associate if (pcm%settings%factorization_mode == FACTORIZATION_THRESHOLD) then call pcm%region_data%set_isr_pseudo_regions () call pcm%region_data%split_up_interference_regions_for_threshold () end if call pcm%region_data%compute_number_of_phase_spaces () call pcm%region_data%set_i_phs_to_i_con () call pcm%region_data%write_to_file & (pcm%id, pcm%vis_fks_regions, pcm%os_data) if (debug_active (D_SUBTRACTION)) & call pcm%region_data%check_consistency (.true.) end subroutine pcm_nlo_setup_region_data @ %def pcm_nlo_setup_region_data @ After region data are set up, we allocate and configure the [[real_partition]] objects, if requested. <>= procedure :: setup_real_partition => pcm_nlo_setup_real_partition <>= subroutine pcm_nlo_setup_real_partition (pcm) class(pcm_nlo_t), intent(inout) :: pcm if (pcm%use_real_partition) then if (.not. allocated (pcm%real_partition)) then allocate (real_partition_fixed_order_t :: pcm%real_partition) select type (partition => pcm%real_partition) type is (real_partition_fixed_order_t) call pcm%region_data%get_all_ftuples (partition%fks_pairs) partition%scale = pcm%real_partition_scale end select end if end if end subroutine pcm_nlo_setup_real_partition @ %def pcm_nlo_setup_real_partition @ Initialize a single component. We require all process-configuration blocks, and specific templates for the phase-space and integrator configuration. We also provide the current component index [[i]] and the [[active]] flag. For a subtraction component, the [[active]] flag is overridden. In the nlo mode, the component types have been determined before. TODO wk 2018: the component type need not be stored in the component; we may remove this when everything is controlled by [[pcm]]. <>= procedure :: init_component => pcm_nlo_init_component <>= subroutine pcm_nlo_init_component & (pcm, component, i, active, & phs_config, env, meta, config) class(pcm_nlo_t), intent(in) :: pcm type(process_component_t), intent(out) :: component integer, intent(in) :: i logical, intent(in) :: active class(phs_config_t), allocatable, intent(in) :: phs_config type(process_environment_t), intent(in) :: env type(process_metadata_t), intent(in) :: meta type(process_config_data_t), intent(in) :: config logical :: activate select case (pcm%nlo_type(i)) case default; activate = active case (NLO_SUBTRACTION); activate = .false. end select call component%init (i, & env, meta, config, & activate, & phs_config) component%component_type = pcm%component_type(i) end subroutine pcm_nlo_init_component @ %def pcm_nlo_init_component @ Override the base method: record the active components in the PCM object, and report inactive components (except for the subtraction component). <>= procedure :: record_inactive_components => pcm_nlo_record_inactive_components <>= subroutine pcm_nlo_record_inactive_components (pcm, component, meta) class(pcm_nlo_t), intent(inout) :: pcm type(process_component_t), dimension(:), intent(in) :: component type(process_metadata_t), intent(inout) :: meta integer :: i pcm%component_active = component%active do i = 1, pcm%n_components select case (pcm%nlo_type(i)) case (NLO_SUBTRACTION) case default if (.not. component(i)%active) call meta%deactivate_component (i) end select end do end subroutine pcm_nlo_record_inactive_components @ %def pcm_nlo_record_inactive_components @ <>= procedure :: core_is_radiation => pcm_nlo_core_is_radiation <>= function pcm_nlo_core_is_radiation (pcm, i_core) result (is_rad) logical :: is_rad class(pcm_nlo_t), intent(in) :: pcm integer, intent(in) :: i_core is_rad = pcm%nlo_type(i_core) == NLO_REAL ! .and. .not. pcm%cm%sub(i_core) end function pcm_nlo_core_is_radiation @ %def pcm_nlo_core_is_radiation @ <>= procedure :: get_n_flv_born => pcm_nlo_get_n_flv_born <>= function pcm_nlo_get_n_flv_born (pcm_nlo) result (n_flv) integer :: n_flv class(pcm_nlo_t), intent(in) :: pcm_nlo n_flv = pcm_nlo%region_data%n_flv_born end function pcm_nlo_get_n_flv_born @ %def pcm_nlo_get_n_flv_born @ <>= procedure :: get_n_flv_real => pcm_nlo_get_n_flv_real <>= function pcm_nlo_get_n_flv_real (pcm_nlo) result (n_flv) integer :: n_flv class(pcm_nlo_t), intent(in) :: pcm_nlo n_flv = pcm_nlo%region_data%n_flv_real end function pcm_nlo_get_n_flv_real @ %def pcm_nlo_get_n_flv_real @ <>= procedure :: get_n_alr => pcm_nlo_get_n_alr <>= function pcm_nlo_get_n_alr (pcm) result (n_alr) integer :: n_alr class(pcm_nlo_t), intent(in) :: pcm n_alr = pcm%region_data%n_regions end function pcm_nlo_get_n_alr @ %def pcm_nlo_get_n_alr @ <>= procedure :: get_flv_states => pcm_nlo_get_flv_states <>= function pcm_nlo_get_flv_states (pcm, born) result (flv) integer, dimension(:,:), allocatable :: flv class(pcm_nlo_t), intent(in) :: pcm logical, intent(in) :: born if (born) then flv = pcm%region_data%get_flv_states_born () else flv = pcm%region_data%get_flv_states_real () end if end function pcm_nlo_get_flv_states @ %def pcm_nlo_get_flv_states @ <>= procedure :: get_qn => pcm_nlo_get_qn <>= function pcm_nlo_get_qn (pcm, born) result (qn) type(quantum_numbers_t), dimension(:,:), allocatable :: qn class(pcm_nlo_t), intent(in) :: pcm logical, intent(in) :: born if (born) then qn = pcm%qn_born else qn = pcm%qn_real end if end function pcm_nlo_get_qn @ %def pcm_nlo_get_qn @ Check if there are massive emitters. Since the mass-structure of all underlying Born configurations have to be the same (\textbf{This does not have to be the case when different components are generated at LO}) , we just use the first one to determine this. <>= procedure :: has_massive_emitter => pcm_nlo_has_massive_emitter <>= function pcm_nlo_has_massive_emitter (pcm) result (val) logical :: val class(pcm_nlo_t), intent(in) :: pcm integer :: i val = .false. associate (reg_data => pcm%region_data) do i = reg_data%n_in + 1, reg_data%n_legs_born if (any (i == reg_data%emitters)) & val = val .or. reg_data%flv_born(1)%massive(i) end do end associate end function pcm_nlo_has_massive_emitter @ %def pcm_nlo_has_massive_emitter @ Returns an array which specifies if the particle at position [[i]] is massive. <>= procedure :: get_mass_info => pcm_nlo_get_mass_info <>= function pcm_nlo_get_mass_info (pcm, i_flv) result (massive) class(pcm_nlo_t), intent(in) :: pcm integer, intent(in) :: i_flv logical, dimension(:), allocatable :: massive allocate (massive (size (pcm%region_data%flv_born(i_flv)%massive))) massive = pcm%region_data%flv_born(i_flv)%massive end function pcm_nlo_get_mass_info @ %def pcm_nlo_get_mass_info @ <>= procedure :: allocate_instance => pcm_nlo_allocate_instance <>= subroutine pcm_nlo_allocate_instance (pcm, instance) class(pcm_nlo_t), intent(in) :: pcm class(pcm_instance_t), intent(inout), allocatable :: instance allocate (pcm_instance_nlo_t :: instance) end subroutine pcm_nlo_allocate_instance @ %def pcm_nlo_allocate_instance @ <>= procedure :: init_qn => pcm_nlo_init_qn <>= subroutine pcm_nlo_init_qn (pcm, model) class(pcm_nlo_t), intent(inout) :: pcm class(model_data_t), intent(in) :: model integer, dimension(:,:), allocatable :: flv_states type(flavor_t), dimension(:), allocatable :: flv integer :: i type(quantum_numbers_t), dimension(:), allocatable :: qn allocate (flv_states (pcm%region_data%n_legs_born, pcm%region_data%n_flv_born)) flv_states = pcm%get_flv_states (.true.) allocate (pcm%qn_born (size (flv_states, dim = 1), size (flv_states, dim = 2))) allocate (flv (size (flv_states, dim = 1))) allocate (qn (size (flv_states, dim = 1))) do i = 1, pcm%get_n_flv_born () call flv%init (flv_states (:,i), model) call qn%init (flv) pcm%qn_born(:,i) = qn end do deallocate (flv); deallocate (qn) deallocate (flv_states) allocate (flv_states (pcm%region_data%n_legs_real, pcm%region_data%n_flv_real)) flv_states = pcm%get_flv_states (.false.) allocate (pcm%qn_real (size (flv_states, dim = 1), size (flv_states, dim = 2))) allocate (flv (size (flv_states, dim = 1))) allocate (qn (size (flv_states, dim = 1))) do i = 1, pcm%get_n_flv_real () call flv%init (flv_states (:,i), model) call qn%init (flv) pcm%qn_real(:,i) = qn end do end subroutine pcm_nlo_init_qn @ %def pcm_nlo_init_qn @ <>= procedure :: allocate_ps_matching => pcm_nlo_allocate_ps_matching <>= subroutine pcm_nlo_allocate_ps_matching (pcm) class(pcm_nlo_t), intent(inout) :: pcm if (.not. allocated (pcm%real_partition)) then allocate (powheg_damping_simple_t :: pcm%real_partition) end if end subroutine pcm_nlo_allocate_ps_matching @ %def pcm_nlo_allocate_ps_matching @ <>= procedure :: activate_dalitz_plot => pcm_nlo_activate_dalitz_plot <>= subroutine pcm_nlo_activate_dalitz_plot (pcm, filename) class(pcm_nlo_t), intent(inout) :: pcm type(string_t), intent(in) :: filename call pcm%dalitz_plot%init (free_unit (), filename, .false.) call pcm%dalitz_plot%write_header () end subroutine pcm_nlo_activate_dalitz_plot @ %def pcm_nlo_activate_dalitz_plot @ <>= procedure :: register_dalitz_plot => pcm_nlo_register_dalitz_plot <>= subroutine pcm_nlo_register_dalitz_plot (pcm, emitter, p) class(pcm_nlo_t), intent(inout) :: pcm integer, intent(in) :: emitter type(vector4_t), intent(in), dimension(:) :: p real(default) :: k0_n, k0_np1 k0_n = p(emitter)%p(0) k0_np1 = p(size(p))%p(0) call pcm%dalitz_plot%register (k0_n, k0_np1) end subroutine pcm_nlo_register_dalitz_plot @ %def pcm_nlo_register_dalitz_plot @ <>= procedure :: setup_phs_generator => pcm_nlo_setup_phs_generator <>= subroutine pcm_nlo_setup_phs_generator (pcm, pcm_instance, generator, & sqrts, mode, singular_jacobian) class(pcm_nlo_t), intent(in) :: pcm type(phs_fks_generator_t), intent(inout) :: generator type(pcm_instance_nlo_t), intent(in), target :: pcm_instance real(default), intent(in) :: sqrts integer, intent(in), optional:: mode logical, intent(in), optional :: singular_jacobian logical :: yorn yorn = .false.; if (present (singular_jacobian)) yorn = singular_jacobian call generator%connect_kinematics (pcm_instance%isr_kinematics, & pcm_instance%real_kinematics, pcm%has_massive_emitter ()) generator%n_in = pcm%region_data%n_in call generator%set_sqrts_hat (sqrts) call generator%set_emitters (pcm%region_data%emitters) call generator%setup_masses (pcm%region_data%n_legs_born) generator%is_massive = pcm%get_mass_info (1) generator%singular_jacobian = yorn if (present (mode)) generator%mode = mode call generator%set_xi_and_y_bounds (pcm%settings%fks_template%xi_min, & pcm%settings%fks_template%y_max) end subroutine pcm_nlo_setup_phs_generator @ %def pcm_nlo_setup_phs_generator @ <>= procedure :: final => pcm_nlo_final <>= subroutine pcm_nlo_final (pcm) class(pcm_nlo_t), intent(inout) :: pcm if (allocated (pcm%real_partition)) deallocate (pcm%real_partition) call pcm%dalitz_plot%final () end subroutine pcm_nlo_final @ %def pcm_nlo_final @ <>= procedure :: is_nlo => pcm_nlo_is_nlo <>= function pcm_nlo_is_nlo (pcm) result (is_nlo) logical :: is_nlo class(pcm_nlo_t), intent(in) :: pcm is_nlo = .true. end function pcm_nlo_is_nlo @ %def pcm_nlo_is_nlo @ As a first implementation, it acts as a wrapper for the NLO controller object and the squared matrix-element collector. <>= public :: pcm_instance_nlo_t <>= type, extends (pcm_instance_t) :: pcm_instance_nlo_t type(real_kinematics_t), pointer :: real_kinematics => null () type(isr_kinematics_t), pointer :: isr_kinematics => null () type(real_subtraction_t) :: real_sub type(virtual_t) :: virtual type(soft_mismatch_t) :: soft_mismatch type(dglap_remnant_t) :: dglap_remnant integer, dimension(:), allocatable :: i_mci_to_real_component contains <> end type pcm_instance_nlo_t @ %def pcm_instance_nlo_t @ <>= procedure :: set_radiation_event => pcm_instance_nlo_set_radiation_event procedure :: set_subtraction_event => pcm_instance_nlo_set_subtraction_event <>= subroutine pcm_instance_nlo_set_radiation_event (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance pcm_instance%real_sub%radiation_event = .true. pcm_instance%real_sub%subtraction_event = .false. end subroutine pcm_instance_nlo_set_radiation_event subroutine pcm_instance_nlo_set_subtraction_event (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance pcm_instance%real_sub%radiation_event = .false. pcm_instance%real_sub%subtraction_event = .true. end subroutine pcm_instance_nlo_set_subtraction_event @ %def pcm_instance_nlo_set_radiation_event @ %def pcm_instance_nlo_set_subtraction_event <>= procedure :: disable_subtraction => pcm_instance_nlo_disable_subtraction <>= subroutine pcm_instance_nlo_disable_subtraction (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance pcm_instance%real_sub%subtraction_deactivated = .true. end subroutine pcm_instance_nlo_disable_subtraction @ %def pcm_instance_nlo_disable_subtraction @ <>= procedure :: init_config => pcm_instance_nlo_init_config <>= subroutine pcm_instance_nlo_init_config (pcm_instance, active_components, & nlo_types, energy, i_real_fin, model) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance logical, intent(in), dimension(:) :: active_components integer, intent(in), dimension(:) :: nlo_types real(default), intent(in), dimension(:) :: energy integer, intent(in) :: i_real_fin class(model_data_t), intent(in) :: model integer :: i_component if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, "pcm_instance_nlo_init_config") call pcm_instance%init_real_and_isr_kinematics (energy) select type (pcm => pcm_instance%config) type is (pcm_nlo_t) do i_component = 1, size (active_components) if (active_components(i_component) .or. pcm%settings%combined_integration) then select case (nlo_types(i_component)) case (NLO_REAL) if (i_component /= i_real_fin) then call pcm_instance%setup_real_component & (pcm%settings%fks_template%subtraction_disabled) end if case (NLO_VIRTUAL) call pcm_instance%init_virtual (model) case (NLO_MISMATCH) call pcm_instance%init_soft_mismatch () case (NLO_DGLAP) call pcm_instance%init_dglap_remnant () end select end if end do end select end subroutine pcm_instance_nlo_init_config @ %def pcm_instance_nlo_init_config @ <>= procedure :: setup_real_component => pcm_instance_nlo_setup_real_component <>= subroutine pcm_instance_nlo_setup_real_component (pcm_instance, & subtraction_disabled) class(pcm_instance_nlo_t), intent(inout), target :: pcm_instance logical, intent(in) :: subtraction_disabled call pcm_instance%init_real_subtraction () if (subtraction_disabled) call pcm_instance%disable_subtraction () end subroutine pcm_instance_nlo_setup_real_component @ %def pcm_instance_nlo_setup_real_component @ <>= procedure :: init_real_and_isr_kinematics => & pcm_instance_nlo_init_real_and_isr_kinematics <>= subroutine pcm_instance_nlo_init_real_and_isr_kinematics (pcm_instance, energy) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance real(default), dimension(:), intent(in) :: energy integer :: n_contr allocate (pcm_instance%real_kinematics) allocate (pcm_instance%isr_kinematics) select type (config => pcm_instance%config) type is (pcm_nlo_t) associate (region_data => config%region_data) if (allocated (region_data%alr_contributors)) then n_contr = size (region_data%alr_contributors) else if (config%settings%factorization_mode == FACTORIZATION_THRESHOLD) then n_contr = 2 else n_contr = 1 end if call pcm_instance%real_kinematics%init & (region_data%n_legs_real, region_data%n_phs, & region_data%n_regions, n_contr) if (config%settings%factorization_mode == FACTORIZATION_THRESHOLD) & call pcm_instance%real_kinematics%init_onshell & (region_data%n_legs_real, region_data%n_phs) pcm_instance%isr_kinematics%n_in = region_data%n_in end associate end select pcm_instance%isr_kinematics%beam_energy = energy end subroutine pcm_instance_nlo_init_real_and_isr_kinematics @ %def pcm_instance_nlo_init_real_and_isr_kinematics @ <>= procedure :: set_real_and_isr_kinematics => & pcm_instance_nlo_set_real_and_isr_kinematics <>= subroutine pcm_instance_nlo_set_real_and_isr_kinematics (pcm_instance, phs_identifiers, sqrts) class(pcm_instance_nlo_t), intent(inout), target :: pcm_instance type(phs_identifier_t), intent(in), dimension(:) :: phs_identifiers real(default), intent(in) :: sqrts call pcm_instance%real_sub%set_real_kinematics & (pcm_instance%real_kinematics) call pcm_instance%real_sub%set_isr_kinematics & (pcm_instance%isr_kinematics) end subroutine pcm_instance_nlo_set_real_and_isr_kinematics @ %def pcm_instance_nlo_set_real_and_isr_kinematics @ <>= procedure :: init_real_subtraction => pcm_instance_nlo_init_real_subtraction <>= subroutine pcm_instance_nlo_init_real_subtraction (pcm_instance) class(pcm_instance_nlo_t), intent(inout), target :: pcm_instance select type (config => pcm_instance%config) type is (pcm_nlo_t) associate (region_data => config%region_data) call pcm_instance%real_sub%init (region_data, config%settings) if (allocated (config%settings%selected_alr)) then associate (selected_alr => config%settings%selected_alr) if (any (selected_alr < 0)) then call msg_fatal ("Fixed alpha region must be non-negative!") else if (any (selected_alr > region_data%n_regions)) then call msg_fatal ("Fixed alpha region is larger than the total"& &" number of singular regions!") else allocate (pcm_instance%real_sub%selected_alr (size (selected_alr))) pcm_instance%real_sub%selected_alr = selected_alr end if end associate end if end associate end select end subroutine pcm_instance_nlo_init_real_subtraction @ %def pcm_instance_nlo_init_real_subtraction @ <>= procedure :: set_momenta_and_scales_virtual => & pcm_instance_nlo_set_momenta_and_scales_virtual <>= subroutine pcm_instance_nlo_set_momenta_and_scales_virtual (pcm_instance, p, & ren_scale, fac_scale, es_scale) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance type(vector4_t), intent(in), dimension(:) :: p real(default), intent(in) :: ren_scale, fac_scale, es_scale select type (config => pcm_instance%config) type is (pcm_nlo_t) associate (virtual => pcm_instance%virtual) call virtual%set_ren_scale (p, ren_scale) call virtual%set_fac_scale (p, fac_scale) call virtual%set_ellis_sexton_scale (es_scale) end associate end select end subroutine pcm_instance_nlo_set_momenta_and_scales_virtual @ %def pcm_instance_nlo_set_momenta_and_scales_virtual @ <>= procedure :: set_fac_scale => pcm_instance_nlo_set_fac_scale <>= subroutine pcm_instance_nlo_set_fac_scale (pcm_instance, fac_scale) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance real(default), intent(in) :: fac_scale pcm_instance%isr_kinematics%fac_scale = fac_scale end subroutine pcm_instance_nlo_set_fac_scale @ %def pcm_instance_nlo_set_fac_scale @ <>= procedure :: set_momenta => pcm_instance_nlo_set_momenta <>= subroutine pcm_instance_nlo_set_momenta (pcm_instance, p_born, p_real, i_phs, cms) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance type(vector4_t), dimension(:), intent(in) :: p_born, p_real integer, intent(in) :: i_phs logical, intent(in), optional :: cms logical :: yorn yorn = .false.; if (present (cms)) yorn = cms associate (kinematics => pcm_instance%real_kinematics) if (yorn) then if (.not. kinematics%p_born_cms%initialized) & call kinematics%p_born_cms%init (size (p_born), 1) if (.not. kinematics%p_real_cms%initialized) & call kinematics%p_real_cms%init (size (p_real), 1) kinematics%p_born_cms%phs_point(1)%p = p_born kinematics%p_real_cms%phs_point(i_phs)%p = p_real else if (.not. kinematics%p_born_lab%initialized) & call kinematics%p_born_lab%init (size (p_born), 1) if (.not. kinematics%p_real_lab%initialized) & call kinematics%p_real_lab%init (size (p_real), 1) kinematics%p_born_lab%phs_point(1)%p = p_born kinematics%p_real_lab%phs_point(i_phs)%p = p_real end if end associate end subroutine pcm_instance_nlo_set_momenta @ %def pcm_instance_nlo_set_momenta @ <>= procedure :: get_momenta => pcm_instance_nlo_get_momenta <>= function pcm_instance_nlo_get_momenta (pcm_instance, i_phs, born_phsp, cms) result (p) type(vector4_t), dimension(:), allocatable :: p class(pcm_instance_nlo_t), intent(in) :: pcm_instance integer, intent(in) :: i_phs logical, intent(in) :: born_phsp logical, intent(in), optional :: cms logical :: yorn yorn = .false.; if (present (cms)) yorn = cms select type (config => pcm_instance%config) type is (pcm_nlo_t) if (born_phsp) then if (yorn) then allocate (p (1 : config%region_data%n_legs_born), & source = pcm_instance%real_kinematics%p_born_cms%phs_point(1)%p) else allocate (p (1 : config%region_data%n_legs_born), & source = pcm_instance%real_kinematics%p_born_lab%phs_point(1)%p) end if else if (yorn) then allocate (p (1 : config%region_data%n_legs_real), & source = pcm_instance%real_kinematics%p_real_cms%phs_point(i_phs)%p) else allocate (p ( 1 : config%region_data%n_legs_real), & source = pcm_instance%real_kinematics%p_real_lab%phs_point(i_phs)%p) end if end if end select end function pcm_instance_nlo_get_momenta @ %def pcm_instance_nlo_get_momenta @ <>= procedure :: get_xi_max => pcm_instance_nlo_get_xi_max <>= function pcm_instance_nlo_get_xi_max (pcm_instance, alr) result (xi_max) real(default) :: xi_max class(pcm_instance_nlo_t), intent(in) :: pcm_instance integer, intent(in) :: alr integer :: i_phs i_phs = pcm_instance%real_kinematics%alr_to_i_phs (alr) xi_max = pcm_instance%real_kinematics%xi_max (i_phs) end function pcm_instance_nlo_get_xi_max @ %def pcm_instance_nlo_get_xi_max @ <>= procedure :: get_n_born => pcm_instance_nlo_get_n_born <>= function pcm_instance_nlo_get_n_born (pcm_instance) result (n_born) integer :: n_born class(pcm_instance_nlo_t), intent(in) :: pcm_instance select type (config => pcm_instance%config) type is (pcm_nlo_t) n_born = config%region_data%n_legs_born end select end function pcm_instance_nlo_get_n_born @ %def pcm_instance_nlo_get_n_born @ <>= procedure :: get_n_real => pcm_instance_nlo_get_n_real <>= function pcm_instance_nlo_get_n_real (pcm_instance) result (n_real) integer :: n_real class(pcm_instance_nlo_t), intent(in) :: pcm_instance select type (config => pcm_instance%config) type is (pcm_nlo_t) n_real = config%region_data%n_legs_real end select end function pcm_instance_nlo_get_n_real @ %def pcm_instance_nlo_get_n_real @ <>= procedure :: get_n_regions => pcm_instance_nlo_get_n_regions <>= function pcm_instance_nlo_get_n_regions (pcm_instance) result (n_regions) integer :: n_regions class(pcm_instance_nlo_t), intent(in) :: pcm_instance select type (config => pcm_instance%config) type is (pcm_nlo_t) n_regions = config%region_data%n_regions end select end function pcm_instance_nlo_get_n_regions @ %def pcm_instance_nlo_get_n_regions @ <>= procedure :: set_x_rad => pcm_instance_nlo_set_x_rad <>= subroutine pcm_instance_nlo_set_x_rad (pcm_instance, x_tot) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance real(default), intent(in), dimension(:) :: x_tot integer :: n_par n_par = size (x_tot) if (n_par < 3) then pcm_instance%real_kinematics%x_rad = zero else pcm_instance%real_kinematics%x_rad = x_tot (n_par - 2 : n_par) end if end subroutine pcm_instance_nlo_set_x_rad @ %def pcm_instance_nlo_set_x_rad @ <>= procedure :: init_virtual => pcm_instance_nlo_init_virtual <>= subroutine pcm_instance_nlo_init_virtual (pcm_instance, model) class(pcm_instance_nlo_t), intent(inout), target :: pcm_instance class(model_data_t), intent(in) :: model type(nlo_settings_t), pointer :: settings select type (config => pcm_instance%config) type is (pcm_nlo_t) associate (region_data => config%region_data) settings => config%settings call pcm_instance%virtual%init (region_data%get_flv_states_born (), & region_data%n_in, settings, & region_data%regions(1)%nlo_correction_type, model, config%has_pdfs) end associate end select end subroutine pcm_instance_nlo_init_virtual @ %def pcm_instance_nlo_init_virtual @ <>= procedure :: disable_virtual_subtraction => pcm_instance_nlo_disable_virtual_subtraction <>= subroutine pcm_instance_nlo_disable_virtual_subtraction (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance end subroutine pcm_instance_nlo_disable_virtual_subtraction @ %def pcm_instance_nlo_disable_virtual_subtraction @ <>= procedure :: compute_sqme_virt => pcm_instance_nlo_compute_sqme_virt <>= subroutine pcm_instance_nlo_compute_sqme_virt (pcm_instance, p, & alpha_coupling, separate_uborns, sqme_virt) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance type(vector4_t), intent(in), dimension(:) :: p real(default), intent(in) :: alpha_coupling logical, intent(in) :: separate_uborns real(default), dimension(:), allocatable, intent(inout) :: sqme_virt type(vector4_t), dimension(:), allocatable :: pp associate (virtual => pcm_instance%virtual) allocate (pp (size (p))) if (virtual%settings%factorization_mode == FACTORIZATION_THRESHOLD) then pp = pcm_instance%real_kinematics%p_born_onshell%get_momenta (1) else pp = p end if select type (config => pcm_instance%config) type is (pcm_nlo_t) if (separate_uborns) then allocate (sqme_virt (config%get_n_flv_born ())) else allocate (sqme_virt (1)) end if sqme_virt = zero call virtual%evaluate (config%region_data, & alpha_coupling, pp, separate_uborns, sqme_virt) end select end associate end subroutine pcm_instance_nlo_compute_sqme_virt @ %def pcm_instance_nlo_compute_sqme_virt @ <>= procedure :: compute_sqme_mismatch => pcm_instance_nlo_compute_sqme_mismatch <>= subroutine pcm_instance_nlo_compute_sqme_mismatch (pcm_instance, & alpha_s, separate_uborns, sqme_mism) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance real(default), intent(in) :: alpha_s logical, intent(in) :: separate_uborns real(default), dimension(:), allocatable, intent(inout) :: sqme_mism select type (config => pcm_instance%config) type is (pcm_nlo_t) if (separate_uborns) then allocate (sqme_mism (config%get_n_flv_born ())) else allocate (sqme_mism (1)) end if sqme_mism = zero sqme_mism = pcm_instance%soft_mismatch%evaluate (alpha_s) end select end subroutine pcm_instance_nlo_compute_sqme_mismatch @ %def pcm_instance_nlo_compute_sqme_mismatch @ <>= procedure :: compute_sqme_dglap_remnant => pcm_instance_nlo_compute_sqme_dglap_remnant <>= subroutine pcm_instance_nlo_compute_sqme_dglap_remnant (pcm_instance, & alpha_coupling, separate_uborns, sqme_dglap) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance real(default), intent(in) :: alpha_coupling logical, intent(in) :: separate_uborns real(default), dimension(:), allocatable, intent(inout) :: sqme_dglap select type (config => pcm_instance%config) type is (pcm_nlo_t) if (separate_uborns) then allocate (sqme_dglap (config%get_n_flv_born ())) else allocate (sqme_dglap (1)) end if end select sqme_dglap = zero call pcm_instance%dglap_remnant%evaluate (alpha_coupling, separate_uborns, sqme_dglap) end subroutine pcm_instance_nlo_compute_sqme_dglap_remnant @ %def pcm_instance_nlo_compute_sqme_dglap_remnant @ <>= procedure :: set_fixed_order_event_mode => pcm_instance_nlo_set_fixed_order_event_mode <>= subroutine pcm_instance_nlo_set_fixed_order_event_mode (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance pcm_instance%real_sub%purpose = FIXED_ORDER_EVENTS end subroutine pcm_instance_nlo_set_fixed_order_event_mode <>= procedure :: set_powheg_mode => pcm_instance_nlo_set_powheg_mode <>= subroutine pcm_instance_nlo_set_powheg_mode (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance pcm_instance%real_sub%purpose = POWHEG end subroutine pcm_instance_nlo_set_powheg_mode @ %def pcm_instance_nlo_set_fixed_order_event_mode @ %def pcm_instance_nlo_set_powheg_mode @ <>= procedure :: init_soft_mismatch => pcm_instance_nlo_init_soft_mismatch <>= subroutine pcm_instance_nlo_init_soft_mismatch (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance select type (config => pcm_instance%config) type is (pcm_nlo_t) call pcm_instance%soft_mismatch%init (config%region_data, & pcm_instance%real_kinematics, config%settings%factorization_mode) end select end subroutine pcm_instance_nlo_init_soft_mismatch @ %def pcm_instance_nlo_init_soft_mismatch @ <>= procedure :: init_dglap_remnant => pcm_instance_nlo_init_dglap_remnant <>= subroutine pcm_instance_nlo_init_dglap_remnant (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance select type (config => pcm_instance%config) type is (pcm_nlo_t) call pcm_instance%dglap_remnant%init ( & config%settings, & config%region_data, & pcm_instance%isr_kinematics) end select end subroutine pcm_instance_nlo_init_dglap_remnant @ %def pcm_instance_nlo_init_dglap_remnant @ <>= procedure :: is_fixed_order_nlo_events & => pcm_instance_nlo_is_fixed_order_nlo_events <>= function pcm_instance_nlo_is_fixed_order_nlo_events (pcm_instance) result (is_fnlo) logical :: is_fnlo class(pcm_instance_nlo_t), intent(in) :: pcm_instance is_fnlo = pcm_instance%real_sub%purpose == FIXED_ORDER_EVENTS end function pcm_instance_nlo_is_fixed_order_nlo_events @ %def pcm_instance_nlo_is_fixed_order_nlo_events @ <>= procedure :: final => pcm_instance_nlo_final <>= subroutine pcm_instance_nlo_final (pcm_instance) class(pcm_instance_nlo_t), intent(inout) :: pcm_instance call pcm_instance%real_sub%final () call pcm_instance%virtual%final () call pcm_instance%soft_mismatch%final () call pcm_instance%dglap_remnant%final () if (associated (pcm_instance%real_kinematics)) then call pcm_instance%real_kinematics%final () nullify (pcm_instance%real_kinematics) end if if (associated (pcm_instance%isr_kinematics)) then nullify (pcm_instance%isr_kinematics) end if end subroutine pcm_instance_nlo_final @ %def pcm_instance_nlo_final @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Kinematics instance} In this data type we combine all objects (instances) necessary for generating (or recovering) a kinematical configuration. The components work together as an implementation of multi-channel phase space. [[sf_chain]] is an instance of the structure-function chain. It is used both for generating kinematics and, after the proper scale has been determined, evaluating the structure function entries. [[phs]] is an instance of the phase space for the elementary process. The array [[f]] contains the products of the Jacobians that originate from parameter mappings in the structure-function chain or in the phase space. We allocate this explicitly if either [[sf_chain]] or [[phs]] are explicitly allocated, otherwise we can take over a pointer. All components are implemented as pointers to (anonymous) targets. For each component, there is a flag that tells whether this component is to be regarded as a proper component (`owned' by the object) or as a pointer. @ <<[[kinematics.f90]]>>= <> module kinematics <> <> use format_utils, only: write_separator use diagnostics use io_units use lorentz use physics_defs use sf_base use phs_base use interactions use mci_base use phs_fks use fks_regions use process_config use process_mci use pcm, only: pcm_instance_nlo_t use ttv_formfactors, only: m1s_to_mpole <> <> <> contains <> end module kinematics @ %def kinematics <>= public :: kinematics_t <>= type :: kinematics_t integer :: n_in = 0 integer :: n_channel = 0 integer :: selected_channel = 0 type(sf_chain_instance_t), pointer :: sf_chain => null () class(phs_t), pointer :: phs => null () real(default), dimension(:), pointer :: f => null () real(default) :: phs_factor logical :: sf_chain_allocated = .false. logical :: phs_allocated = .false. logical :: f_allocated = .false. integer :: emitter = -1 integer :: i_phs = 0 integer :: i_con = 0 logical :: only_cm_frame = .false. logical :: new_seed = .true. logical :: threshold = .false. contains <> end type kinematics_t @ %def kinematics_t @ Output. Show only those components which are marked as owned. <>= procedure :: write => kinematics_write <>= subroutine kinematics_write (object, unit) class(kinematics_t), intent(in) :: object integer, intent(in), optional :: unit integer :: u, c u = given_output_unit (unit) if (object%f_allocated) then write (u, "(1x,A)") "Flux * PHS volume:" write (u, "(2x,ES19.12)") object%phs_factor write (u, "(1x,A)") "Jacobian factors per channel:" do c = 1, size (object%f) write (u, "(3x,I0,':',1x,ES14.7)", advance="no") c, object%f(c) if (c == object%selected_channel) then write (u, "(1x,A)") "[selected]" else write (u, *) end if end do end if if (object%sf_chain_allocated) then call write_separator (u) call object%sf_chain%write (u) end if if (object%phs_allocated) then call write_separator (u) call object%phs%write (u) end if end subroutine kinematics_write @ %def kinematics_write @ Finalizer. Delete only those components which are marked as owned. <>= procedure :: final => kinematics_final <>= subroutine kinematics_final (object) class(kinematics_t), intent(inout) :: object if (object%sf_chain_allocated) then call object%sf_chain%final () deallocate (object%sf_chain) object%sf_chain_allocated = .false. end if if (object%phs_allocated) then call object%phs%final () deallocate (object%phs) object%phs_allocated = .false. end if if (object%f_allocated) then deallocate (object%f) object%f_allocated = .false. end if end subroutine kinematics_final @ %def kinematics_final @ Set the flags indicating whether the phase space shall be set up for the calculation of the real contribution. For this case, also set the emitter. <>= procedure :: set_nlo_info => kinematics_set_nlo_info <>= subroutine kinematics_set_nlo_info (k, nlo_type) class(kinematics_t), intent(inout) :: k integer, intent(in) :: nlo_type if (nlo_type == NLO_VIRTUAL) k%only_cm_frame = .true. end subroutine kinematics_set_nlo_info @ %def kinematics_set_nlo_info @ Allocate the structure-function chain instance, initialize it as a copy of the [[sf_chain]] template, and prepare it for evaluation. The [[sf_chain]] remains a target because the (usually constant) beam momenta are taken from there. <>= procedure :: init_sf_chain => kinematics_init_sf_chain <>= subroutine kinematics_init_sf_chain (k, sf_chain, config, extended_sf) class(kinematics_t), intent(inout) :: k type(sf_chain_t), intent(in), target :: sf_chain type(process_beam_config_t), intent(in) :: config logical, intent(in), optional :: extended_sf integer :: n_strfun, n_channel integer :: c k%n_in = config%data%get_n_in () n_strfun = config%n_strfun n_channel = config%n_channel allocate (k%sf_chain) k%sf_chain_allocated = .true. call k%sf_chain%init (sf_chain, n_channel) if (n_strfun /= 0) then do c = 1, n_channel call k%sf_chain%set_channel (c, config%sf_channel(c)) end do end if call k%sf_chain%link_interactions () call k%sf_chain%exchange_mask () call k%sf_chain%init_evaluators (extended_sf = extended_sf) end subroutine kinematics_init_sf_chain @ %def kinematics_init_sf_chain @ Allocate and initialize the phase-space part and the array of Jacobian factors. <>= procedure :: init_phs => kinematics_init_phs <>= subroutine kinematics_init_phs (k, config) class(kinematics_t), intent(inout) :: k class(phs_config_t), intent(in), target :: config k%n_channel = config%get_n_channel () call config%allocate_instance (k%phs) call k%phs%init (config) k%phs_allocated = .true. allocate (k%f (k%n_channel)) k%f = 0 k%f_allocated = .true. end subroutine kinematics_init_phs @ %def kinematics_init_phs @ <>= procedure :: evaluate_radiation_kinematics => kinematics_evaluate_radiation_kinematics <>= subroutine kinematics_evaluate_radiation_kinematics (k, r_in) class(kinematics_t), intent(inout) :: k real(default), intent(in), dimension(:) :: r_in select type (phs => k%phs) type is (phs_fks_t) call phs%generate_radiation_variables & (r_in(phs%n_r_born + 1 : phs%n_r_born + 3), threshold = k%threshold) call phs%compute_cms_energy () end select end subroutine kinematics_evaluate_radiation_kinematics @ %def kinematics_evaluate_radiation_kinematics @ <>= procedure :: compute_xi_ref_momenta => kinematics_compute_xi_ref_momenta <>= subroutine kinematics_compute_xi_ref_momenta (k, reg_data, nlo_type) class(kinematics_t), intent(inout) :: k type(region_data_t), intent(in) :: reg_data integer, intent(in) :: nlo_type logical :: use_contributors use_contributors = allocated (reg_data%alr_contributors) select type (phs => k%phs) type is (phs_fks_t) if (use_contributors) then call phs%compute_xi_ref_momenta (contributors = reg_data%alr_contributors) else if (k%threshold) then if (.not. is_subtraction_component (k%emitter, nlo_type)) & call phs%compute_xi_ref_momenta_threshold () else call phs%compute_xi_ref_momenta () end if end select end subroutine kinematics_compute_xi_ref_momenta @ %def kinematics_compute_xi_ref_momenta @ Generate kinematics, given a phase-space channel and a MC parameter set. The main result is the momentum array [[p]], but we also fill the momentum entries in the structure-function chain and the Jacobian-factor array [[f]]. Regarding phase space, we fill only the parameter arrays for the selected channel. <>= procedure :: compute_selected_channel => kinematics_compute_selected_channel <>= subroutine kinematics_compute_selected_channel & (k, mci_work, phs_channel, p, success) class(kinematics_t), intent(inout) :: k type(mci_work_t), intent(in) :: mci_work integer, intent(in) :: phs_channel type(vector4_t), dimension(:), intent(out) :: p logical, intent(out) :: success integer :: sf_channel k%selected_channel = phs_channel sf_channel = k%phs%config%get_sf_channel (phs_channel) call k%sf_chain%compute_kinematics (sf_channel, mci_work%get_x_strfun ()) call k%sf_chain%get_out_momenta (p(1:k%n_in)) call k%phs%set_incoming_momenta (p(1:k%n_in)) call k%phs%compute_flux () call k%phs%select_channel (phs_channel) call k%phs%evaluate_selected_channel (phs_channel, & mci_work%get_x_process ()) select type (phs => k%phs) type is (phs_fks_t) if (debug_on) call msg_debug2 (D_REAL, "phase space is phs_FKS") if (phs%q_defined) then call phs%get_born_momenta (p) if (debug_on) then call msg_debug2 (D_REAL, "q is defined") call msg_debug2 (D_REAL, "get_born_momenta called") end if k%phs_factor = phs%get_overall_factor () success = .true. else k%phs_factor = 0 success = .false. end if class default if (phs%q_defined) then call k%phs%get_outgoing_momenta (p(k%n_in + 1 :)) k%phs_factor = k%phs%get_overall_factor () success = .true. else k%phs_factor = 0 success = .false. end if end select end subroutine kinematics_compute_selected_channel @ %def kinematics_compute_selected_channel @ Complete kinematics by filling the non-selected phase-space parameter arrays. <>= procedure :: compute_other_channels => kinematics_compute_other_channels <>= subroutine kinematics_compute_other_channels (k, mci_work, phs_channel) class(kinematics_t), intent(inout) :: k type(mci_work_t), intent(in) :: mci_work integer, intent(in) :: phs_channel integer :: c, c_sf call k%phs%evaluate_other_channels (phs_channel) do c = 1, k%n_channel c_sf = k%phs%config%get_sf_channel (c) k%f(c) = k%sf_chain%get_f (c_sf) * k%phs%get_f (c) end do end subroutine kinematics_compute_other_channels @ %def kinematics_compute_other_channels @ Just fetch the outgoing momenta of the [[sf_chain]] subobject, which become the incoming (seed) momenta of the hard interaction. This is a stripped down-version of the above which we use when recovering kinematics. Momenta are known, but no MC parameters yet. (We do not use the [[get_out_momenta]] method of the chain, since this relies on the structure-function interactions, which are not necessary filled here. We do rely on the momenta of the last evaluator in the chain, however.) <>= procedure :: get_incoming_momenta => kinematics_get_incoming_momenta <>= subroutine kinematics_get_incoming_momenta (k, p) class(kinematics_t), intent(in) :: k type(vector4_t), dimension(:), intent(out) :: p type(interaction_t), pointer :: int integer :: i int => k%sf_chain%get_out_int_ptr () do i = 1, k%n_in p(i) = int%get_momentum (k%sf_chain%get_out_i (i)) end do end subroutine kinematics_get_incoming_momenta @ %def kinematics_get_incoming_momenta @ This inverts the remainder of the above [[compute]] method. We know the momenta and recover the rest, as far as needed. If we select a channel, we can complete the inversion and reconstruct the MC parameter set. <>= procedure :: recover_mcpar => kinematics_recover_mcpar <>= subroutine kinematics_recover_mcpar (k, mci_work, phs_channel, p) class(kinematics_t), intent(inout) :: k type(mci_work_t), intent(inout) :: mci_work integer, intent(in) :: phs_channel type(vector4_t), dimension(:), intent(in) :: p integer :: c, c_sf real(default), dimension(:), allocatable :: x_sf, x_phs c = phs_channel c_sf = k%phs%config%get_sf_channel (c) k%selected_channel = c call k%sf_chain%recover_kinematics (c_sf) call k%phs%set_incoming_momenta (p(1:k%n_in)) call k%phs%compute_flux () call k%phs%set_outgoing_momenta (p(k%n_in+1:)) call k%phs%inverse () do c = 1, k%n_channel c_sf = k%phs%config%get_sf_channel (c) k%f(c) = k%sf_chain%get_f (c_sf) * k%phs%get_f (c) end do k%phs_factor = k%phs%get_overall_factor () c = phs_channel c_sf = k%phs%config%get_sf_channel (c) allocate (x_sf (k%sf_chain%config%get_n_bound ())) allocate (x_phs (k%phs%config%get_n_par ())) call k%phs%select_channel (c) call k%sf_chain%get_mcpar (c_sf, x_sf) call k%phs%get_mcpar (c, x_phs) call mci_work%set_x_strfun (x_sf) call mci_work%set_x_process (x_phs) end subroutine kinematics_recover_mcpar @ %def kinematics_recover_mcpar @ This first part of [[recover_mcpar]]: just handle the sfchain. <>= procedure :: recover_sfchain => kinematics_recover_sfchain <>= subroutine kinematics_recover_sfchain (k, channel, p) class(kinematics_t), intent(inout) :: k integer, intent(in) :: channel type(vector4_t), dimension(:), intent(in) :: p k%selected_channel = channel call k%sf_chain%recover_kinematics (channel) end subroutine kinematics_recover_sfchain @ %def kinematics_recover_sfchain @ Retrieve the MC input parameter array for a specific channel. We assume that the kinematics is complete, so this is known for all channels. <>= procedure :: get_mcpar => kinematics_get_mcpar <>= subroutine kinematics_get_mcpar (k, phs_channel, r) class(kinematics_t), intent(in) :: k integer, intent(in) :: phs_channel real(default), dimension(:), intent(out) :: r integer :: sf_channel, n_par_sf, n_par_phs sf_channel = k%phs%config%get_sf_channel (phs_channel) n_par_phs = k%phs%config%get_n_par () n_par_sf = k%sf_chain%config%get_n_bound () if (n_par_sf > 0) then call k%sf_chain%get_mcpar (sf_channel, r(1:n_par_sf)) end if if (n_par_phs > 0) then call k%phs%get_mcpar (phs_channel, r(n_par_sf+1:)) end if end subroutine kinematics_get_mcpar @ %def kinematics_get_mcpar @ Evaluate the structure function chain, assuming that kinematics is known. The status must be precisely [[SF_DONE_KINEMATICS]]. We thus avoid evaluating the chain twice via different pointers to the same target. <>= procedure :: evaluate_sf_chain => kinematics_evaluate_sf_chain <>= subroutine kinematics_evaluate_sf_chain (k, fac_scale, negative_sf, sf_rescale) class(kinematics_t), intent(inout) :: k real(default), intent(in) :: fac_scale logical, intent(in), optional :: negative_sf class(sf_rescale_t), intent(inout), optional :: sf_rescale select case (k%sf_chain%get_status ()) case (SF_DONE_KINEMATICS) call k%sf_chain%evaluate (fac_scale, negative_sf = negative_sf, sf_rescale = sf_rescale) end select end subroutine kinematics_evaluate_sf_chain @ %def kinematics_evaluate_sf_chain @ Recover beam momenta, i.e., return the beam momenta stored in the current [[sf_chain]] to their source. This is a side effect. <>= procedure :: return_beam_momenta => kinematics_return_beam_momenta <>= subroutine kinematics_return_beam_momenta (k) class(kinematics_t), intent(in) :: k call k%sf_chain%return_beam_momenta () end subroutine kinematics_return_beam_momenta @ %def kinematics_return_beam_momenta @ Check wether the phase space is configured in the center-of-mass frame. Relevant for using the proper momenta input for BLHA matrix elements. <>= procedure :: lab_is_cm => kinematics_lab_is_cm <>= function kinematics_lab_is_cm (k) result (lab_is_cm) logical :: lab_is_cm class(kinematics_t), intent(in) :: k lab_is_cm = k%phs%config%lab_is_cm end function kinematics_lab_is_cm @ %def kinematics_lab_is_cm @ <>= procedure :: modify_momenta_for_subtraction => kinematics_modify_momenta_for_subtraction <>= subroutine kinematics_modify_momenta_for_subtraction (k, p_in, p_out) class(kinematics_t), intent(inout) :: k type(vector4_t), intent(in), dimension(:) :: p_in type(vector4_t), intent(out), dimension(:), allocatable :: p_out allocate (p_out (size (p_in))) if (k%threshold) then select type (phs => k%phs) type is (phs_fks_t) p_out = phs%get_onshell_projected_momenta () end select else p_out = p_in end if end subroutine kinematics_modify_momenta_for_subtraction @ %def kinematics_modify_momenta_for_subtraction @ <>= procedure :: threshold_projection => kinematics_threshold_projection <>= subroutine kinematics_threshold_projection (k, pcm_instance, nlo_type) class(kinematics_t), intent(inout) :: k type(pcm_instance_nlo_t), intent(inout) :: pcm_instance integer, intent(in) :: nlo_type real(default) :: sqrts, mtop type(lorentz_transformation_t) :: L_to_cms type(vector4_t), dimension(:), allocatable :: p_tot integer :: n_tot n_tot = k%phs%get_n_tot () allocate (p_tot (size (pcm_instance%real_kinematics%p_born_cms%phs_point(1)%p))) select type (phs => k%phs) type is (phs_fks_t) p_tot = pcm_instance%real_kinematics%p_born_cms%phs_point(1)%p class default p_tot(1 : k%n_in) = phs%p p_tot(k%n_in + 1 : n_tot) = phs%q end select sqrts = sum (p_tot (1:k%n_in))**1 mtop = m1s_to_mpole (sqrts) L_to_cms = get_boost_for_threshold_projection (p_tot, sqrts, mtop) call pcm_instance%real_kinematics%p_born_cms%set_momenta (1, p_tot) associate (p_onshell => pcm_instance%real_kinematics%p_born_onshell%phs_point(1)%p) call threshold_projection_born (mtop, L_to_cms, p_tot, p_onshell) if (debug2_active (D_THRESHOLD)) then print *, 'On-shell projected Born: ' call vector4_write_set (p_onshell) end if end associate end subroutine kinematics_threshold_projection @ %def kinematics_threshold_projection @ <>= procedure :: evaluate_radiation => kinematics_evaluate_radiation <>= subroutine kinematics_evaluate_radiation (k, p_in, p_out, success) class(kinematics_t), intent(inout) :: k type(vector4_t), intent(in), dimension(:) :: p_in type(vector4_t), intent(out), dimension(:), allocatable :: p_out logical, intent(out) :: success type(vector4_t), dimension(:), allocatable :: p_real type(vector4_t), dimension(:), allocatable :: p_born real(default) :: xi_max_offshell, xi_offshell, y_offshell, jac_rand_dummy, phi select type (phs => k%phs) type is (phs_fks_t) allocate (p_born (size (p_in))) if (k%threshold) then p_born = phs%get_onshell_projected_momenta () else p_born = p_in end if if (.not. k%phs%lab_is_cm () .and. .not. k%threshold) then p_born = inverse (k%phs%lt_cm_to_lab) * p_born end if call phs%compute_xi_max (p_born, k%threshold) if (k%emitter >= 0) then allocate (p_real (size (p_born) + 1)) allocate (p_out (size (p_born) + 1)) if (k%emitter <= k%n_in) then call phs%generate_isr (k%i_phs, p_real) else if (k%threshold) then jac_rand_dummy = 1._default call compute_y_from_emitter (phs%generator%real_kinematics%x_rad (I_Y), & phs%generator%real_kinematics%p_born_cms%get_momenta(1), & k%n_in, k%emitter, .false., phs%generator%y_max, jac_rand_dummy, & y_offshell) call phs%compute_xi_max (k%emitter, k%i_phs, y_offshell, & phs%generator%real_kinematics%p_born_cms%get_momenta(1), & xi_max_offshell) xi_offshell = xi_max_offshell * phs%generator%real_kinematics%xi_tilde phi = phs%generator%real_kinematics%phi call phs%generate_fsr (k%emitter, k%i_phs, p_real, & xi_y_phi = [xi_offshell, y_offshell, phi], no_jacobians = .true.) call phs%generator%real_kinematics%p_real_cms%set_momenta (k%i_phs, p_real) call phs%generate_fsr_threshold (k%emitter, k%i_phs, p_real) if (debug2_active (D_SUBTRACTION)) & call generate_fsr_threshold_for_other_emitters (k%emitter, k%i_phs) else if (k%i_con > 0) then call phs%generate_fsr (k%emitter, k%i_phs, p_real, k%i_con) else call phs%generate_fsr (k%emitter, k%i_phs, p_real) end if end if success = check_scalar_products (p_real) if (debug2_active (D_SUBTRACTION)) then call msg_debug2 (D_SUBTRACTION, "Real phase-space: ") call vector4_write_set (p_real) end if p_out = p_real else allocate (p_out (size (p_in))); p_out = p_in success = .true. end if end select contains subroutine generate_fsr_threshold_for_other_emitters (emitter, i_phs) integer, intent(in) :: emitter, i_phs integer :: ii_phs, this_emitter select type (phs => k%phs) type is (phs_fks_t) do ii_phs = 1, size (phs%phs_identifiers) this_emitter = phs%phs_identifiers(ii_phs)%emitter if (ii_phs /= i_phs .and. this_emitter /= emitter) & call phs%generate_fsr_threshold (this_emitter, i_phs) end do end select end subroutine end subroutine kinematics_evaluate_radiation @ %def kinematics_evaluate_radiation @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Instances} <<[[instances.f90]]>>= <> module instances <> <> <> use io_units use format_utils, only: write_separator use constants use diagnostics use os_interface use numeric_utils use lorentz use mci_base use particles use sm_qcd, only: qcd_t use interactions use quantum_numbers use model_data use helicities use flavors use beam_structures use variables use pdg_arrays, only: is_quark use sf_base use physics_defs use process_constants use process_libraries use state_matrices use integration_results use phs_base use prc_core, only: prc_core_t, prc_core_state_t !!! We should depend less on these modules (move it to pcm_nlo_t e.g.) use phs_wood, only: phs_wood_t use phs_fks use blha_olp_interfaces, only: prc_blha_t use blha_config, only: BLHA_AMP_COLOR_C use prc_external, only: prc_external_t, prc_external_state_t use prc_threshold, only: prc_threshold_t use blha_olp_interfaces, only: blha_result_array_size use prc_openloops, only: prc_openloops_t, openloops_state_t use prc_recola, only: prc_recola_t use blha_olp_interfaces, only: blha_color_c_fill_offdiag, blha_color_c_fill_diag use ttv_formfactors, only: m1s_to_mpole !!! local modules use parton_states use process_counter use pcm_base use pcm use process_config use process_mci use process use kinematics <> <> <> <> contains <> end module instances @ %def instances @ \subsection{Term instance} A [[term_instance_t]] object contains all data that describe a term. Each process component consists of one or more distinct terms which may differ in kinematics, but whose squared transition matrices have to be added pointwise. The [[active]] flag is set when this term is connected to an active process component. Inactive terms are skipped for kinematics and evaluation. The [[k_term]] object is the instance of the kinematics setup (structure-function chain, phase space, etc.) that applies specifically to this term. In ordinary cases, it consists of straight pointers to the seed kinematics. The [[amp]] array stores the amplitude values when we get them from evaluating the associated matrix-element code. The [[int_hard]] interaction describes the elementary hard process. It receives the momenta and the amplitude entries for each sampling point. The [[isolated]] object holds the effective parton state for the elementary interaction. The amplitude entries are computed from [[int_hard]]. The [[connected]] evaluator set convolutes this scattering matrix with the beam (and possibly structure-function) density matrix. The [[checked]] flag is set once we have applied cuts on this term. The result of this is stored in the [[passed]] flag. Although each [[term_instance]] carries a [[weight]], this currently always keeps the value $1$ and is only used to be given to routines to fulfill their signature. <>= type :: term_instance_t type(process_term_t), pointer :: config => null () logical :: active = .false. type(kinematics_t) :: k_term complex(default), dimension(:), allocatable :: amp type(interaction_t) :: int_hard type(isolated_state_t) :: isolated type(connected_state_t) :: connected class(prc_core_state_t), allocatable :: core_state logical :: checked = .false. logical :: passed = .false. real(default) :: scale = 0 real(default) :: fac_scale = 0 real(default) :: ren_scale = 0 real(default) :: es_scale = 0 real(default), allocatable :: alpha_qcd_forced real(default) :: weight = 1 type(vector4_t), dimension(:), allocatable :: p_seed type(vector4_t), dimension(:), allocatable :: p_hard class(pcm_instance_t), pointer :: pcm_instance => null () integer :: nlo_type = BORN integer, dimension(:), allocatable :: same_kinematics logical :: negative_sf = .false. contains <> end type term_instance_t @ %def term_instance_t @ <>= procedure :: write => term_instance_write <>= subroutine term_instance_write (term, unit, show_eff_state, testflag) class(term_instance_t), intent(in) :: term integer, intent(in), optional :: unit logical, intent(in), optional :: show_eff_state logical, intent(in), optional :: testflag integer :: u logical :: state u = given_output_unit (unit) state = .true.; if (present (show_eff_state)) state = show_eff_state if (term%active) then if (associated (term%config)) then write (u, "(1x,A,I0,A,I0,A)") "Term #", term%config%i_term, & " (component #", term%config%i_component, ")" else write (u, "(1x,A)") "Term [undefined]" end if else write (u, "(1x,A,I0,A)") "Term #", term%config%i_term, & " [inactive]" end if if (term%checked) then write (u, "(3x,A,L1)") "passed cuts = ", term%passed end if if (term%passed) then write (u, "(3x,A,ES19.12)") "overall scale = ", term%scale write (u, "(3x,A,ES19.12)") "factorization scale = ", term%fac_scale write (u, "(3x,A,ES19.12)") "renormalization scale = ", term%ren_scale if (allocated (term%alpha_qcd_forced)) then write (u, "(3x,A,ES19.12)") "alpha(QCD) forced = ", & term%alpha_qcd_forced end if write (u, "(3x,A,ES19.12)") "reweighting factor = ", term%weight end if call term%k_term%write (u) call write_separator (u) write (u, "(1x,A)") "Amplitude (transition matrix of the & &hard interaction):" call write_separator (u) call term%int_hard%basic_write (u, testflag = testflag) if (state .and. term%isolated%has_trace) then call write_separator (u) write (u, "(1x,A)") "Evaluators for the hard interaction:" call term%isolated%write (u, testflag = testflag) end if if (state .and. term%connected%has_trace) then call write_separator (u) write (u, "(1x,A)") "Evaluators for the connected process:" call term%connected%write (u, testflag = testflag) end if end subroutine term_instance_write @ %def term_instance_write @ The interactions and evaluators must be finalized. <>= procedure :: final => term_instance_final <>= subroutine term_instance_final (term) class(term_instance_t), intent(inout) :: term if (allocated (term%amp)) deallocate (term%amp) if (allocated (term%core_state)) deallocate (term%core_state) if (allocated (term%alpha_qcd_forced)) & deallocate (term%alpha_qcd_forced) if (allocated (term%p_seed)) deallocate(term%p_seed) if (allocated (term%p_hard)) deallocate (term%p_hard) call term%k_term%final () call term%connected%final () call term%isolated%final () call term%int_hard%final () term%pcm_instance => null () end subroutine term_instance_final @ %def term_instance_final @ For initialization, we make use of defined assignment for the [[interaction_t]] type. This creates a deep copy. The hard interaction (incoming momenta) is linked to the structure function instance. In the isolated state, we either set pointers to both, or we create modified copies ([[rearrange]]) as effective structure-function chain and interaction, respectively. Finally, we set up the [[subevt]] component that will be used for evaluating observables, collecting particles from the trace evaluator in the effective connected state. Their quantum numbers must be determined by following back source links and set explicitly, since they are already eliminated in that trace. The [[rearrange]] parts are still commented out; they could become relevant for a NLO algorithm. <>= procedure :: init => term_instance_init <>= subroutine term_instance_init (term, process, i_term, real_finite) class(term_instance_t), intent(inout), target :: term type(process_t), intent(in), target:: process integer, intent(in) :: i_term logical, intent(in), optional :: real_finite class(prc_core_t), pointer :: core => null () type(process_beam_config_t) :: beam_config type(interaction_t), pointer :: sf_chain_int type(interaction_t), pointer :: src_int type(quantum_numbers_mask_t), dimension(:), allocatable :: mask_in type(state_matrix_t), pointer :: state_matrix type(flavor_t), dimension(:), allocatable :: flv_int, flv_src, f_in, f_out integer, dimension(:,:), allocatable :: flv_born, flv_real type(flavor_t), dimension(:,:), allocatable :: flv_pdf type(quantum_numbers_t), dimension(:,:), allocatable :: qn_pdf integer :: n_in, n_vir, n_out, n_tot, n_sub integer :: n_flv_born, n_flv_real, n_flv_total integer :: i, j logical :: me_already_squared, keep_fs_flavors logical :: decrease_n_tot logical :: requires_extended_sf me_already_squared = .false. keep_fs_flavors = .false. term%config => process%get_term_ptr (i_term) term%int_hard = term%config%int core => process%get_core_term (i_term) term%negative_sf = process%get_negative_sf () call core%allocate_workspace (term%core_state) select type (core) class is (prc_external_t) call reduce_interaction (term%int_hard, & core%includes_polarization (), .true., .false.) me_already_squared = .true. allocate (term%amp (term%int_hard%get_n_matrix_elements ())) class default allocate (term%amp (term%config%n_allowed)) end select if (allocated (term%core_state)) then select type (core_state => term%core_state) type is (openloops_state_t) call core_state%init_threshold (process%get_model_ptr ()) end select end if term%amp = cmplx (0, 0, default) decrease_n_tot = term%nlo_type == NLO_REAL .and. & term%config%i_term_global /= term%config%i_sub if (present (real_finite)) then if (real_finite) decrease_n_tot = .false. end if if (decrease_n_tot) then allocate (term%p_seed (term%int_hard%get_n_tot () - 1)) else allocate (term%p_seed (term%int_hard%get_n_tot ())) end if allocate (term%p_hard (term%int_hard%get_n_tot ())) sf_chain_int => term%k_term%sf_chain%get_out_int_ptr () n_in = term%int_hard%get_n_in () do j = 1, n_in i = term%k_term%sf_chain%get_out_i (j) call term%int_hard%set_source_link (j, sf_chain_int, i) end do call term%isolated%init (term%k_term%sf_chain, term%int_hard) allocate (mask_in (n_in)) mask_in = term%k_term%sf_chain%get_out_mask () select type (phs => term%k_term%phs) type is (phs_wood_t) if (me_already_squared) then call term%isolated%setup_identity_trace & (core, mask_in, .true., .false.) else call term%isolated%setup_square_trace & (core, mask_in, term%config%col, .false.) end if type is (phs_fks_t) select case (phs%mode) case (PHS_MODE_ADDITIONAL_PARTICLE) if (me_already_squared) then call term%isolated%setup_identity_trace & (core, mask_in, .true., .false.) else keep_fs_flavors = term%config%data%n_flv > 1 call term%isolated%setup_square_trace & (core, mask_in, term%config%col, & keep_fs_flavors) end if case (PHS_MODE_COLLINEAR_REMNANT) if (me_already_squared) then call term%isolated%setup_identity_trace & (core, mask_in, .true., .false.) else call term%isolated%setup_square_trace & (core, mask_in, term%config%col, .false.) end if end select class default call term%isolated%setup_square_trace & (core, mask_in, term%config%col, .false.) end select if (term%nlo_type == NLO_VIRTUAL .or. (term%nlo_type == NLO_REAL .and. & term%config%i_term_global == term%config%i_sub) .or. & term%nlo_type == NLO_MISMATCH) then n_sub = term%get_n_sub () else if (term%nlo_type == NLO_DGLAP) then n_sub = n_beams_rescaled else !!! No integration of real subtraction in interactions yet n_sub = 0 end if keep_fs_flavors = keep_fs_flavors .or. me_already_squared requires_extended_sf = term%nlo_type == NLO_DGLAP .or. & (term%is_subtraction () .and. process%pcm_contains_pdfs ()) call term%connected%setup_connected_trace (term%isolated, & undo_helicities = undo_helicities (core, me_already_squared), & keep_fs_flavors = keep_fs_flavors, & requires_extended_sf = requires_extended_sf) associate (int_eff => term%isolated%int_eff) state_matrix => int_eff%get_state_matrix_ptr () n_tot = int_eff%get_n_tot () flv_int = quantum_numbers_get_flavor & (state_matrix%get_quantum_number (1)) allocate (f_in (n_in)) f_in = flv_int(1:n_in) deallocate (flv_int) end associate n_in = term%connected%trace%get_n_in () n_vir = term%connected%trace%get_n_vir () n_out = term%connected%trace%get_n_out () allocate (f_out (n_out)) do j = 1, n_out call term%connected%trace%find_source & (n_in + n_vir + j, src_int, i) if (associated (src_int)) then state_matrix => src_int%get_state_matrix_ptr () flv_src = quantum_numbers_get_flavor & (state_matrix%get_quantum_number (1)) f_out(j) = flv_src(i) deallocate (flv_src) end if end do beam_config = process%get_beam_config () call term%connected%setup_subevt (term%isolated%sf_chain_eff, & beam_config%data%flv, f_in, f_out) call term%connected%setup_var_list & (process%get_var_list_ptr (), beam_config%data) ! Does connected%trace never have any helicity qn? call term%init_interaction_qn_index (core, term%connected%trace, n_sub, & process%get_model_ptr (), is_polarized = .false.) call term%init_interaction_qn_index (core, term%int_hard, n_sub, process%get_model_ptr ()) if (requires_extended_sf) then select type (config => term%pcm_instance%config) type is (pcm_nlo_t) n_in = config%region_data%get_n_in () flv_born = config%region_data%get_flv_states_born () flv_real = config%region_data%get_flv_states_real () n_flv_born = config%region_data%get_n_flv_born () n_flv_real = config%region_data%get_n_flv_real () n_flv_total = n_flv_born + n_flv_real allocate (flv_pdf(n_in, n_flv_total), & qn_pdf(n_in, n_flv_total)) call flv_pdf(:, :n_flv_born)%init (flv_born(:n_in, :)) call flv_pdf(:, n_flv_born + 1:n_flv_total)%init (flv_real(:n_in, :)) call qn_pdf%init (flv_pdf) call sf_chain_int%init_qn_index (qn_pdf, n_flv_born, n_flv_real) end select end if contains function undo_helicities (core, me_squared) result (val) logical :: val class(prc_core_t), intent(in) :: core logical, intent(in) :: me_squared select type (core) class is (prc_external_t) val = me_squared .and. .not. core%includes_polarization () class default val = .false. end select end function undo_helicities subroutine reduce_interaction (int, polarized_beams, keep_fs_flavors, & keep_colors) type(interaction_t), intent(inout) :: int logical, intent(in) :: polarized_beams logical, intent(in) :: keep_fs_flavors, keep_colors type(quantum_numbers_mask_t), dimension(:), allocatable :: qn_mask logical, dimension(:), allocatable :: mask_f, mask_c, mask_h integer :: n_tot, n_in n_in = int%get_n_in (); n_tot = int%get_n_tot () allocate (qn_mask (n_tot)) allocate (mask_f (n_tot), mask_c (n_tot), mask_h (n_tot)) mask_c = .not. keep_colors mask_f (1 : n_in) = .false. if (keep_fs_flavors) then mask_f (n_in + 1 : ) = .false. else mask_f (n_in + 1 : ) = .true. end if if (polarized_beams) then mask_h (1 : n_in) = .false. else mask_h (1 : n_in) = .true. end if mask_h (n_in + 1 : ) = .true. call qn_mask%init (mask_f, mask_c, mask_h) call int%reduce_state_matrix (qn_mask, keep_order = .true.) end subroutine reduce_interaction <> end subroutine term_instance_init @ %def term_instance_init @ Set up index mapping from state matrix to index pair [[i_flv]], [[i_sub]]. <>= public :: setup_interaction_qn_index <>= subroutine setup_interaction_qn_index (int, data, qn_config, n_sub, is_polarized) class(interaction_t), intent(inout) :: int class(process_constants_t), intent(in) :: data type(quantum_numbers_t), dimension(:, :), intent(in) :: qn_config integer, intent(in) :: n_sub logical, intent(in) :: is_polarized integer :: i type(quantum_numbers_t), dimension(:, :), allocatable :: qn_hel if (is_polarized) then call setup_interaction_qn_hel (int, data, qn_hel) call int%init_qn_index (qn_config, n_sub, qn_hel) call int%set_qn_index_helicity_flip (.true.) else call int%init_qn_index (qn_config, n_sub) end if end subroutine setup_interaction_qn_index @ %def setup_interaction_qn_index @ Set up beam polarisation quantum numbers, if beam polarisation is required. We retrieve the full helicity information from [[term%config%data]] and reduce the information only to the inital state. Afterwards, we uniquify the initial state polarization by a applying an index (hash) table. The helicity information is fed into an array of quantum numbers to assign flavor, helicity and subtraction indices correctly to their matrix element. <>= public :: setup_interaction_qn_hel <>= subroutine setup_interaction_qn_hel (int, data, qn_hel) class(interaction_t), intent(in) :: int class(process_constants_t), intent(in) :: data type(quantum_numbers_t), dimension(:, :), allocatable, intent(out) :: qn_hel type(helicity_t), dimension(:), allocatable :: hel integer, dimension(:), allocatable :: index_table integer, dimension(:, :), allocatable :: hel_state integer :: i, j, n_hel_unique associate (n_in => int%get_n_in (), n_tot => int%get_n_tot ()) allocate (hel_state (n_tot, data%get_n_hel ()), & source = data%hel_state) allocate (index_table (data%get_n_hel ()), & source = 0) forall (j=1:data%get_n_hel (), i=n_in+1:n_tot) hel_state(i, j) = 0 n_hel_unique = 0 HELICITY: do i = 1, data%get_n_hel () do j = 1, data%get_n_hel () if (index_table (j) == 0) then index_table(j) = i; n_hel_unique = n_hel_unique + 1 cycle HELICITY else if (all (hel_state(:, i) == & hel_state(:, index_table(j)))) then cycle HELICITY end if end do end do HELICITY allocate (qn_hel (n_tot, n_hel_unique)) allocate (hel (n_tot)) do j = 1, n_hel_unique call hel%init (hel_state(:, index_table(j))) call qn_hel(:, j)%init (hel) end do end associate end subroutine setup_interaction_qn_hel @ %def setup_interaction_qn_hel @ <>= procedure :: init_interaction_qn_index => term_instance_init_interaction_qn_index <>= subroutine term_instance_init_interaction_qn_index (term, core, int, n_sub, & model, is_polarized) class(term_instance_t), intent(inout), target :: term class(prc_core_t), intent(in) :: core class(interaction_t), intent(inout) :: int integer, intent(in) :: n_sub class(model_data_t), intent(in) :: model logical, intent(in), optional :: is_polarized logical :: polarized type(quantum_numbers_t), dimension(:, :), allocatable :: qn_config integer, dimension(:,:), allocatable :: flv_born type(flavor_t), dimension(:), allocatable :: flv integer :: i select type (core) class is (prc_external_t) if (present (is_polarized)) then polarized = is_polarized else polarized = core%includes_polarization () end if select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) associate (is_born => .not. (term%nlo_type == NLO_REAL .and. & .not. term%is_subtraction ())) select type (config => pcm_instance%config) type is (pcm_nlo_t) qn_config = config%get_qn (is_born) end select call setup_interaction_qn_index (int, term%config%data, & qn_config, n_sub, polarized) end associate class default call term%config%data%get_flv_state (flv_born) allocate (flv (size (flv_born, dim = 1))) allocate (qn_config (size (flv_born, dim = 1), size (flv_born, dim = 2))) do i = 1, core%data%n_flv call flv%init (flv_born(:,i), model) call qn_config(:, i)%init (flv) end do call setup_interaction_qn_index (int, term%config%data, & qn_config, n_sub, polarized) end select class default call int%init_qn_index () end select end subroutine term_instance_init_interaction_qn_index @ %def term_instance_init_interaction_qn_index @ <>= procedure :: init_from_process => term_instance_init_from_process <>= subroutine term_instance_init_from_process (term_instance, & process, i, pcm_instance, sf_chain) class(term_instance_t), intent(inout), target :: term_instance type(process_t), intent(in), target :: process integer, intent(in) :: i class(pcm_instance_t), intent(in), target :: pcm_instance type(sf_chain_t), intent(in), target :: sf_chain type(process_term_t) :: term integer :: i_component logical :: requires_extended_sf term = process%get_term_ptr (i) i_component = term%i_component if (i_component /= 0) then term_instance%pcm_instance => pcm_instance term_instance%nlo_type = process%get_nlo_type_component (i_component) requires_extended_sf = term_instance%nlo_type == NLO_DGLAP .or. & (term_instance%nlo_type == NLO_REAL .and. process%get_i_sub (i) == i) call term_instance%setup_kinematics (sf_chain, & process%get_beam_config_ptr (), & process%get_phs_config (i_component), & requires_extended_sf) call term_instance%init (process, i, & real_finite = process%component_is_real_finite (i_component)) select type (phs => term_instance%k_term%phs) type is (phs_fks_t) call term_instance%set_emitter (process%get_pcm_ptr ()) call term_instance%setup_fks_kinematics (process%get_var_list_ptr (), & process%get_beam_config_ptr ()) end select call term_instance%set_threshold (process%get_pcm_ptr ()) call term_instance%setup_expressions (process%get_meta (), process%get_config ()) end if end subroutine term_instance_init_from_process @ %def term_instance_init_from_process @ Initialize the seed-kinematics configuration. All subobjects are allocated explicitly. <>= procedure :: setup_kinematics => term_instance_setup_kinematics <>= subroutine term_instance_setup_kinematics (term, sf_chain, & beam_config, phs_config, extended_sf) class(term_instance_t), intent(inout) :: term type(sf_chain_t), intent(in), target :: sf_chain type(process_beam_config_t), intent(in), target :: beam_config class(phs_config_t), intent(in), target :: phs_config logical, intent(in) :: extended_sf select type (config => term%pcm_instance%config) type is (pcm_nlo_t) call term%k_term%init_sf_chain (sf_chain, beam_config, & extended_sf = config%has_pdfs .and. extended_sf) class default call term%k_term%init_sf_chain (sf_chain, beam_config) end select !!! Add one for additional Born matrix element call term%k_term%init_phs (phs_config) call term%k_term%set_nlo_info (term%nlo_type) select type (phs => term%k_term%phs) type is (phs_fks_t) call phs%allocate_momenta (phs_config, & .not. (term%nlo_type == NLO_REAL)) select type (config => term%pcm_instance%config) type is (pcm_nlo_t) call config%region_data%init_phs_identifiers (phs%phs_identifiers) !!! The triple select type pyramid of doom select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) if (allocated (pcm_instance%real_kinematics%alr_to_i_phs)) & call config%region_data%set_alr_to_i_phs (phs%phs_identifiers, & pcm_instance%real_kinematics%alr_to_i_phs) end select end select end select end subroutine term_instance_setup_kinematics @ %def term_instance_setup_kinematics @ <>= procedure :: setup_fks_kinematics => term_instance_setup_fks_kinematics <>= subroutine term_instance_setup_fks_kinematics (term, var_list, beam_config) class(term_instance_t), intent(inout), target :: term type(var_list_t), intent(in) :: var_list type(process_beam_config_t), intent(in) :: beam_config integer :: mode logical :: singular_jacobian if (.not. (term%nlo_type == NLO_REAL .or. term%nlo_type == NLO_DGLAP .or. & term%nlo_type == NLO_MISMATCH)) return singular_jacobian = var_list%get_lval (var_str ("?powheg_use_singular_jacobian")) if (term%nlo_type == NLO_REAL) then mode = check_generator_mode (GEN_REAL_PHASE_SPACE) else if (term%nlo_type == NLO_MISMATCH) then mode = check_generator_mode (GEN_SOFT_MISMATCH) else mode = PHS_MODE_UNDEFINED end if select type (phs => term%k_term%phs) type is (phs_fks_t) select type (config => term%pcm_instance%config) type is (pcm_nlo_t) select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) call config%setup_phs_generator (pcm_instance, & phs%generator, phs%config%sqrts, mode, singular_jacobian) if (beam_config%has_structure_function ()) then pcm_instance%isr_kinematics%isr_mode = SQRTS_VAR else pcm_instance%isr_kinematics%isr_mode = SQRTS_FIXED end if if (debug_on) call msg_debug (D_PHASESPACE, "isr_mode: ", pcm_instance%isr_kinematics%isr_mode) end select end select class default call msg_fatal ("Phase space should be an FKS phase space!") end select contains function check_generator_mode (gen_mode_default) result (gen_mode) integer :: gen_mode integer, intent(in) :: gen_mode_default select type (config => term%pcm_instance%config) type is (pcm_nlo_t) associate (settings => config%settings) if (settings%test_coll_limit .and. settings%test_anti_coll_limit) & call msg_fatal ("You cannot check the collinear and anti-collinear limit "& &"at the same time!") if (settings%test_soft_limit .and. .not. settings%test_coll_limit & .and. .not. settings%test_anti_coll_limit) then gen_mode = GEN_SOFT_LIMIT_TEST else if (.not. settings%test_soft_limit .and. settings%test_coll_limit) then gen_mode = GEN_COLL_LIMIT_TEST else if (.not. settings%test_soft_limit .and. settings%test_anti_coll_limit) then gen_mode = GEN_ANTI_COLL_LIMIT_TEST else if (settings%test_soft_limit .and. settings%test_coll_limit) then gen_mode = GEN_SOFT_COLL_LIMIT_TEST else if (settings%test_soft_limit .and. settings%test_anti_coll_limit) then gen_mode = GEN_SOFT_ANTI_COLL_LIMIT_TEST else gen_mode = gen_mode_default end if end associate end select end function check_generator_mode end subroutine term_instance_setup_fks_kinematics @ %def term_instance_setup_fks_kinematics @ Set up seed kinematics, starting from the MC parameter set given as argument. As a result, the [[k_seed]] kinematics object is evaluated (except for the structure-function matrix-element evaluation, which we postpone until we know the factorization scale), and we have a valid [[p_seed]] momentum array. <>= procedure :: compute_seed_kinematics => term_instance_compute_seed_kinematics <>= subroutine term_instance_compute_seed_kinematics & (term, mci_work, phs_channel, success) class(term_instance_t), intent(inout), target :: term type(mci_work_t), intent(in) :: mci_work integer, intent(in) :: phs_channel logical, intent(out) :: success call term%k_term%compute_selected_channel & (mci_work, phs_channel, term%p_seed, success) end subroutine term_instance_compute_seed_kinematics @ %def term_instance_compute_seed_kinematics @ <>= procedure :: evaluate_radiation_kinematics => term_instance_evaluate_radiation_kinematics <>= subroutine term_instance_evaluate_radiation_kinematics (term, x) class(term_instance_t), intent(inout) :: term real(default), dimension(:), intent(in) :: x select type (phs => term%k_term%phs) type is (phs_fks_t) if (phs%mode == PHS_MODE_ADDITIONAL_PARTICLE) & call term%k_term%evaluate_radiation_kinematics (x) end select end subroutine term_instance_evaluate_radiation_kinematics @ %def term_instance_evaluate_radiation_kinematics @ <>= procedure :: compute_xi_ref_momenta => term_instance_compute_xi_ref_momenta <>= subroutine term_instance_compute_xi_ref_momenta (term) class(term_instance_t), intent(inout) :: term select type (pcm => term%pcm_instance%config) type is (pcm_nlo_t) call term%k_term%compute_xi_ref_momenta (pcm%region_data, term%nlo_type) end select end subroutine term_instance_compute_xi_ref_momenta @ %def term_instance_compute_xi_ref_momenta @ <>= procedure :: generate_fsr_in => term_instance_generate_fsr_in <>= subroutine term_instance_generate_fsr_in (term) class(term_instance_t), intent(inout) :: term select type (phs => term%k_term%phs) type is (phs_fks_t) call phs%generate_fsr_in () end select end subroutine term_instance_generate_fsr_in @ %def term_instance_generate_fsr_in @ <>= procedure :: evaluate_projections => term_instance_evaluate_projections <>= subroutine term_instance_evaluate_projections (term) class(term_instance_t), intent(inout) :: term if (term%k_term%threshold .and. term%nlo_type > BORN) then if (debug2_active (D_THRESHOLD)) & print *, 'Evaluate on-shell projection: ', & char (component_status (term%nlo_type)) select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) call term%k_term%threshold_projection (pcm_instance, term%nlo_type) end select end if end subroutine term_instance_evaluate_projections @ %def term_instance_evaluate_projections @ <>= procedure :: redo_sf_chain => term_instance_redo_sf_chain <>= subroutine term_instance_redo_sf_chain (term, mci_work, phs_channel) class(term_instance_t), intent(inout) :: term type(mci_work_t), intent(in) :: mci_work integer, intent(in) :: phs_channel real(default), dimension(:), allocatable :: x integer :: sf_channel, n real(default) :: xi, y n = size (mci_work%get_x_strfun ()) if (n > 0) then allocate (x(n)) x = mci_work%get_x_strfun () associate (k => term%k_term) sf_channel = k%phs%config%get_sf_channel (phs_channel) call k%sf_chain%compute_kinematics (sf_channel, x) deallocate (x) end associate end if end subroutine term_instance_redo_sf_chain @ %def term_instance_redo_sf_chain @ Inverse: recover missing parts of the kinematics, given a complete set of seed momenta. Select a channel and reconstruct the MC parameter set. <>= procedure :: recover_mcpar => term_instance_recover_mcpar <>= subroutine term_instance_recover_mcpar (term, mci_work, phs_channel) class(term_instance_t), intent(inout), target :: term type(mci_work_t), intent(inout) :: mci_work integer, intent(in) :: phs_channel call term%k_term%recover_mcpar (mci_work, phs_channel, term%p_seed) end subroutine term_instance_recover_mcpar @ %def term_instance_recover_mcpar @ Part of [[recover_mcpar]], separately accessible. Reconstruct all kinematics data in the structure-function chain instance. <>= procedure :: recover_sfchain => term_instance_recover_sfchain <>= subroutine term_instance_recover_sfchain (term, channel) class(term_instance_t), intent(inout), target :: term integer, intent(in) :: channel call term%k_term%recover_sfchain (channel, term%p_seed) end subroutine term_instance_recover_sfchain @ %def term_instance_recover_sfchain @ Compute the momenta in the hard interactions, one for each term that constitutes this process component. In simple cases this amounts to just copying momenta. In more advanced cases, we may generate distinct sets of momenta from the seed kinematics. The interactions in the term instances are accessed individually. We may choose to calculate all terms at once together with the seed kinematics, use [[component%core_state]] for storage, and just fill the interactions here. <>= procedure :: compute_hard_kinematics => & term_instance_compute_hard_kinematics <>= subroutine term_instance_compute_hard_kinematics & (term, recover, skip_term, success) class(term_instance_t), intent(inout) :: term integer, intent(in), optional :: skip_term logical, intent(in), optional :: recover logical, intent(out) :: success type(vector4_t), dimension(:), allocatable :: p if (allocated (term%core_state)) & call term%core_state%reset_new_kinematics () if (present (skip_term)) then if (term%config%i_term_global == skip_term) return end if if (present (recover)) then if (recover) return end if if (term%nlo_type == NLO_REAL .and. term%k_term%emitter >= 0) then call term%k_term%evaluate_radiation (term%p_seed, p, success) select type (config => term%pcm_instance%config) type is (pcm_nlo_t) if (config%dalitz_plot%active) then if (term%k_term%emitter > term%k_term%n_in) then if (p(term%k_term%emitter)**2 > tiny_07) & call config%register_dalitz_plot (term%k_term%emitter, p) end if end if end select else if (is_subtraction_component (term%k_term%emitter, term%nlo_type)) then call term%k_term%modify_momenta_for_subtraction (term%p_seed, p) success = .true. else allocate (p (size (term%p_seed))); p = term%p_seed success = .true. end if call term%int_hard%set_momenta (p) if (debug_on) then call msg_debug2 (D_REAL, "inside compute_hard_kinematics") if (debug2_active (D_REAL)) call vector4_write_set (p) end if end subroutine term_instance_compute_hard_kinematics @ %def term_instance_compute_hard_kinematics @ Here, we invert this. We fetch the incoming momenta which reside in the appropriate [[sf_chain]] object, stored within the [[k_seed]] subobject. On the other hand, we have the outgoing momenta of the effective interaction. We rely on the process core to compute the remaining seed momenta and to fill the momenta within the hard interaction. (The latter is trivial if hard and effective interaction coincide.) After this is done, the incoming momenta in the trace evaluator that corresponds to the hard (effective) interaction, are still left undefined. We remedy this by calling [[receive_kinematics]] once. <>= procedure :: recover_seed_kinematics => & term_instance_recover_seed_kinematics <>= subroutine term_instance_recover_seed_kinematics (term, p_seed_ref) class(term_instance_t), intent(inout) :: term integer :: n_in type(vector4_t), dimension(:), intent(in), optional :: p_seed_ref n_in = term%k_term%n_in call term%k_term%get_incoming_momenta (term%p_seed(1:n_in)) associate (int_eff => term%isolated%int_eff) call int_eff%set_momenta (term%p_seed(1:n_in), outgoing = .false.) if (present (p_seed_ref)) then term%p_seed(n_in + 1 : ) = p_seed_ref else term%p_seed(n_in + 1 : ) = int_eff%get_momenta (outgoing = .true.) end if end associate call term%isolated%receive_kinematics () end subroutine term_instance_recover_seed_kinematics @ %def term_instance_recover_seed_kinematics @ Compute the integration parameters for all channels except the selected one. <>= procedure :: compute_other_channels => & term_instance_compute_other_channels <>= subroutine term_instance_compute_other_channels & (term, mci_work, phs_channel) class(term_instance_t), intent(inout), target :: term type(mci_work_t), intent(in) :: mci_work integer, intent(in) :: phs_channel call term%k_term%compute_other_channels (mci_work, phs_channel) end subroutine term_instance_compute_other_channels @ %def term_instance_compute_other_channels @ Recover beam momenta, i.e., return the beam momenta as currently stored in the kinematics subobject to their source. This is a side effect. <>= procedure :: return_beam_momenta => term_instance_return_beam_momenta <>= subroutine term_instance_return_beam_momenta (term) class(term_instance_t), intent(in) :: term call term%k_term%return_beam_momenta () end subroutine term_instance_return_beam_momenta @ %def term_instance_return_beam_momenta @ Applies the real partition by computing the real partition function $F(\Phi)$ and multiplying either $\mathcal{R}_\text{sin} = \mathcal{R} \cdot F$ or $\mathcal{R}_\text{fin} = \mathcal{R} \cdot (1-F)$. <>= procedure :: apply_real_partition => term_instance_apply_real_partition <>= subroutine term_instance_apply_real_partition (term, process) class(term_instance_t), intent(inout) :: term type(process_t), intent(in) :: process real(default) :: f, sqme integer :: i_component integer :: i_amp, n_amps, qn_index logical :: is_subtraction i_component = term%config%i_component if (process%component_is_selected (i_component) .and. & process%get_component_nlo_type (i_component) == NLO_REAL) then is_subtraction = process%get_component_type (i_component) == COMP_REAL_SING & .and. term%k_term%emitter < 0 if (is_subtraction) return select case (process%get_component_type (i_component)) case (COMP_REAL_FIN) call term%connected%trace%set_duplicate_flv_zero() end select select type (pcm => process%get_pcm_ptr ()) type is (pcm_nlo_t) f = pcm%real_partition%get_f (term%p_hard) end select n_amps = term%connected%trace%get_n_matrix_elements () do i_amp = 1, n_amps qn_index = term%connected%trace%get_qn_index (i_amp, i_sub = 0) sqme = real (term%connected%trace%get_matrix_element (qn_index)) if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "term_instance_apply_real_partition") select type (pcm => term%pcm_instance%config) type is (pcm_nlo_t) select case (process%get_component_type (i_component)) case (COMP_REAL_FIN, COMP_REAL_SING) select case (process%get_component_type (i_component)) case (COMP_REAL_FIN) if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "Real finite") sqme = sqme * (one - f) case (COMP_REAL_SING) if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "Real singular") sqme = sqme * f end select end select end select if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "apply_damping: sqme", sqme) call term%connected%trace%set_matrix_element (qn_index, cmplx (sqme, zero, default)) end do end if end subroutine term_instance_apply_real_partition @ %def term_instance_apply_real_partition @ <>= procedure :: get_lorentz_transformation => term_instance_get_lorentz_transformation <>= function term_instance_get_lorentz_transformation (term) result (lt) type(lorentz_transformation_t) :: lt class(term_instance_t), intent(in) :: term lt = term%k_term%phs%get_lorentz_transformation () end function term_instance_get_lorentz_transformation @ %def term_instance_get_lorentz_transformation @ <>= procedure :: get_p_hard => term_instance_get_p_hard <>= pure function term_instance_get_p_hard (term_instance) result (p_hard) type(vector4_t), dimension(:), allocatable :: p_hard class(term_instance_t), intent(in) :: term_instance allocate (p_hard (size (term_instance%p_hard))) p_hard = term_instance%p_hard end function term_instance_get_p_hard @ %def term_instance_get_p_hard @ <>= procedure :: set_emitter => term_instance_set_emitter <>= subroutine term_instance_set_emitter (term, pcm) class(term_instance_t), intent(inout) :: term class(pcm_t), intent(in) :: pcm integer :: i_phs logical :: set_emitter select type (pcm) type is (pcm_nlo_t) !!! Without resonances, i_alr = i_phs i_phs = term%config%i_term term%k_term%i_phs = term%config%i_term select type (phs => term%k_term%phs) type is (phs_fks_t) set_emitter = i_phs <= pcm%region_data%n_phs .and. term%nlo_type == NLO_REAL if (set_emitter) then term%k_term%emitter = phs%phs_identifiers(i_phs)%emitter select type (pcm => term%pcm_instance%config) type is (pcm_nlo_t) if (allocated (pcm%region_data%i_phs_to_i_con)) & term%k_term%i_con = pcm%region_data%i_phs_to_i_con (i_phs) end select end if end select end select end subroutine term_instance_set_emitter @ %def term_instance_set_emitter @ <>= procedure :: set_threshold => term_instance_set_threshold <>= subroutine term_instance_set_threshold (term, pcm) class(term_instance_t), intent(inout) :: term class(pcm_t), intent(in) :: pcm select type (pcm) type is (pcm_nlo_t) term%k_term%threshold = pcm%settings%factorization_mode == FACTORIZATION_THRESHOLD class default term%k_term%threshold = .false. end select end subroutine term_instance_set_threshold @ %def term_instance_set_threshold @ For initializing the expressions, we need the local variable list and the parse trees. <>= procedure :: setup_expressions => term_instance_setup_expressions <>= subroutine term_instance_setup_expressions (term, meta, config) class(term_instance_t), intent(inout), target :: term type(process_metadata_t), intent(in), target :: meta type(process_config_data_t), intent(in) :: config if (allocated (config%ef_cuts)) & call term%connected%setup_cuts (config%ef_cuts) if (allocated (config%ef_scale)) & call term%connected%setup_scale (config%ef_scale) if (allocated (config%ef_fac_scale)) & call term%connected%setup_fac_scale (config%ef_fac_scale) if (allocated (config%ef_ren_scale)) & call term%connected%setup_ren_scale (config%ef_ren_scale) if (allocated (config%ef_weight)) & call term%connected%setup_weight (config%ef_weight) end subroutine term_instance_setup_expressions @ %def term_instance_setup_expressions @ Prepare the extra evaluators that we need for processing events. The matrix elements we get from OpenLoops and GoSam are already squared and summed over color and helicity. They should not be squared again. <>= procedure :: setup_event_data => term_instance_setup_event_data <>= subroutine term_instance_setup_event_data (term, core, model) class(term_instance_t), intent(inout), target :: term class(prc_core_t), intent(in) :: core class(model_data_t), intent(in), target :: model integer :: n_in + logical :: mask_color type(quantum_numbers_mask_t), dimension(:), allocatable :: mask_in n_in = term%int_hard%get_n_in () allocate (mask_in (n_in)) mask_in = term%k_term%sf_chain%get_out_mask () call setup_isolated (term%isolated, core, model, mask_in, term%config%col) - call setup_connected (term%connected, term%isolated, term%nlo_type) + select type (pcm_instance => term%pcm_instance) + type is (pcm_instance_nlo_t) + mask_color = pcm_instance%is_fixed_order_nlo_events () + class default + mask_color = .false. + end select + call setup_connected (term%connected, term%isolated, core, & + term%nlo_type, mask_color) contains subroutine setup_isolated (isolated, core, model, mask, color) type(isolated_state_t), intent(inout), target :: isolated class(prc_core_t), intent(in) :: core class(model_data_t), intent(in), target :: model type(quantum_numbers_mask_t), intent(in), dimension(:) :: mask integer, intent(in), dimension(:) :: color select type (core) class is (prc_blha_t) call isolated%matrix%init_identity(isolated%int_eff) isolated%has_matrix = .true. class default call isolated%setup_square_matrix (core, model, mask, color) end select - !!! TODO (PS-09-10-20) We should not square the flows if they come from BLHA either + !!! TODO (PS-09-10-20) We should not square the flows + !!! if they come from BLHA either call isolated%setup_square_flows (core, model, mask) end subroutine setup_isolated - subroutine setup_connected (connected, isolated, nlo_type) + subroutine setup_connected (connected, isolated, core, nlo_type, mask_color) type(connected_state_t), intent(inout), target :: connected type(isolated_state_t), intent(in), target :: isolated - integer :: nlo_type + class(prc_core_t), intent(in) :: core + integer, intent(in) :: nlo_type + logical, intent(in) :: mask_color type(quantum_numbers_mask_t), dimension(:), allocatable :: mask call connected%setup_connected_matrix (isolated) if (term%nlo_type == NLO_VIRTUAL .or. (term%nlo_type == NLO_REAL & .and. term%config%i_term_global == term%config%i_sub) & .or. term%nlo_type == NLO_DGLAP) then !!! We do not care about the subtraction matrix elements in !!! connected%matrix, because all entries there are supposed !!! to be squared. To be able to match with flavor quantum numbers, !!! we remove the subtraction quantum entries from the state matrix. allocate (mask (connected%matrix%get_n_tot())) call mask%set_sub (1) call connected%matrix%reduce_state_matrix (mask, keep_order = .true.) end if call term%init_interaction_qn_index (core, connected%matrix, 0, model, & is_polarized = .false.) - call connected%setup_connected_flows (isolated) + select type (core) + class is (prc_blha_t) + call connected%setup_connected_flows & + (isolated, mask_color = mask_color) + class default + call connected%setup_connected_flows (isolated) + end select call connected%setup_state_flv (isolated%get_n_out ()) end subroutine setup_connected end subroutine term_instance_setup_event_data @ %def term_instance_setup_event_data @ Color-correlated matrix elements should be obtained from the external BLHA provider. According to the standard, the matrix elements output is a one-dimensional array. For FKS subtraction, we require the matrix $B_{ij}$. BLHA prescribes a mapping $(i, j) \to k$, where $k$ is the index of the matrix element in the output array. It focusses on the off-diagonal entries, i.e. $i \neq j$. The subroutine [[blha_color_c_fill_offdiag]] realizes this mapping. The diagonal entries can simply be obtained as the product of the Born matrix element and either $C_A$ or $C_F$, which is achieved by [[blha_color_c_fill_diag]]. For simple processes, i.e. those with only one color line, it is $B_{ij} = C_F \cdot B$. For those, we keep the possibility of computing color correlations by a multiplication of the Born matrix element with $C_F$. It is triggered by the [[use_internal_color_correlations]] flag and should be used only for testing purposes. However, it is also used for the threshold computation where the process is well-defined and fixed. <>= procedure :: evaluate_color_correlations => & term_instance_evaluate_color_correlations <>= subroutine term_instance_evaluate_color_correlations (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(inout) :: core integer :: i_flv_born select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) select type (config => pcm_instance%config) type is (pcm_nlo_t) if (debug_on) call msg_debug2 (D_SUBTRACTION, & "term_instance_evaluate_color_correlations: " // & "use_internal_color_correlations:", & config%settings%use_internal_color_correlations) if (debug_on) call msg_debug2 (D_SUBTRACTION, "fac_scale", term%fac_scale) do i_flv_born = 1, config%region_data%n_flv_born select case (term%nlo_type) case (NLO_REAL) call transfer_me_array_to_bij (config, i_flv_born, & pcm_instance%real_sub%sqme_born (i_flv_born), & pcm_instance%real_sub%sqme_born_color_c (:, :, i_flv_born)) case (NLO_MISMATCH) call transfer_me_array_to_bij (config, i_flv_born, & pcm_instance%soft_mismatch%sqme_born (i_flv_born), & pcm_instance%soft_mismatch%sqme_born_color_c (:, :, i_flv_born)) case (NLO_VIRTUAL) !!! This is just a copy of the above with a different offset and can for sure be unified call transfer_me_array_to_bij (config, i_flv_born, & -one, pcm_instance%virtual%sqme_color_c (:, :, i_flv_born)) end select end do end select end select contains function get_trivial_cf_factors (n_tot, flv, factorization_mode) result (beta_ij) integer, intent(in) :: n_tot, factorization_mode integer, intent(in), dimension(:) :: flv real(default), dimension(n_tot, n_tot) :: beta_ij if (factorization_mode == NO_FACTORIZATION) then beta_ij = get_trivial_cf_factors_default (n_tot, flv) else beta_ij = get_trivial_cf_factors_threshold (n_tot, flv) end if end function get_trivial_cf_factors function get_trivial_cf_factors_default (n_tot, flv) result (beta_ij) integer, intent(in) :: n_tot integer, intent(in), dimension(:) :: flv real(default), dimension(n_tot, n_tot) :: beta_ij integer :: i, j beta_ij = zero if (count (is_quark (flv)) == 2) then do i = 1, n_tot do j = 1, n_tot if (is_quark(flv(i)) .and. is_quark(flv(j))) then if (i == j) then beta_ij(i,j)= -cf else beta_ij(i,j) = cf end if end if end do end do end if end function get_trivial_cf_factors_default function get_trivial_cf_factors_threshold (n_tot, flv) result (beta_ij) integer, intent(in) :: n_tot integer, intent(in), dimension(:) :: flv real(default), dimension(n_tot, n_tot) :: beta_ij integer :: i beta_ij = zero do i = 1, 4 beta_ij(i,i) = -cf end do beta_ij(1,2) = cf; beta_ij(2,1) = cf beta_ij(3,4) = cf; beta_ij(4,3) = cf end function get_trivial_cf_factors_threshold subroutine transfer_me_array_to_bij (pcm, i_flv, & sqme_born, sqme_color_c) type(pcm_nlo_t), intent(in) :: pcm integer, intent(in) :: i_flv real(default), intent(in) :: sqme_born real(default), dimension(:,:), intent(inout) :: sqme_color_c integer :: i_color_c, i_sub, n_offset real(default), dimension(:), allocatable :: sqme if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "transfer_me_array_to_bij") if (pcm%settings%use_internal_color_correlations) then !!! A negative value for sqme_born indicates that the Born matrix !!! element is multiplied at a different place, e.g. in the case !!! of the virtual component sqme_color_c = get_trivial_cf_factors & (pcm%region_data%get_n_legs_born (), & pcm%region_data%get_flv_states_born (i_flv), & pcm%settings%factorization_mode) if (sqme_born > zero) then sqme_color_c = sqme_born * sqme_color_c else if (sqme_born == zero) then sqme_color_c = zero end if else n_offset = 0 if (term%nlo_type == NLO_VIRTUAL) then n_offset = 1 else if (pcm%has_pdfs .and. term%is_subtraction ()) then n_offset = n_beams_rescaled end if allocate (sqme (term%get_n_sub_color ()), source = zero) do i_sub = 1, term%get_n_sub_color () sqme(i_sub) = real(term%connected%trace%get_matrix_element ( & term%connected%trace%get_qn_index (i_flv, i_sub = i_sub + n_offset)), & default) end do call blha_color_c_fill_offdiag (pcm%region_data%n_legs_born, & sqme, sqme_color_c) call blha_color_c_fill_diag (real(term%connected%trace%get_matrix_element ( & term%connected%trace%get_qn_index (i_flv, i_sub = 0)), default), & pcm%region_data%get_flv_states_born (i_flv), & sqme_color_c) end if end subroutine transfer_me_array_to_bij end subroutine term_instance_evaluate_color_correlations @ %def term_instance_evaluate_color_correlations @ <>= procedure :: evaluate_charge_correlations => & term_instance_evaluate_charge_correlations <>= subroutine term_instance_evaluate_charge_correlations (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(inout) :: core integer :: i_flv_born select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) select type (config => pcm_instance%config) type is (pcm_nlo_t) do i_flv_born = 1, config%region_data%n_flv_born select case (term%nlo_type) case (NLO_REAL) call transfer_me_array_to_bij (config, i_flv_born, & pcm_instance%real_sub%sqme_born (i_flv_born), & pcm_instance%real_sub%sqme_born_charge_c (:, :, i_flv_born)) case (NLO_MISMATCH) call transfer_me_array_to_bij (config, i_flv_born, & pcm_instance%soft_mismatch%sqme_born (i_flv_born), & pcm_instance%soft_mismatch%sqme_born_charge_c (:, :, i_flv_born)) case (NLO_VIRTUAL) call transfer_me_array_to_bij (config, i_flv_born, & one, pcm_instance%virtual%sqme_charge_c (:, :, i_flv_born)) end select end do end select end select contains subroutine transfer_me_array_to_bij (pcm, i_flv, sqme_born, sqme_charge_c) type(pcm_nlo_t), intent(in) :: pcm integer, intent(in) :: i_flv real(default), intent(in) :: sqme_born real(default), dimension(:,:), intent(inout) :: sqme_charge_c integer :: n_legs_born, i, j real(default), dimension(:), allocatable :: sigma real(default), dimension(:), allocatable :: Q n_legs_born = pcm%region_data%n_legs_born associate (flv_born => pcm%region_data%flv_born(i_flv)) allocate (sigma (n_legs_born), Q (size (flv_born%charge))) Q = flv_born%charge sigma(1:flv_born%n_in) = -one sigma(flv_born%n_in + 1: ) = one end associate do i = 1, n_legs_born do j = 1, n_legs_born sqme_charge_c(i, j) = sigma(i) * sigma(j) * Q(i) * Q(j) * (-one) end do end do sqme_charge_c = sqme_charge_c * sqme_born end subroutine transfer_me_array_to_bij end subroutine term_instance_evaluate_charge_correlations @ %def term_instance_evaluate_charge_correlations @ The information about spin correlations is not stored in the [[nlo_settings]] because it is only available after the [[fks_regions]] have been created. <>= procedure :: evaluate_spin_correlations => term_instance_evaluate_spin_correlations <>= subroutine term_instance_evaluate_spin_correlations (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(inout) :: core integer :: i_flv, i_sub, i_emitter, emitter integer :: n_flv, n_sub_color, n_sub_spin, n_offset,i,j real(default), dimension(1:3, 1:3) :: sqme_spin_c real(default), dimension(:), allocatable :: sqme_spin_c_all real(default), dimension(:), allocatable :: sqme_spin_c_arr if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, & "term_instance_evaluate_spin_correlations") select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) if (pcm_instance%real_sub%requires_spin_correlations () & .and. term%nlo_type == NLO_REAL) then select type (core) type is (prc_openloops_t) select type (config => pcm_instance%config) type is (pcm_nlo_t) n_flv = term%connected%trace%get_qn_index_n_flv () n_sub_color = term%get_n_sub_color () n_sub_spin = term%get_n_sub_spin () n_offset = 0; if (config%has_pdfs) n_offset = n_beams_rescaled allocate (sqme_spin_c_arr(6)) do i_flv = 1, n_flv allocate (sqme_spin_c_all(n_sub_spin)) do i_sub = 1, n_sub_spin sqme_spin_c_all(i_sub) = real(term%connected%trace%get_matrix_element & (term%connected%trace%get_qn_index (i_flv, & i_sub = i_sub + n_offset + n_sub_color)), default) end do do i_emitter = 1, config%region_data%n_emitters emitter = config%region_data%emitters(i_emitter) if (emitter > 0) then call split_array (sqme_spin_c_all, sqme_spin_c_arr) do j = 1, size (sqme_spin_c, dim=2) do i = j, size (sqme_spin_c, dim=1) !!! Restoring the symmetric matrix packed into a 1-dim array !!! c.f. [[prc_openloops_compute_sqme_spin_c]] sqme_spin_c(i,j) = sqme_spin_c_arr(j + i * (i - 1) / 2) if (i /= j) sqme_spin_c(j,i) = sqme_spin_c(i,j) end do end do pcm_instance%real_sub%sqme_born_spin_c(:,:,emitter,i_flv) = sqme_spin_c end if end do deallocate (sqme_spin_c_all) end do end select class default call msg_fatal ("Spin correlations so far only supported by OpenLoops.") end select end if end select end subroutine term_instance_evaluate_spin_correlations @ %def term_instance_evaluate_spin_correlations @ <>= procedure :: apply_fks => term_instance_apply_fks <>= subroutine term_instance_apply_fks (term, alpha_s_sub, alpha_qed_sub) class(term_instance_t), intent(inout) :: term real(default), intent(in) :: alpha_s_sub, alpha_qed_sub real(default), dimension(:), allocatable :: sqme integer :: i, i_phs, emitter logical :: is_subtraction select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) select type (config => pcm_instance%config) type is (pcm_nlo_t) if (term%connected%has_matrix) then allocate (sqme (config%get_n_alr ())) else allocate (sqme (1)) end if sqme = zero select type (phs => term%k_term%phs) type is (phs_fks_t) if (pcm_instance%config%has_pdfs .and. & config%settings%use_internal_color_correlations) then call msg_fatal ("Color correlations for proton processes " // & "so far only supported by OpenLoops.") end if call pcm_instance%set_real_and_isr_kinematics & (phs%phs_identifiers, term%k_term%phs%get_sqrts ()) if (term%k_term%emitter < 0) then call pcm_instance%set_subtraction_event () do i_phs = 1, config%region_data%n_phs emitter = phs%phs_identifiers(i_phs)%emitter call pcm_instance%real_sub%compute (emitter, & i_phs, alpha_s_sub, alpha_qed_sub, term%connected%has_matrix, sqme) end do else call pcm_instance%set_radiation_event () emitter = term%k_term%emitter; i_phs = term%k_term%i_phs do i = 1, term%connected%trace%get_qn_index_n_flv () pcm_instance%real_sub%sqme_real_non_sub (i, i_phs) = & real (term%connected%trace%get_matrix_element ( & term%connected%trace%get_qn_index (i))) end do call pcm_instance%real_sub%compute (emitter, i_phs, alpha_s_sub, & alpha_qed_sub, term%connected%has_matrix, sqme) end if end select end select end select if (term%connected%has_trace) & call term%connected%trace%set_only_matrix_element & (1, cmplx (sum(sqme), 0, default)) select type (config => term%pcm_instance%config) type is (pcm_nlo_t) is_subtraction = term%k_term%emitter < 0 if (term%connected%has_matrix) & call refill_evaluator (cmplx (sqme * term%weight, 0, default), & config%get_qn (is_subtraction), & config%region_data%get_flavor_indices (is_subtraction), & term%connected%matrix) if (term%connected%has_flows) & call refill_evaluator (cmplx (sqme * term%weight, 0, default), & config%get_qn (is_subtraction), & config%region_data%get_flavor_indices (is_subtraction), & term%connected%flows) end select end subroutine term_instance_apply_fks @ %def term_instance_apply_fks @ <>= procedure :: evaluate_sqme_virt => term_instance_evaluate_sqme_virt <>= subroutine term_instance_evaluate_sqme_virt (term, alpha_s, alpha_qed) class(term_instance_t), intent(inout) :: term real(default), intent(in) :: alpha_s, alpha_qed real(default) :: alpha_coupling type(vector4_t), dimension(:), allocatable :: p_born real(default), dimension(:), allocatable :: sqme_virt integer :: i_flv if (term%nlo_type /= NLO_VIRTUAL) call msg_fatal & ("Trying to evaluate virtual matrix element with unsuited term_instance.") if (debug2_active (D_VIRTUAL)) then call msg_debug2 (D_VIRTUAL, "Evaluating virtual-subtracted matrix elements") print *, 'ren_scale: ', term%ren_scale print *, 'fac_scale: ', term%fac_scale print *, 'Ellis-Sexton scale:', term%es_scale end if select type (config => term%pcm_instance%config) type is (pcm_nlo_t) select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) select case (char (config%region_data%regions(1)%nlo_correction_type)) case ("QCD") alpha_coupling = alpha_s if (debug2_active (D_VIRTUAL)) print *, 'alpha_s: ', alpha_coupling case ("EW") alpha_coupling = alpha_qed if (debug2_active (D_VIRTUAL)) print *, 'alpha_qed: ', alpha_coupling end select allocate (p_born (config%region_data%n_legs_born)) if (config%settings%factorization_mode == FACTORIZATION_THRESHOLD) then p_born = pcm_instance%real_kinematics%p_born_onshell%get_momenta(1) else p_born = term%int_hard%get_momenta () end if call pcm_instance%set_momenta_and_scales_virtual & (p_born, term%ren_scale, term%fac_scale, term%es_scale) select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) associate (virtual => pcm_instance%virtual) do i_flv = 1, term%connected%trace%get_qn_index_n_flv () virtual%sqme_born(i_flv) = & real (term%connected%trace%get_matrix_element ( & term%connected%trace%get_qn_index (i_flv, i_sub = 0))) virtual%sqme_virt_fin(i_flv) = & real (term%connected%trace%get_matrix_element ( & term%connected%trace%get_qn_index (i_flv, i_sub = 1))) end do end associate end select call pcm_instance%compute_sqme_virt (term%p_hard, alpha_coupling, & term%connected%has_matrix, sqme_virt) call term%connected%trace%set_only_matrix_element & (1, cmplx (sum(sqme_virt), 0, default)) if (term%connected%has_matrix) & call refill_evaluator (cmplx (sqme_virt * term%weight, 0, default), & config%get_qn (.true.), & remove_duplicates_from_int_array ( & config%region_data%get_flavor_indices (.true.)), & term%connected%matrix) if (term%connected%has_flows) & call refill_evaluator (cmplx (sqme_virt * term%weight, 0, default), & config%get_qn (.true.), & remove_duplicates_from_int_array ( & config%region_data%get_flavor_indices (.true.)), & term%connected%flows) end select end select end subroutine term_instance_evaluate_sqme_virt @ %def term_instance_evaluate_sqme_virt @ <>= procedure :: evaluate_sqme_mismatch => term_instance_evaluate_sqme_mismatch <>= subroutine term_instance_evaluate_sqme_mismatch (term, alpha_s) class(term_instance_t), intent(inout) :: term real(default), intent(in) :: alpha_s real(default), dimension(:), allocatable :: sqme_mism if (term%nlo_type /= NLO_MISMATCH) call msg_fatal & ("Trying to evaluate soft mismatch with unsuited term_instance.") select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) call pcm_instance%compute_sqme_mismatch & (alpha_s, term%connected%has_matrix, sqme_mism) end select call term%connected%trace%set_only_matrix_element & (1, cmplx (sum (sqme_mism) * term%weight, 0, default)) if (term%connected%has_matrix) then select type (config => term%pcm_instance%config) type is (pcm_nlo_t) if (term%connected%has_matrix) & call refill_evaluator (cmplx (sqme_mism * term%weight, 0, default), & config%get_qn (.true.), & remove_duplicates_from_int_array ( & config%region_data%get_flavor_indices (.true.)), & term%connected%matrix) if (term%connected%has_flows) & call refill_evaluator (cmplx (sqme_mism * term%weight, 0, default), & config%get_qn (.true.), & remove_duplicates_from_int_array ( & config%region_data%get_flavor_indices (.true.)), & term%connected%flows) end select end if end subroutine term_instance_evaluate_sqme_mismatch @ %def term_instance_evaluate_sqme_mismatch @ <>= procedure :: evaluate_sqme_dglap => term_instance_evaluate_sqme_dglap <>= subroutine term_instance_evaluate_sqme_dglap (term, alpha_s, alpha_qed) class(term_instance_t), intent(inout) :: term real(default), intent(in) :: alpha_s, alpha_qed real(default) :: alpha_coupling real(default), dimension(:), allocatable :: sqme_dglap integer :: i_flv if (term%nlo_type /= NLO_DGLAP) call msg_fatal & ("Trying to evaluate DGLAP remnant with unsuited term_instance.") if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "term_instance_evaluate_sqme_dglap") select type (config => term%pcm_instance%config) type is (pcm_nlo_t) select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) select case (char (config%region_data%regions(1)%nlo_correction_type)) case ("QCD") alpha_coupling = alpha_s case ("EW") alpha_coupling = alpha_qed end select if (debug2_active (D_PROCESS_INTEGRATION)) then associate (n_flv => pcm_instance%dglap_remnant%reg_data%n_flv_born) print *, "size(sqme_born) = ", size (pcm_instance%dglap_remnant%sqme_born) call term%connected%trace%write () do i_flv = 1, n_flv print *, "i_flv = ", i_flv, ", n_flv = ", n_flv print *, "sqme_born(i_flv) = ", pcm_instance%dglap_remnant%sqme_born(i_flv) end do end associate end if call pcm_instance%compute_sqme_dglap_remnant (alpha_coupling, & term%connected%has_matrix, sqme_dglap) end select end select call term%connected%trace%set_only_matrix_element & (1, cmplx (sum (sqme_dglap) * term%weight, 0, default)) if (term%connected%has_matrix) then select type (config => term%pcm_instance%config) type is (pcm_nlo_t) if (term%connected%has_matrix) & call refill_evaluator (cmplx (sqme_dglap * term%weight, 0, default), & config%get_qn (.true.), & remove_duplicates_from_int_array ( & config%region_data%get_flavor_indices (.true.)), & term%connected%matrix) if (term%connected%has_flows) & call refill_evaluator (cmplx (sqme_dglap * term%weight, 0, default), & config%get_qn (.true.), & remove_duplicates_from_int_array ( & config%region_data%get_flavor_indices (.true.)), & term%connected%flows) end select end if end subroutine term_instance_evaluate_sqme_dglap @ %def term_instance_evaluate_sqme_dglap @ Reset the term instance: clear the parton-state expressions and deactivate. <>= procedure :: reset => term_instance_reset <>= subroutine term_instance_reset (term) class(term_instance_t), intent(inout) :: term call term%connected%reset_expressions () if (allocated (term%alpha_qcd_forced)) deallocate (term%alpha_qcd_forced) term%active = .false. end subroutine term_instance_reset @ %def term_instance_reset @ Force an $\alpha_s$ value that should be used in the matrix-element calculation. <>= procedure :: set_alpha_qcd_forced => term_instance_set_alpha_qcd_forced <>= subroutine term_instance_set_alpha_qcd_forced (term, alpha_qcd) class(term_instance_t), intent(inout) :: term real(default), intent(in) :: alpha_qcd if (allocated (term%alpha_qcd_forced)) then term%alpha_qcd_forced = alpha_qcd else allocate (term%alpha_qcd_forced, source = alpha_qcd) end if end subroutine term_instance_set_alpha_qcd_forced @ %def term_instance_set_alpha_qcd_forced @ Complete the kinematics computation for the effective parton states. We assume that the [[compute_hard_kinematics]] method of the process component instance has already been called, so the [[int_hard]] contains the correct hard kinematics. The duty of this procedure is first to compute the effective kinematics and store this in the [[int_eff]] effective interaction inside the [[isolated]] parton state. The effective kinematics may differ from the kinematics in the hard interaction. It may involve parton recombination or parton splitting. The [[rearrange_partons]] method is responsible for this part. We may also call a method to compute the effective structure-function chain at this point. This is not implemented yet. In the simple case that no rearrangement is necessary, as indicated by the [[rearrange]] flag, the effective interaction is a pointer to the hard interaction, and we can skip the rearrangement method. Similarly for the effective structure-function chain. (If we have an algorithm that uses rarrangement, it should evaluate [[k_term]] explicitly.) The final step of kinematics setup is to transfer the effective kinematics to the evaluators and to the [[subevt]]. <>= procedure :: compute_eff_kinematics => & term_instance_compute_eff_kinematics <>= subroutine term_instance_compute_eff_kinematics (term) class(term_instance_t), intent(inout) :: term term%checked = .false. term%passed = .false. call term%isolated%receive_kinematics () call term%connected%receive_kinematics () end subroutine term_instance_compute_eff_kinematics @ %def term_instance_compute_eff_kinematics @ Inverse. Reconstruct the connected state from the momenta in the trace evaluator (which we assume to be set), then reconstruct the isolated state as far as possible. The second part finalizes the momentum configuration, using the incoming seed momenta <>= procedure :: recover_hard_kinematics => & term_instance_recover_hard_kinematics <>= subroutine term_instance_recover_hard_kinematics (term) class(term_instance_t), intent(inout) :: term term%checked = .false. term%passed = .false. call term%connected%send_kinematics () call term%isolated%send_kinematics () end subroutine term_instance_recover_hard_kinematics @ %def term_instance_recover_hard_kinematics @ Check the term whether it passes cuts and, if successful, evaluate scales and weights. The factorization scale is also given to the term kinematics, enabling structure-function evaluation. <>= procedure :: evaluate_expressions => & term_instance_evaluate_expressions <>= subroutine term_instance_evaluate_expressions (term, scale_forced) class(term_instance_t), intent(inout) :: term real(default), intent(in), allocatable, optional :: scale_forced call term%connected%evaluate_expressions (term%passed, & term%scale, term%fac_scale, term%ren_scale, term%weight, & scale_forced, force_evaluation = .true.) term%checked = .true. end subroutine term_instance_evaluate_expressions @ %def term_instance_evaluate_expressions @ Evaluate the trace: first evaluate the hard interaction, then the trace evaluator. We use the [[evaluate_interaction]] method of the process component which generated this term. The [[subevt]] and cut expressions are not yet filled. The [[component]] argument is intent(inout) because the [[compute_amplitude]] method may modify the [[core_state]] workspace object. <>= procedure :: evaluate_interaction => term_instance_evaluate_interaction <>= subroutine term_instance_evaluate_interaction (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(in), pointer :: core if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, & "term_instance_evaluate_interaction") if (term%k_term%only_cm_frame .and. (.not. term%k_term%lab_is_cm())) then term%p_hard = term%get_boost_to_cms () * term%int_hard%get_momenta () else term%p_hard = term%int_hard%get_momenta () end if select type (core) class is (prc_external_t) call term%evaluate_interaction_userdef (core) class default call term%evaluate_interaction_default (core) end select call term%int_hard%set_matrix_element (term%amp) end subroutine term_instance_evaluate_interaction @ %def term_instance_evaluate_interaction @ <>= procedure :: evaluate_interaction_default & => term_instance_evaluate_interaction_default <>= subroutine term_instance_evaluate_interaction_default (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(in) :: core integer :: i do i = 1, term%config%n_allowed term%amp(i) = core%compute_amplitude (term%config%i_term, term%p_hard, & term%config%flv(i), term%config%hel(i), term%config%col(i), & term%fac_scale, term%ren_scale, term%alpha_qcd_forced, & term%core_state) end do select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) call pcm_instance%set_fac_scale (term%fac_scale) end select end subroutine term_instance_evaluate_interaction_default @ %def term_instance_evaluate_interaction_default @ <>= procedure :: evaluate_interaction_userdef & => term_instance_evaluate_interaction_userdef <>= subroutine term_instance_evaluate_interaction_userdef (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(inout) :: core if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, & "term_instance_evaluate_interaction_userdef") select type (core_state => term%core_state) type is (openloops_state_t) select type (core) type is (prc_openloops_t) call core%compute_alpha_s (core_state, term%ren_scale) if (allocated (core_state%threshold_data)) & call evaluate_threshold_parameters (core_state, core, term%k_term%phs%get_sqrts ()) end select class is (prc_external_state_t) select type (core) class is (prc_external_t) call core%compute_alpha_s (core_state, term%ren_scale) end select end select call evaluate_threshold_interaction () if (term%nlo_type == NLO_VIRTUAL) then call term%evaluate_interaction_userdef_loop (core) else call term%evaluate_interaction_userdef_tree (core) end if select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) call pcm_instance%set_fac_scale (term%fac_scale) end select contains subroutine evaluate_threshold_parameters (core_state, core, sqrts) type(openloops_state_t), intent(inout) :: core_state type(prc_openloops_t), intent(inout) :: core real(default), intent(in) :: sqrts real(default) :: mtop, wtop mtop = m1s_to_mpole (sqrts) wtop = core_state%threshold_data%compute_top_width & (mtop, core_state%alpha_qcd) call core%set_mass_and_width (6, mtop, wtop) end subroutine subroutine evaluate_threshold_interaction () integer :: leg select type (core) type is (prc_threshold_t) if (term%nlo_type > BORN) then select type (pcm => term%pcm_instance) type is (pcm_instance_nlo_t) if (term%k_term%emitter >= 0) then call core%set_offshell_momenta & (pcm%real_kinematics%p_real_cms%get_momenta(term%config%i_term)) leg = thr_leg (term%k_term%emitter) call core%set_leg (leg) call core%set_onshell_momenta & (pcm%real_kinematics%p_real_onshell(leg)%get_momenta(term%config%i_term)) else call core%set_leg (0) call core%set_offshell_momenta & (pcm%real_kinematics%p_born_cms%get_momenta(1)) end if end select else call core%set_leg (-1) call core%set_offshell_momenta (term%p_hard) end if end select end subroutine evaluate_threshold_interaction end subroutine term_instance_evaluate_interaction_userdef @ %def term_instance_evaluate_interaction_userdef @ Retrieve the matrix elements from a matrix element provider and place them into [[term%amp]]. For the handling of NLO calculations, FKS applies a book keeping handling flavor and/or particle type (e.g. for QCD: quark/gluon and quark flavor) in order to calculate the subtraction terms. Therefore, we have to insert the calculated matrix elements correctly into the state matrix where each entry corresponds to a set of quantum numbers. We apply a mapping [[hard_qn_ind]] from a list of quantum numbers provided by FKS to the hard process [[int_hard]]. The calculated matrix elements are insert into [[term%amp]] in the following way. The first [[n_born]] particles are the matrix element of the hard process. In non-trivial beams, we store another [[n_beams_rescaled]] copies of these matrix elements as the first [[n_beams_rescaled]] subtractions. This is a remnant from times before the method [[term_instance_set_sf_factors]] and these entries are not used anymore. However, eliminating these entries involves deeper changes in how the connection tables for the evaluator product are set up and should therefore be part of a larger refactoring of the interactions \& state matrices. The next $n_{\text{born}}\times n_{sub_color}$ are color-correlated Born matrix elements, with then again the next $n_{\text{born}}\times n_{emitters}\times n_{sub_spin}$ being spin-correlated Born matrix elements. If two or more flavor structures would produce the same amplitude we only compute one and use the [[eqv_index]] determined by the [[prc_core]] and just copy the result to improve performance. <>= procedure :: evaluate_interaction_userdef_tree & => term_instance_evaluate_interaction_userdef_tree <>= subroutine term_instance_evaluate_interaction_userdef_tree (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(inout) :: core real(default) :: sqme real(default), dimension(:), allocatable :: sqme_color_c real(default), dimension(:), allocatable :: sqme_spin_c real(default), dimension(6) :: sqme_spin_c_tmp integer :: n_flv, n_hel, n_sub_color, n_sub_spin, n_pdf_off integer :: i_flv, i_hel, i_sub, i_color_c, i_color_c_eqv, i_spin_c, i_spin_c_eqv integer :: i_flv_eqv, i_hel_eqv integer :: emitter, i_emitter logical :: bad_point, bp logical, dimension(:,:), allocatable :: eqv_me_evaluated if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, & "term_instance_evaluate_interaction_userdef_tree") allocate (sqme_color_c (blha_result_array_size & (term%int_hard%get_n_tot (), BLHA_AMP_COLOR_C))) n_flv = term%int_hard%get_qn_index_n_flv () n_hel = term%int_hard%get_qn_index_n_hel () n_sub_color = term%get_n_sub_color () n_sub_spin = term%get_n_sub_spin () allocate (eqv_me_evaluated(n_flv,n_hel)) eqv_me_evaluated = .false. do i_flv = 1, n_flv do i_hel = 1, n_hel i_flv_eqv = core%data%eqv_flv_index(i_flv) i_hel_eqv = core%data%eqv_hel_index(i_hel) if (.not. eqv_me_evaluated(i_flv_eqv, i_hel_eqv)) then select type (core) class is (prc_external_t) call core%update_alpha_s (term%core_state, term%ren_scale) call core%compute_sqme (i_flv, i_hel, term%p_hard, term%ren_scale, & sqme, bad_point) call term%pcm_instance%set_bad_point (bad_point) associate (i_int => term%int_hard%get_qn_index & (i_flv = i_flv, i_hel = i_hel, i_sub = 0)) term%amp(i_int) = cmplx (sqme, 0, default) end associate end select n_pdf_off = 0 if (term%pcm_instance%config%has_pdfs .and. & (term%is_subtraction () .or. term%nlo_type == NLO_DGLAP)) then n_pdf_off = n_pdf_off + n_beams_rescaled do i_sub = 1, n_pdf_off term%amp(term%int_hard%get_qn_index (i_flv, i_hel, i_sub)) = & term%amp(term%int_hard%get_qn_index (i_flv, i_hel, i_sub = 0)) end do end if if ((term%nlo_type == NLO_REAL .and. term%is_subtraction ()) .or. & term%nlo_type == NLO_MISMATCH) then sqme_color_c = zero select type (core) class is (prc_blha_t) call core%compute_sqme_color_c_raw (i_flv, i_hel, & term%p_hard, term%ren_scale, sqme_color_c, bad_point) call term%pcm_instance%set_bad_point (bad_point) class is (prc_recola_t) call core%compute_sqme_color_c_raw (i_flv, i_hel, & term%p_hard, term%ren_scale, sqme_color_c, bad_point) call term%pcm_instance%set_bad_point (bad_point) end select do i_sub = 1, n_sub_color i_color_c = term%int_hard%get_qn_index & (i_flv, i_hel, i_sub + n_pdf_off) term%amp(i_color_c) = cmplx (sqme_color_c(i_sub), 0, default) end do if (n_sub_spin > 0) then bad_point = .false. allocate (sqme_spin_c(0)) select type (core) type is (prc_openloops_t) select type (config => term%pcm_instance%config) type is (pcm_nlo_t) do i_emitter = 1, config%region_data%n_emitters emitter = config%region_data%emitters(i_emitter) if (emitter > 0) then call core%compute_sqme_spin_c & (i_flv, & i_hel, & emitter, & term%p_hard, & term%ren_scale, & sqme_spin_c_tmp, & bp) sqme_spin_c = [sqme_spin_c, sqme_spin_c_tmp] bad_point = bad_point .or. bp end if end do end select do i_sub = 1, n_sub_spin i_spin_c = term%int_hard%get_qn_index (i_flv, i_hel, & i_sub + n_pdf_off + n_sub_color) term%amp(i_spin_c) = cmplx & (sqme_spin_c(i_sub), 0, default) end do end select deallocate (sqme_spin_c) end if end if eqv_me_evaluated(i_flv_eqv, i_hel_eqv) = .true. else associate (i_int => term%int_hard%get_qn_index & (i_flv = i_flv, i_hel = i_hel, i_sub = 0), & i_int_eqv => term%int_hard%get_qn_index & (i_flv = i_flv_eqv, i_hel = i_hel_eqv, i_sub = 0)) term%amp(i_int) = term%amp(i_int_eqv) end associate n_pdf_off = 0 if (term%pcm_instance%config%has_pdfs .and. & (term%is_subtraction () .or. term%nlo_type == NLO_DGLAP)) then n_pdf_off = n_pdf_off + n_beams_rescaled do i_sub = 1, n_pdf_off term%amp(term%int_hard%get_qn_index (i_flv, i_hel, i_sub)) = & term%amp(term%int_hard%get_qn_index (i_flv, i_hel, i_sub = 0)) end do end if if ((term%nlo_type == NLO_REAL .and. term%is_subtraction ()) .or. & term%nlo_type == NLO_MISMATCH) then do i_sub = 1, n_sub_color i_color_c = term%int_hard%get_qn_index & (i_flv, i_hel, i_sub + n_pdf_off) i_color_c_eqv = term%int_hard%get_qn_index & (i_flv_eqv, i_hel_eqv, i_sub + n_pdf_off) term%amp(i_color_c) = term%amp(i_color_c_eqv) end do do i_sub = 1, n_sub_spin i_spin_c = term%int_hard%get_qn_index (i_flv, i_hel, & i_sub + n_pdf_off + n_sub_color) i_spin_c_eqv = term%int_hard%get_qn_index (i_flv_eqv, i_hel_eqv, & i_sub + n_pdf_off + n_sub_color) term%amp(i_spin_c) = term%amp(i_spin_c_eqv) end do end if end if end do end do end subroutine term_instance_evaluate_interaction_userdef_tree @ %def term_instance_evaluate_interaction_userdef_tree @ Same as for [[term_instance_evaluate_interaction_userdef_tree]], but for the integrated-subtraction and finite one-loop terms. We only need color-correlated Born matrix elements, but an additional entry per flavor structure for the finite one-loop contribution. We thus have $2+n_{sub_color}$ entries in the [[term%amp]] for each [[i_flv]] and [[i_hel]] combination. If two or more flavor structures would produce the same amplitude we only compute one and use the [[eqv_index]] determined by the [[prc_core]] and just copy the result to improve performance. <>= procedure :: evaluate_interaction_userdef_loop & => term_instance_evaluate_interaction_userdef_loop <>= subroutine term_instance_evaluate_interaction_userdef_loop (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(in) :: core integer :: n_hel, n_sub, n_flv integer :: i, i_flv, i_hel, i_sub, i_virt, i_color_c, i_color_c_eqv integer :: i_flv_eqv, i_hel_eqv real(default), dimension(4) :: sqme_virt real(default), dimension(:), allocatable :: sqme_color_c logical :: bad_point logical, dimension(:,:), allocatable :: eqv_me_evaluated if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, & "term_instance_evaluate_interaction_userdef_loop") allocate (sqme_color_c (blha_result_array_size & (term%int_hard%get_n_tot (), BLHA_AMP_COLOR_C))) n_flv = term%int_hard%get_qn_index_n_flv () n_hel = term%int_hard%get_qn_index_n_hel () n_sub = term%int_hard%get_qn_index_n_sub () allocate (eqv_me_evaluated(n_flv,n_hel)) eqv_me_evaluated = .false. i_virt = 1 do i_flv = 1, n_flv do i_hel = 1, n_hel i_flv_eqv = core%data%eqv_flv_index(i_flv) i_hel_eqv = core%data%eqv_hel_index(i_hel) if (.not. eqv_me_evaluated(i_flv_eqv, i_hel_eqv)) then select type (core) class is (prc_external_t) call core%compute_sqme_virt (i_flv, i_hel, term%p_hard, & term%ren_scale, term%es_scale, & term%pcm_instance%config%blha_defaults%loop_method, & sqme_virt, bad_point) call term%pcm_instance%set_bad_point (bad_point) end select associate (i_born => term%int_hard%get_qn_index (i_flv, i_hel = i_hel, i_sub = 0), & i_loop => term%int_hard%get_qn_index (i_flv, i_hel = i_hel, i_sub = i_virt)) term%amp(i_loop) = cmplx (sqme_virt(3), 0, default) term%amp(i_born) = cmplx (sqme_virt(4), 0, default) end associate select type (config => term%pcm_instance%config) type is (pcm_nlo_t) select type (core) class is (prc_blha_t) call core%compute_sqme_color_c_raw (i_flv, i_hel, & term%p_hard, term%ren_scale, & sqme_color_c, bad_point) call term%pcm_instance%set_bad_point (bad_point) do i_sub = 1 + i_virt, n_sub i_color_c = term%int_hard%get_qn_index & (i_flv, i_hel = i_hel, i_sub = i_sub) ! Index shift: i_sub - i_virt term%amp(i_color_c) = & cmplx (sqme_color_c(i_sub - i_virt), 0, default) end do type is (prc_recola_t) call core%compute_sqme_color_c_raw (i_flv, i_hel, & term%p_hard, term%ren_scale, sqme_color_c, bad_point) call term%pcm_instance%set_bad_point (bad_point) do i_sub = 1 + i_virt, n_sub i_color_c = term%int_hard%get_qn_index & (i_flv, i_hel = i_hel, i_sub = i_sub) ! Index shift: i_sub - i_virt term%amp(i_color_c) = & cmplx (sqme_color_c(i_sub - i_virt), 0, default) end do end select end select eqv_me_evaluated(i_flv_eqv, i_hel_eqv) = .true. else associate (i_born => term%int_hard%get_qn_index (i_flv, i_hel = i_hel, i_sub = 0), & i_loop => term%int_hard%get_qn_index (i_flv, i_hel = i_hel, i_sub = i_virt), & i_born_eqv => term%int_hard%get_qn_index & (i_flv_eqv, i_hel = i_hel_eqv, i_sub = 0), & i_loop_eqv => term%int_hard%get_qn_index & (i_flv_eqv, i_hel = i_hel_eqv, i_sub = 1)) term%amp(i_loop) = term%amp(i_loop_eqv) term%amp(i_born) = term%amp(i_born_eqv) end associate do i_sub = 1 + i_virt, n_sub i_color_c = term%int_hard%get_qn_index & (i_flv, i_hel = i_hel, i_sub = i_sub) i_color_c_eqv = term%int_hard%get_qn_index & (i_flv_eqv, i_hel = i_hel_eqv, i_sub = i_sub) ! Index shift: i_sub - i_virt term%amp(i_color_c) = term%amp(i_color_c_eqv) end do end if end do end do end subroutine term_instance_evaluate_interaction_userdef_loop @ %def term_instance_evaluate_interaction_userdef_loop @ Evaluate the trace. First evaluate the structure-function chain (i.e., the density matrix of the incoming partons). Do this twice, in case the sf-chain instances within [[k_term]] and [[isolated]] differ. Next, evaluate the hard interaction, then compute the convolution with the initial state. <>= procedure :: evaluate_trace => term_instance_evaluate_trace <>= subroutine term_instance_evaluate_trace (term) class(term_instance_t), intent(inout) :: term call term%k_term%evaluate_sf_chain (term%fac_scale, term%negative_sf) call term%evaluate_scaled_sf_chains () call term%isolated%evaluate_sf_chain (term%fac_scale) call term%isolated%evaluate_trace () call term%connected%evaluate_trace () end subroutine term_instance_evaluate_trace @ %def term_instance_evaluate_trace @ Include rescaled structure functions due to NLO calculation. We rescale the structure function for the real subtraction [[sf_rescale_collinear]], the collinear counter terms [[sf_rescale_dglap_t]] and for the case, in which we have an emitter in the initial state, we rescale the kinematics for it using [[sf_rescale_real_t]].\\ References: arXiv:0709.2092, (2.35)-(2.42).\\ Obviously, it is completely irrelevant, which beam is treated. It becomes problematic when handling [[e, p]]-beams. <>= procedure :: evaluate_scaled_sf_chains => term_instance_evaluate_scaled_sf_chains <>= subroutine term_instance_evaluate_scaled_sf_chains (term) class(term_instance_t), intent(inout) :: term class(sf_rescale_t), allocatable :: sf_rescale if (.not. term%pcm_instance%config%has_pdfs) return if (term%nlo_type == NLO_REAL) then if (term%is_subtraction ()) then allocate (sf_rescale_collinear_t :: sf_rescale) select type (pcm => term%pcm_instance) type is (pcm_instance_nlo_t) select type (sf_rescale) type is (sf_rescale_collinear_t) call sf_rescale%set (pcm%real_kinematics%xi_tilde) end select end select call term%k_term%sf_chain%evaluate (term%fac_scale, term%negative_sf, sf_rescale) deallocate (sf_rescale) else if (term%k_term%emitter >= 0 .and. term%k_term%emitter <= term%k_term%n_in) then allocate (sf_rescale_real_t :: sf_rescale) select type (pcm => term%pcm_instance) type is (pcm_instance_nlo_t) select type (sf_rescale) type is (sf_rescale_real_t) call sf_rescale%set (pcm%real_kinematics%xi_tilde * & pcm%real_kinematics%xi_max (term%k_term%i_phs), & pcm%real_kinematics%y (term%k_term%i_phs)) end select end select call term%k_term%sf_chain%evaluate (term%fac_scale, term%negative_sf, sf_rescale) deallocate (sf_rescale) else call term%k_term%sf_chain%evaluate (term%fac_scale, term%negative_sf) end if else if (term%nlo_type == NLO_DGLAP) then allocate (sf_rescale_dglap_t :: sf_rescale) select type (pcm => term%pcm_instance) type is (pcm_instance_nlo_t) select type (sf_rescale) type is (sf_rescale_dglap_t) call sf_rescale%set (pcm%isr_kinematics%z) end select end select call term%k_term%sf_chain%evaluate (term%fac_scale, term%negative_sf, sf_rescale) deallocate (sf_rescale) end if end subroutine term_instance_evaluate_scaled_sf_chains @ %def term_instance_evaluate_scaled_sf_chains @ Evaluate the extra data that we need for processing the object as a physical event. <>= procedure :: evaluate_event_data => term_instance_evaluate_event_data <>= subroutine term_instance_evaluate_event_data (term) class(term_instance_t), intent(inout) :: term logical :: only_momenta only_momenta = term%nlo_type > BORN call term%isolated%evaluate_event_data (only_momenta) call term%connected%evaluate_event_data (only_momenta) end subroutine term_instance_evaluate_event_data @ %def term_instance_evaluate_event_data @ <>= procedure :: set_fac_scale => term_instance_set_fac_scale <>= subroutine term_instance_set_fac_scale (term, fac_scale) class(term_instance_t), intent(inout) :: term real(default), intent(in) :: fac_scale term%fac_scale = fac_scale end subroutine term_instance_set_fac_scale @ %def term_instance_set_fac_scale @ Return data that might be useful for external processing. The factorization scale: <>= procedure :: get_fac_scale => term_instance_get_fac_scale <>= function term_instance_get_fac_scale (term) result (fac_scale) class(term_instance_t), intent(in) :: term real(default) :: fac_scale fac_scale = term%fac_scale end function term_instance_get_fac_scale @ %def term_instance_get_fac_scale @ We take the strong coupling from the process core. The value is calculated when a new event is requested, so we should call it only after the event has been evaluated. If it is not available there (a negative number is returned), we take the value stored in the term configuration, which should be determined by the model. If the model does not provide a value, the result is zero. <>= procedure :: get_alpha_s => term_instance_get_alpha_s <>= function term_instance_get_alpha_s (term, core) result (alpha_s) class(term_instance_t), intent(in) :: term class(prc_core_t), intent(in) :: core real(default) :: alpha_s alpha_s = core%get_alpha_s (term%core_state) if (alpha_s < zero) alpha_s = term%config%alpha_s end function term_instance_get_alpha_s @ %def term_instance_get_alpha_s @ <>= procedure :: reset_phs_identifiers => term_instance_reset_phs_identifiers <>= subroutine term_instance_reset_phs_identifiers (term) class(term_instance_t), intent(inout) :: term select type (phs => term%k_term%phs) type is (phs_fks_t) phs%phs_identifiers%evaluated = .false. end select end subroutine term_instance_reset_phs_identifiers @ %def term_instance_reset_phs_identifiers @ The second helicity for [[helicities]] comes with a minus sign because OpenLoops inverts the helicity index of antiparticles. <>= procedure :: get_helicities_for_openloops => term_instance_get_helicities_for_openloops <>= subroutine term_instance_get_helicities_for_openloops (term, helicities) class(term_instance_t), intent(in) :: term integer, dimension(:,:), allocatable, intent(out) :: helicities type(helicity_t), dimension(:), allocatable :: hel type(quantum_numbers_t), dimension(:,:), allocatable :: qn type(quantum_numbers_mask_t) :: qn_mask integer :: h, i, j, n_in call qn_mask%set_sub (1) call term%isolated%trace%get_quantum_numbers_mask (qn_mask, qn) n_in = term%int_hard%get_n_in () allocate (helicities (size (qn, dim=1), n_in)) allocate (hel (n_in)) do i = 1, size (qn, dim=1) do j = 1, n_in hel(j) = qn(i, j)%get_helicity () call hel(j)%diagonalize () call hel(j)%get_indices (h, h) helicities (i, j) = h end do end do end subroutine term_instance_get_helicities_for_openloops @ %def term_instance_get_helicities_for_openloops @ <>= procedure :: get_boost_to_lab => term_instance_get_boost_to_lab <>= function term_instance_get_boost_to_lab (term) result (lt) type(lorentz_transformation_t) :: lt class(term_instance_t), intent(in) :: term lt = term%k_term%phs%get_lorentz_transformation () end function term_instance_get_boost_to_lab @ %def term_instance_get_boost_to_lab @ <>= procedure :: get_boost_to_cms => term_instance_get_boost_to_cms <>= function term_instance_get_boost_to_cms (term) result (lt) type(lorentz_transformation_t) :: lt class(term_instance_t), intent(in) :: term lt = inverse (term%k_term%phs%get_lorentz_transformation ()) end function term_instance_get_boost_to_cms @ %def term_instance_get_boost_to_cms @ <>= procedure :: get_i_term_global => term_instance_get_i_term_global <>= elemental function term_instance_get_i_term_global (term) result (i_term) integer :: i_term class(term_instance_t), intent(in) :: term i_term = term%config%i_term_global end function term_instance_get_i_term_global @ %def term_instance_get_i_term_global @ <>= procedure :: is_subtraction => term_instance_is_subtraction <>= elemental function term_instance_is_subtraction (term) result (sub) logical :: sub class(term_instance_t), intent(in) :: term sub = term%config%i_term_global == term%config%i_sub end function term_instance_is_subtraction @ %def term_instance_is_subtraction @ Retrieve [[n_sub]] which was calculated in [[process_term_setup_interaction]]. <>= procedure :: get_n_sub => term_instance_get_n_sub procedure :: get_n_sub_color => term_instance_get_n_sub_color procedure :: get_n_sub_spin => term_instance_get_n_sub_spin <>= function term_instance_get_n_sub (term) result (n_sub) integer :: n_sub class(term_instance_t), intent(in) :: term n_sub = term%config%n_sub end function term_instance_get_n_sub function term_instance_get_n_sub_color (term) result (n_sub_color) integer :: n_sub_color class(term_instance_t), intent(in) :: term n_sub_color = term%config%n_sub_color end function term_instance_get_n_sub_color function term_instance_get_n_sub_spin (term) result (n_sub_spin) integer :: n_sub_spin class(term_instance_t), intent(in) :: term n_sub_spin = term%config%n_sub_spin end function term_instance_get_n_sub_spin @ %def term_instance_get_n_sub @ %def term_instance_get_n_sub_color @ %def term_instance_get_n_sub_spin @ \subsection{The process instance} A process instance contains all process data that depend on the sampling point and thus change often. In essence, it is an event record at the elementary (parton) level. We do not call it such, to avoid confusion with the actual event records. If decays are involved, the latter are compositions of several elementary processes (i.e., their instances). We implement the process instance as an extension of the [[mci_sampler_t]] that we need for computing integrals and generate events. The base type contains: the [[integrand]], the [[selected_channel]], the two-dimensional array [[x]] of parameters, and the one-dimensional array [[f]] of Jacobians. These subobjects are public and used for communicating with the multi-channel integrator. The [[process]] pointer accesses the process of which this record is an instance. It is required whenever the calculation needs invariant configuration data, therefore the process should stay in memory for the whole lifetime of its instances. The [[evaluation_status]] code is used to check the current status. In particular, failure at various stages is recorded there. The [[count]] object records process evaluations, broken down according to status. The [[sqme]] value is the single real number that results from evaluating and tracing the kinematics and matrix elements. This is the number that is handed over to an integration routine. The [[weight]] value is the event weight. It is defined when an event has been generated from the process instance, either weighted or unweighted. The value is the [[sqme]] value times Jacobian weights from the integration, or unity, respectively. The [[i_mci]] index chooses a subset of components that are associated with a common parameter set and integrator, i.e., that are added coherently. The [[sf_chain]] subobject is a realization of the beam and structure-function configuration in the [[process]] object. It is not used for calculation directly but serves as the template for the sf-chain instances that are contained in the [[component]] objects. The [[component]] subobjects determine the state of each component. The [[term]] subobjects are workspace for evaluating kinematics, matrix elements, cuts etc. The [[mci_work]] subobject contains the array of real input parameters (random numbers) that generates the kinematical point. It also contains the workspace for the MC integrators. The active entry of the [[mci_work]] array is selected by the [[i_mci]] index above. The [[hook]] pointer accesses a list of after evaluate objects which are evalutated after the matrix element. <>= public :: process_instance_t <>= type, extends (mci_sampler_t) :: process_instance_t type(process_t), pointer :: process => null () integer :: evaluation_status = STAT_UNDEFINED real(default) :: sqme = 0 real(default) :: weight = 0 real(default) :: excess = 0 integer :: n_dropped = 0 integer :: i_mci = 0 integer :: selected_channel = 0 type(sf_chain_t) :: sf_chain type(term_instance_t), dimension(:), allocatable :: term type(mci_work_t), dimension(:), allocatable :: mci_work class(pcm_instance_t), allocatable :: pcm class(process_instance_hook_t), pointer :: hook => null () contains <> end type process_instance_t @ %def process_instance @ Wrapper type for storing pointers to process instance objects in arrays. <>= public :: process_instance_ptr_t <>= type :: process_instance_ptr_t type(process_instance_t), pointer :: p => null () end type process_instance_ptr_t @ %def process_instance_ptr_t @ The process hooks are first-in-last-out list of objects which are evaluated after the phase space and matrixelement are evaluated. It is possible to retrieve the sampler object and read the sampler information. The hook object are part of the [[process_instance]] and therefore, share a common lifetime. A data transfer, after the usual lifetime of the [[process_instance]], is not provided, as such the finalisation procedure has to take care of this! E.g. write the object to file from which later the collected information can then be retrieved. <>= public :: process_instance_hook_t <>= type, abstract :: process_instance_hook_t class(process_instance_hook_t), pointer :: next => null () contains procedure(process_instance_hook_init), deferred :: init procedure(process_instance_hook_final), deferred :: final procedure(process_instance_hook_evaluate), deferred :: evaluate end type process_instance_hook_t @ %def process_instance_hook_t @ We have to provide a [[init]], a [[final]] procedure and, for after evaluation, the [[evaluate]] procedure. The [[init]] procedures accesses [[var_list]] and current [[instance]] object. <>= public :: process_instance_hook_final, process_instance_hook_evaluate <>= abstract interface subroutine process_instance_hook_init (hook, var_list, instance) import :: process_instance_hook_t, var_list_t, process_instance_t class(process_instance_hook_t), intent(inout), target :: hook type(var_list_t), intent(in) :: var_list class(process_instance_t), intent(in), target :: instance end subroutine process_instance_hook_init subroutine process_instance_hook_final (hook) import :: process_instance_hook_t class(process_instance_hook_t), intent(inout) :: hook end subroutine process_instance_hook_final subroutine process_instance_hook_evaluate (hook, instance) import :: process_instance_hook_t, process_instance_t class(process_instance_hook_t), intent(inout) :: hook class(process_instance_t), intent(in), target :: instance end subroutine process_instance_hook_evaluate end interface @ %def process_instance_hook_final, process_instance_hook_evaluate @ The output routine contains a header with the most relevant information about the process, copied from [[process_metadata_write]]. We mark the active components by an asterisk. The next section is the MC parameter input. The following sections are written only if the evaluation status is beyond setting the parameters, or if the [[verbose]] option is set. <>= procedure :: write_header => process_instance_write_header procedure :: write => process_instance_write <>= subroutine process_instance_write_header (object, unit, testflag) class(process_instance_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: testflag integer :: u u = given_output_unit (unit) call write_separator (u, 2) if (associated (object%process)) then call object%process%write_meta (u, testflag) else write (u, "(1x,A)") "Process instance [undefined process]" return end if write (u, "(3x,A)", advance = "no") "status = " select case (object%evaluation_status) case (STAT_INITIAL); write (u, "(A)") "initialized" case (STAT_ACTIVATED); write (u, "(A)") "activated" case (STAT_BEAM_MOMENTA); write (u, "(A)") "beam momenta set" case (STAT_FAILED_KINEMATICS); write (u, "(A)") "failed kinematics" case (STAT_SEED_KINEMATICS); write (u, "(A)") "seed kinematics" case (STAT_HARD_KINEMATICS); write (u, "(A)") "hard kinematics" case (STAT_EFF_KINEMATICS); write (u, "(A)") "effective kinematics" case (STAT_FAILED_CUTS); write (u, "(A)") "failed cuts" case (STAT_PASSED_CUTS); write (u, "(A)") "passed cuts" case (STAT_EVALUATED_TRACE); write (u, "(A)") "evaluated trace" call write_separator (u) write (u, "(3x,A,ES19.12)") "sqme = ", object%sqme case (STAT_EVENT_COMPLETE); write (u, "(A)") "event complete" call write_separator (u) write (u, "(3x,A,ES19.12)") "sqme = ", object%sqme write (u, "(3x,A,ES19.12)") "weight = ", object%weight if (.not. vanishes (object%excess)) & write (u, "(3x,A,ES19.12)") "excess = ", object%excess case default; write (u, "(A)") "undefined" end select if (object%i_mci /= 0) then call write_separator (u) call object%mci_work(object%i_mci)%write (u, testflag) end if call write_separator (u, 2) end subroutine process_instance_write_header subroutine process_instance_write (object, unit, testflag) class(process_instance_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: testflag integer :: u, i u = given_output_unit (unit) call object%write_header (u) if (object%evaluation_status >= STAT_BEAM_MOMENTA) then call object%sf_chain%write (u) call write_separator (u, 2) if (object%evaluation_status >= STAT_SEED_KINEMATICS) then if (object%evaluation_status >= STAT_HARD_KINEMATICS) then call write_separator (u, 2) write (u, "(1x,A)") "Active terms:" if (any (object%term%active)) then do i = 1, size (object%term) if (object%term(i)%active) then call write_separator (u) call object%term(i)%write (u, & show_eff_state = & object%evaluation_status >= STAT_EFF_KINEMATICS, & testflag = testflag) end if end do end if end if call write_separator (u, 2) end if end if end subroutine process_instance_write @ %def process_instance_write_header @ %def process_instance_write @ Initialization connects the instance with a process. All initial information is transferred from the process object. The process object contains templates for the interaction subobjects (beam and term), but no evaluators. The initialization routine creates evaluators for the matrix element trace, other evaluators are left untouched. Before we start generating, we double-check if the process library has been updated after the process was initializated ([[check_library_sanity]]). This may happen if between integration and event generation the library has been recompiled, so all links become broken. The [[instance]] object must have the [[target]] attribute (also in any caller) since the initialization routine assigns various pointers to subobject of [[instance]]. <>= procedure :: init => process_instance_init <>= subroutine process_instance_init (instance, process) class(process_instance_t), intent(out), target :: instance type(process_t), intent(inout), target :: process integer :: i class(pcm_t), pointer :: pcm type(process_term_t) :: term type(var_list_t), pointer :: var_list integer :: i_born, i_real, i_real_fin if (debug_on) call msg_debug (D_PROCESS_INTEGRATION, "process_instance_init") instance%process => process call instance%process%check_library_sanity () call instance%setup_sf_chain (process%get_beam_config_ptr ()) allocate (instance%mci_work (process%get_n_mci ())) do i = 1, size (instance%mci_work) call instance%process%init_mci_work (instance%mci_work(i), i) end do call instance%process%reset_selected_cores () pcm => instance%process%get_pcm_ptr () call pcm%allocate_instance (instance%pcm) call instance%pcm%link_config (pcm) select type (pcm) type is (pcm_nlo_t) !!! The process is kept when the integration is finalized, but not the !!! process_instance. Thus, we check whether pcm has been initialized !!! but set up the pcm_instance each time. i_real_fin = process%get_associated_real_fin (1) if (.not. pcm%initialized) then ! i_born = pcm%get_i_core_nlo_type (BORN) i_born = pcm%get_i_core (pcm%i_born) ! i_real = pcm%get_i_core_nlo_type (NLO_REAL, include_sub = .false.) ! i_real = pcm%get_i_core_nlo_type (NLO_REAL) i_real = pcm%get_i_core (pcm%i_real) term = process%get_term_ptr (process%get_i_term (i_real)) call pcm%init_qn (process%get_model_ptr ()) if (i_real_fin > 0) call pcm%allocate_ps_matching () var_list => process%get_var_list_ptr () if (var_list%get_sval (var_str ("$dalitz_plot")) /= var_str ('')) & call pcm%activate_dalitz_plot (var_list%get_sval (var_str ("$dalitz_plot"))) end if pcm%initialized = .true. select type (pcm_instance => instance%pcm) type is (pcm_instance_nlo_t) call pcm_instance%init_config (process%component_can_be_integrated (), & process%get_nlo_type_component (), process%get_energy (), & i_real_fin, process%get_model_ptr ()) end select end select allocate (instance%term (process%get_n_terms ())) do i = 1, process%get_n_terms () call instance%term(i)%init_from_process (process, i, instance%pcm, & instance%sf_chain) end do call instance%set_i_mci_to_real_component () call instance%find_same_kinematics () instance%evaluation_status = STAT_INITIAL end subroutine process_instance_init @ %def process_instance_init @ @ Finalize all subobjects that may contain allocated pointers. <>= procedure :: final => process_instance_final <>= subroutine process_instance_final (instance) class(process_instance_t), intent(inout) :: instance class(process_instance_hook_t), pointer :: current integer :: i instance%process => null () if (allocated (instance%mci_work)) then do i = 1, size (instance%mci_work) call instance%mci_work(i)%final () end do deallocate (instance%mci_work) end if call instance%sf_chain%final () if (allocated (instance%term)) then do i = 1, size (instance%term) call instance%term(i)%final () end do deallocate (instance%term) end if call instance%pcm%final () instance%evaluation_status = STAT_UNDEFINED do while (associated (instance%hook)) current => instance%hook call current%final () instance%hook => current%next deallocate (current) end do instance%hook => null () end subroutine process_instance_final @ %def process_instance_final @ Revert the process instance to initial state. We do not deallocate anything, just reset the state index and deactivate all components and terms. We do not reset the choice of the MCI set [[i_mci]] unless this is required explicitly. <>= procedure :: reset => process_instance_reset <>= subroutine process_instance_reset (instance, reset_mci) class(process_instance_t), intent(inout) :: instance logical, intent(in), optional :: reset_mci integer :: i call instance%process%reset_selected_cores () do i = 1, size (instance%term) call instance%term(i)%reset () end do instance%term%checked = .false. instance%term%passed = .false. instance%term%k_term%new_seed = .true. if (present (reset_mci)) then if (reset_mci) instance%i_mci = 0 end if instance%selected_channel = 0 instance%evaluation_status = STAT_INITIAL end subroutine process_instance_reset @ %def process_instance_reset @ \subsubsection{Integration and event generation} The sampler test should just evaluate the squared matrix element [[n_calls]] times, discarding the results, and return. This can be done before integration, e.g., for timing estimates. <>= procedure :: sampler_test => process_instance_sampler_test <>= subroutine process_instance_sampler_test (instance, i_mci, n_calls) class(process_instance_t), intent(inout), target :: instance integer, intent(in) :: i_mci integer, intent(in) :: n_calls integer :: i_mci_work i_mci_work = instance%process%get_i_mci_work (i_mci) call instance%choose_mci (i_mci_work) call instance%reset_counter () call instance%process%sampler_test (instance, n_calls, i_mci_work) call instance%process%set_counter_mci_entry (i_mci_work, instance%get_counter ()) end subroutine process_instance_sampler_test @ %def process_instance_sampler_test @ Generate a weighted event. We select one of the available MCI integrators by its index [[i_mci]] and thus generate an event for the associated (group of) process component(s). The arguments exactly correspond to the initializer and finalizer above. The resulting event is stored in the [[process_instance]] object, which also holds the workspace of the integrator. Note: The [[process]] object contains the random-number state, which changes for each event. Otherwise, all volatile data are inside the [[instance]] object. <>= procedure :: generate_weighted_event => process_instance_generate_weighted_event <>= subroutine process_instance_generate_weighted_event (instance, i_mci) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_mci integer :: i_mci_work i_mci_work = instance%process%get_i_mci_work (i_mci) call instance%choose_mci (i_mci_work) associate (mci_work => instance%mci_work(i_mci_work)) call instance%process%generate_weighted_event & (i_mci_work, mci_work, instance, & instance%keep_failed_events ()) end associate end subroutine process_instance_generate_weighted_event @ %def process_instance_generate_weighted_event @ <>= procedure :: generate_unweighted_event => process_instance_generate_unweighted_event <>= subroutine process_instance_generate_unweighted_event (instance, i_mci) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_mci integer :: i_mci_work i_mci_work = instance%process%get_i_mci_work (i_mci) call instance%choose_mci (i_mci_work) associate (mci_work => instance%mci_work(i_mci_work)) call instance%process%generate_unweighted_event & (i_mci_work, mci_work, instance) end associate end subroutine process_instance_generate_unweighted_event @ %def process_instance_generate_unweighted_event @ This replaces the event generation methods for the situation that the process instance object has been filled by other means (i.e., reading and/or recalculating its contents). We just have to fill in missing MCI data, especially the event weight. <>= procedure :: recover_event => process_instance_recover_event <>= subroutine process_instance_recover_event (instance) class(process_instance_t), intent(inout) :: instance integer :: i_mci i_mci = instance%i_mci call instance%process%set_i_mci_work (i_mci) associate (mci_instance => instance%mci_work(i_mci)%mci) call mci_instance%fetch (instance, instance%selected_channel) end associate end subroutine process_instance_recover_event @ %def process_instance_recover_event @ @ Activate the components and terms that correspond to a currently selected MCI parameter set. <>= procedure :: activate => process_instance_activate <>= subroutine process_instance_activate (instance) class(process_instance_t), intent(inout) :: instance integer :: i, j integer, dimension(:), allocatable :: i_term associate (mci_work => instance%mci_work(instance%i_mci)) call instance%process%select_components (mci_work%get_active_components ()) end associate associate (process => instance%process) do i = 1, instance%process%get_n_components () if (instance%process%component_is_selected (i)) then allocate (i_term (size (process%get_component_i_terms (i)))) i_term = process%get_component_i_terms (i) do j = 1, size (i_term) instance%term(i_term(j))%active = .true. end do end if if (allocated (i_term)) deallocate (i_term) end do end associate instance%evaluation_status = STAT_ACTIVATED end subroutine process_instance_activate @ %def process_instance_activate @ <>= procedure :: find_same_kinematics => process_instance_find_same_kinematics <>= subroutine process_instance_find_same_kinematics (instance) class(process_instance_t), intent(inout) :: instance integer :: i_term1, i_term2, k, n_same do i_term1 = 1, size (instance%term) if (.not. allocated (instance%term(i_term1)%same_kinematics)) then n_same = 1 !!! Index group includes the index of its term_instance do i_term2 = 1, size (instance%term) if (i_term1 == i_term2) cycle if (compare_md5s (i_term1, i_term2)) n_same = n_same + 1 end do allocate (instance%term(i_term1)%same_kinematics (n_same)) associate (same_kinematics1 => instance%term(i_term1)%same_kinematics) same_kinematics1 = 0 k = 1 do i_term2 = 1, size (instance%term) if (compare_md5s (i_term1, i_term2)) then same_kinematics1(k) = i_term2 k = k + 1 end if end do do k = 1, size (same_kinematics1) if (same_kinematics1(k) == i_term1) cycle i_term2 = same_kinematics1(k) allocate (instance%term(i_term2)%same_kinematics (n_same)) instance%term(i_term2)%same_kinematics = same_kinematics1 end do end associate end if end do contains function compare_md5s (i, j) result (same) logical :: same integer, intent(in) :: i, j character(32) :: md5sum_1, md5sum_2 integer :: mode_1, mode_2 mode_1 = 0; mode_2 = 0 select type (phs => instance%term(i)%k_term%phs%config) type is (phs_fks_config_t) md5sum_1 = phs%md5sum_born_config mode_1 = phs%mode class default md5sum_1 = phs%md5sum_phs_config end select select type (phs => instance%term(j)%k_term%phs%config) type is (phs_fks_config_t) md5sum_2 = phs%md5sum_born_config mode_2 = phs%mode class default md5sum_2 = phs%md5sum_phs_config end select same = (md5sum_1 == md5sum_2) .and. (mode_1 == mode_2) end function compare_md5s end subroutine process_instance_find_same_kinematics @ %def process_instance_find_same_kinematics @ <>= procedure :: transfer_same_kinematics => process_instance_transfer_same_kinematics <>= subroutine process_instance_transfer_same_kinematics (instance, i_term) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term integer :: i, i_term_same associate (same_kinematics => instance%term(i_term)%same_kinematics) do i = 1, size (same_kinematics) i_term_same = same_kinematics(i) instance%term(i_term_same)%p_seed = instance%term(i_term)%p_seed associate (phs => instance%term(i_term_same)%k_term%phs) call phs%set_lorentz_transformation & (instance%term(i_term)%k_term%phs%get_lorentz_transformation ()) select type (phs) type is (phs_fks_t) call phs%set_momenta (instance%term(i_term_same)%p_seed) if (i_term_same /= i_term) then call phs%set_reference_frames (.false.) end if end select end associate instance%term(i_term_same)%k_term%new_seed = .false. end do end associate end subroutine process_instance_transfer_same_kinematics @ %def process_instance_transfer_same_kinematics @ <>= procedure :: redo_sf_chains => process_instance_redo_sf_chains <>= subroutine process_instance_redo_sf_chains (instance, i_term, phs_channel) class(process_instance_t), intent(inout) :: instance integer, intent(in), dimension(:) :: i_term integer, intent(in) :: phs_channel integer :: i do i = 1, size (i_term) call instance%term(i_term(i))%redo_sf_chain & (instance%mci_work(instance%i_mci), phs_channel) end do end subroutine process_instance_redo_sf_chains @ %def process_instance_redo_sf_chains @ Integrate the process, using a previously initialized process instance. We select one of the available MCI integrators by its index [[i_mci]] and thus integrate over (structure functions and) phase space for the associated (group of) process component(s). <>= procedure :: integrate => process_instance_integrate <>= subroutine process_instance_integrate (instance, i_mci, n_it, n_calls, & adapt_grids, adapt_weights, final, pacify) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_mci integer, intent(in) :: n_it integer, intent(in) :: n_calls logical, intent(in), optional :: adapt_grids logical, intent(in), optional :: adapt_weights logical, intent(in), optional :: final, pacify integer :: nlo_type, i_mci_work nlo_type = instance%process%get_component_nlo_type (i_mci) i_mci_work = instance%process%get_i_mci_work (i_mci) call instance%choose_mci (i_mci_work) call instance%reset_counter () associate (mci_work => instance%mci_work(i_mci_work), & process => instance%process) call process%integrate (i_mci_work, mci_work, & instance, n_it, n_calls, adapt_grids, adapt_weights, & final, pacify, nlo_type = nlo_type) call process%set_counter_mci_entry (i_mci_work, instance%get_counter ()) end associate end subroutine process_instance_integrate @ %def process_instance_integrate @ Subroutine of the initialization above: initialize the beam and structure-function chain template. We establish pointers to the configuration data, so [[beam_config]] must have a [[target]] attribute. The resulting chain is not used directly for calculation. It will acquire instances which are stored in the process-component instance objects. <>= procedure :: setup_sf_chain => process_instance_setup_sf_chain <>= subroutine process_instance_setup_sf_chain (instance, config) class(process_instance_t), intent(inout) :: instance type(process_beam_config_t), intent(in), target :: config integer :: n_strfun n_strfun = config%n_strfun if (n_strfun /= 0) then call instance%sf_chain%init (config%data, config%sf) else call instance%sf_chain%init (config%data) end if if (config%sf_trace) then call instance%sf_chain%setup_tracing (config%sf_trace_file) end if end subroutine process_instance_setup_sf_chain @ %def process_instance_setup_sf_chain @ This initialization routine should be called only for process instances which we intend as a source for physical events. It initializes the evaluators in the parton states of the terms. They describe the (semi-)exclusive transition matrix and the distribution of color flow for the partonic process, convoluted with the beam and structure-function chain. If the model is not provided explicitly, we may use the model instance that belongs to the process. However, an explicit model allows us to override particle settings. <>= procedure :: setup_event_data => process_instance_setup_event_data <>= subroutine process_instance_setup_event_data (instance, model, i_core) class(process_instance_t), intent(inout), target :: instance class(model_data_t), intent(in), optional, target :: model integer, intent(in), optional :: i_core class(model_data_t), pointer :: current_model integer :: i class(prc_core_t), pointer :: core => null () if (present (model)) then current_model => model else current_model => instance%process%get_model_ptr () end if do i = 1, size (instance%term) associate (term => instance%term(i)) if (associated (term%config)) then core => instance%process%get_core_term (i) call term%setup_event_data (core, current_model) end if end associate end do core => null () end subroutine process_instance_setup_event_data @ %def process_instance_setup_event_data @ Choose a MC parameter set and the corresponding integrator. The choice persists beyond calls of the [[reset]] method above. This method is automatically called here. <>= procedure :: choose_mci => process_instance_choose_mci <>= subroutine process_instance_choose_mci (instance, i_mci) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_mci instance%i_mci = i_mci call instance%reset () end subroutine process_instance_choose_mci @ %def process_instance_choose_mci @ Explicitly set a MC parameter set. Works only if we are in initial state. We assume that the length of the parameter set is correct. After setting the parameters, activate the components and terms that correspond to the chosen MC parameter set. The [[warmup_flag]] is used when a dummy phase-space point is computed for the warmup of e.g. OpenLoops helicities. The setting of the the [[evaluation_status]] has to be avoided then. <>= procedure :: set_mcpar => process_instance_set_mcpar <>= subroutine process_instance_set_mcpar (instance, x, warmup_flag) class(process_instance_t), intent(inout) :: instance real(default), dimension(:), intent(in) :: x logical, intent(in), optional :: warmup_flag logical :: activate activate = .true.; if (present (warmup_flag)) activate = .not. warmup_flag if (instance%evaluation_status == STAT_INITIAL) then associate (mci_work => instance%mci_work(instance%i_mci)) call mci_work%set (x) end associate if (activate) call instance%activate () end if end subroutine process_instance_set_mcpar @ %def process_instance_set_mcpar @ Receive the beam momentum/momenta from a source interaction. This applies to a cascade decay process instance, where the `beam' momentum varies event by event. The master beam momentum array is contained in the main structure function chain subobject [[sf_chain]]. The sf-chain instance that reside in the components will take their beam momenta from there. The procedure transforms the instance status into [[STAT_BEAM_MOMENTA]]. For process instance with fixed beam, this intermediate status is skipped. <>= procedure :: receive_beam_momenta => process_instance_receive_beam_momenta <>= subroutine process_instance_receive_beam_momenta (instance) class(process_instance_t), intent(inout) :: instance if (instance%evaluation_status >= STAT_INITIAL) then call instance%sf_chain%receive_beam_momenta () instance%evaluation_status = STAT_BEAM_MOMENTA end if end subroutine process_instance_receive_beam_momenta @ %def process_instance_receive_beam_momenta @ Set the beam momentum/momenta explicitly. Otherwise, analogous to the previous procedure. <>= procedure :: set_beam_momenta => process_instance_set_beam_momenta <>= subroutine process_instance_set_beam_momenta (instance, p) class(process_instance_t), intent(inout) :: instance type(vector4_t), dimension(:), intent(in) :: p if (instance%evaluation_status >= STAT_INITIAL) then call instance%sf_chain%set_beam_momenta (p) instance%evaluation_status = STAT_BEAM_MOMENTA end if end subroutine process_instance_set_beam_momenta @ %def process_instance_set_beam_momenta @ Recover the initial beam momenta (those in the [[sf_chain]] component), given a valid (recovered) [[sf_chain_instance]] in one of the active components. We need to do this only if the lab frame is not the c.m.\ frame, otherwise those beams would be fixed anyway. <>= procedure :: recover_beam_momenta => process_instance_recover_beam_momenta <>= subroutine process_instance_recover_beam_momenta (instance, i_term) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term if (.not. instance%process%lab_is_cm ()) then if (instance%evaluation_status >= STAT_EFF_KINEMATICS) then call instance%term(i_term)%return_beam_momenta () end if end if end subroutine process_instance_recover_beam_momenta @ %def process_instance_recover_beam_momenta @ Explicitly choose MC integration channel. We assume here that the channel count is identical for all active components. <>= procedure :: select_channel => process_instance_select_channel <>= subroutine process_instance_select_channel (instance, channel) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: channel instance%selected_channel = channel end subroutine process_instance_select_channel @ %def process_instance_select_channel @ First step of process evaluation: set up seed kinematics. That is, for each active process component, compute a momentum array from the MC input parameters. If [[skip_term]] is set, we skip the component that accesses this term. We can assume that the associated data have already been recovered, and we are just computing the rest. <>= procedure :: compute_seed_kinematics => & process_instance_compute_seed_kinematics <>= subroutine process_instance_compute_seed_kinematics & (instance, recover, skip_term) class(process_instance_t), intent(inout) :: instance logical, intent(in), optional :: recover integer, intent(in), optional :: skip_term integer :: channel, skip_component, i, j logical :: success integer, dimension(:), allocatable :: i_term channel = instance%selected_channel if (channel == 0) then call msg_bug ("Compute seed kinematics: undefined integration channel") end if if (present (skip_term)) then skip_component = instance%term(skip_term)%config%i_component else skip_component = 0 end if if (present (recover)) then if (recover) return end if if (instance%evaluation_status >= STAT_ACTIVATED) then success = .true. do i = 1, instance%process%get_n_components () if (i == skip_component) cycle if (instance%process%component_is_selected (i)) then allocate (i_term (size (instance%process%get_component_i_terms (i)))) i_term = instance%process%get_component_i_terms (i) do j = 1, size (i_term) if (instance%term(i_term(j))%k_term%new_seed) then call instance%term(i_term(j))%compute_seed_kinematics & (instance%mci_work(instance%i_mci), channel, success) call instance%transfer_same_kinematics (i_term(j)) end if if (.not. success) exit call instance%term(i_term(j))%evaluate_projections () call instance%term(i_term(j))%evaluate_radiation_kinematics & (instance%mci_work(instance%i_mci)%get_x_process ()) call instance%term(i_term(j))%generate_fsr_in () call instance%term(i_term(j))%compute_xi_ref_momenta () end do end if if (allocated (i_term)) deallocate (i_term) end do if (success) then instance%evaluation_status = STAT_SEED_KINEMATICS else instance%evaluation_status = STAT_FAILED_KINEMATICS end if end if associate (mci_work => instance%mci_work(instance%i_mci)) select type (pcm => instance%pcm) class is (pcm_instance_nlo_t) call pcm%set_x_rad (mci_work%get_x_process ()) end select end associate end subroutine process_instance_compute_seed_kinematics @ %def process_instance_compute_seed_kinematics @ <>= procedure :: get_x_process => process_instance_get_x_process <>= pure function process_instance_get_x_process (instance) result (x) real(default), dimension(:), allocatable :: x class(process_instance_t), intent(in) :: instance allocate (x(size (instance%mci_work(instance%i_mci)%get_x_process ()))) x = instance%mci_work(instance%i_mci)%get_x_process () end function process_instance_get_x_process @ %def process_instance_get_x_process @ <>= procedure :: get_active_component_type => process_instance_get_active_component_type <>= pure function process_instance_get_active_component_type (instance) & result (nlo_type) integer :: nlo_type class(process_instance_t), intent(in) :: instance nlo_type = instance%process%get_component_nlo_type (instance%i_mci) end function process_instance_get_active_component_type @ %def process_instance_get_active_component_type @ Inverse: recover missing parts of the kinematics from the momentum configuration, which we know for a single term and component. Given a channel, reconstruct the MC parameter set. <>= procedure :: recover_mcpar => process_instance_recover_mcpar <>= subroutine process_instance_recover_mcpar (instance, i_term) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term integer :: channel, i if (instance%evaluation_status >= STAT_EFF_KINEMATICS) then channel = instance%selected_channel if (channel == 0) then call msg_bug ("Recover MC parameters: undefined integration channel") end if call instance%term(i_term)%recover_mcpar & (instance%mci_work(instance%i_mci), channel) if (instance%term(i_term)%nlo_type == NLO_REAL) then do i = 1, size (instance%term) if (i /= i_term .and. instance%term(i)%nlo_type == NLO_REAL) then if (instance%term(i)%active) then call instance%term(i)%recover_mcpar & (instance%mci_work(instance%i_mci), channel) end if end if end do end if end if end subroutine process_instance_recover_mcpar @ %def process_instance_recover_mcpar @ This is part of [[recover_mcpar]], extracted for the case when there is no phase space and parameters to recover, but we still need the structure function kinematics for evaluation. <>= procedure :: recover_sfchain => process_instance_recover_sfchain <>= subroutine process_instance_recover_sfchain (instance, i_term) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term integer :: channel if (instance%evaluation_status >= STAT_EFF_KINEMATICS) then channel = instance%selected_channel if (channel == 0) then call msg_bug ("Recover sfchain: undefined integration channel") end if call instance%term(i_term)%recover_sfchain (channel) end if end subroutine process_instance_recover_sfchain @ %def process_instance_recover_sfchain @ Second step of process evaluation: compute all momenta, for all active components, from the seed kinematics. <>= procedure :: compute_hard_kinematics => & process_instance_compute_hard_kinematics <>= subroutine process_instance_compute_hard_kinematics (instance, recover, skip_term) class(process_instance_t), intent(inout) :: instance integer, intent(in), optional :: skip_term logical, intent(in), optional :: recover integer :: i logical :: success success = .true. if (instance%evaluation_status >= STAT_SEED_KINEMATICS) then do i = 1, size (instance%term) if (instance%term(i)%active) then call instance%term(i)%compute_hard_kinematics & (recover, skip_term, success) if (.not. success) exit !!! Ren scale is zero when this is commented out! Understand! if (instance%term(i)%nlo_type == NLO_REAL) & call instance%term(i)%redo_sf_chain & (instance%mci_work(instance%i_mci), & instance%selected_channel) end if end do if (success) then instance%evaluation_status = STAT_HARD_KINEMATICS else instance%evaluation_status = STAT_FAILED_KINEMATICS end if end if end subroutine process_instance_compute_hard_kinematics @ %def process_instance_setup_compute_hard_kinematics @ Inverse: recover seed kinematics. We know the beam momentum configuration and the outgoing momenta of the effective interaction, for one specific term. <>= procedure :: recover_seed_kinematics => & process_instance_recover_seed_kinematics <>= subroutine process_instance_recover_seed_kinematics (instance, i_term) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term type(vector4_t), dimension(:), allocatable :: p_seed_ref integer :: i if (instance%evaluation_status >= STAT_EFF_KINEMATICS) then call instance%term(i_term)%recover_seed_kinematics () if (instance%term(i_term)%nlo_type == NLO_REAL) then allocate (p_seed_ref (instance%term(i_term)%isolated%int_eff%get_n_out ())) p_seed_ref = instance%term(i_term)%isolated%int_eff%get_momenta & (outgoing = .true.) do i = 1, size (instance%term) if (i /= i_term .and. instance%term(i)%nlo_type == NLO_REAL) then if (instance%term(i)%active) then call instance%term(i)%recover_seed_kinematics (p_seed_ref) end if end if end do end if end if end subroutine process_instance_recover_seed_kinematics @ %def process_instance_recover_seed_kinematics @ Third step of process evaluation: compute the effective momentum configurations, for all active terms, from the hard kinematics. <>= procedure :: compute_eff_kinematics => & process_instance_compute_eff_kinematics <>= subroutine process_instance_compute_eff_kinematics (instance, skip_term) class(process_instance_t), intent(inout) :: instance integer, intent(in), optional :: skip_term integer :: i if (instance%evaluation_status >= STAT_HARD_KINEMATICS) then do i = 1, size (instance%term) if (present (skip_term)) then if (i == skip_term) cycle end if if (instance%term(i)%active) then call instance%term(i)%compute_eff_kinematics () end if end do instance%evaluation_status = STAT_EFF_KINEMATICS end if end subroutine process_instance_compute_eff_kinematics @ %def process_instance_setup_compute_eff_kinematics @ Inverse: recover the hard kinematics from effective kinematics for one term, then compute effective kinematics for the other terms. <>= procedure :: recover_hard_kinematics => & process_instance_recover_hard_kinematics <>= subroutine process_instance_recover_hard_kinematics (instance, i_term) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term integer :: i if (instance%evaluation_status >= STAT_EFF_KINEMATICS) then call instance%term(i_term)%recover_hard_kinematics () do i = 1, size (instance%term) if (i /= i_term) then if (instance%term(i)%active) then call instance%term(i)%compute_eff_kinematics () end if end if end do instance%evaluation_status = STAT_EFF_KINEMATICS end if end subroutine process_instance_recover_hard_kinematics @ %def recover_hard_kinematics @ Fourth step of process evaluation: check cuts for all terms. Where successful, compute any scales and weights. Otherwise, deactive the term. If any of the terms has passed, set the state to [[STAT_PASSED_CUTS]]. The argument [[scale_forced]], if present, will override the scale calculation in the term expressions. <>= procedure :: evaluate_expressions => & process_instance_evaluate_expressions <>= subroutine process_instance_evaluate_expressions (instance, scale_forced) class(process_instance_t), intent(inout) :: instance real(default), intent(in), allocatable, optional :: scale_forced integer :: i logical :: passed_real if (instance%evaluation_status >= STAT_EFF_KINEMATICS) then do i = 1, size (instance%term) if (instance%term(i)%active) then call instance%term(i)%evaluate_expressions (scale_forced) end if end do call evaluate_real_scales_and_cuts () call set_ellis_sexton_scale () if (.not. passed_real) then instance%evaluation_status = STAT_FAILED_CUTS else if (any (instance%term%passed)) then instance%evaluation_status = STAT_PASSED_CUTS else instance%evaluation_status = STAT_FAILED_CUTS end if end if end if contains subroutine evaluate_real_scales_and_cuts () integer :: i passed_real = .true. select type (config => instance%pcm%config) type is (pcm_nlo_t) do i = 1, size (instance%term) if (instance%term(i)%active .and. instance%term(i)%nlo_type == NLO_REAL) then if (config%settings%cut_all_real_sqmes) & passed_real = passed_real .and. instance%term(i)%passed if (config%settings%use_born_scale) & call replace_scales (instance%term(i)) end if end do end select end subroutine evaluate_real_scales_and_cuts subroutine replace_scales (this_term) type(term_instance_t), intent(inout) :: this_term integer :: i_sub i_sub = this_term%config%i_sub if (this_term%config%i_term_global /= i_sub .and. i_sub > 0) then this_term%ren_scale = instance%term(i_sub)%ren_scale this_term%fac_scale = instance%term(i_sub)%fac_scale end if end subroutine replace_scales subroutine set_ellis_sexton_scale () real(default) :: es_scale type(var_list_t), pointer :: var_list integer :: i var_list => instance%process%get_var_list_ptr () es_scale = var_list%get_rval (var_str ("ellis_sexton_scale")) do i = 1, size (instance%term) if (instance%term(i)%active .and. instance%term(i)%nlo_type == NLO_VIRTUAL) then if (es_scale < zero) then instance%term(i)%es_scale = instance%term(i)%ren_scale else instance%term(i)%es_scale = es_scale end if end if end do end subroutine set_ellis_sexton_scale end subroutine process_instance_evaluate_expressions @ %def process_instance_evaluate_expressions @ Fifth step of process evaluation: fill the parameters for the non-selected ,channels, that have not been used for seeding. We should do this after evaluating cuts, since we may save some expensive calculations if the phase space point fails the cuts. If [[skip_term]] is set, we skip the component that accesses this term. We can assume that the associated data have already been recovered, and we are just computing the rest. <>= procedure :: compute_other_channels => & process_instance_compute_other_channels <>= subroutine process_instance_compute_other_channels (instance, skip_term) class(process_instance_t), intent(inout) :: instance integer, intent(in), optional :: skip_term integer :: channel, skip_component, i, j integer, dimension(:), allocatable :: i_term channel = instance%selected_channel if (channel == 0) then call msg_bug ("Compute other channels: undefined integration channel") end if if (present (skip_term)) then skip_component = instance%term(skip_term)%config%i_component else skip_component = 0 end if if (instance%evaluation_status >= STAT_PASSED_CUTS) then do i = 1, instance%process%get_n_components () if (i == skip_component) cycle if (instance%process%component_is_selected (i)) then allocate (i_term (size (instance%process%get_component_i_terms (i)))) i_term = instance%process%get_component_i_terms (i) do j = 1, size (i_term) call instance%term(i_term(j))%compute_other_channels & (instance%mci_work(instance%i_mci), channel) end do end if if (allocated (i_term)) deallocate (i_term) end do end if end subroutine process_instance_compute_other_channels @ %def process_instance_compute_other_channels @ If not done otherwise, we flag the kinematics as new for the core state, such that the routine below will actually compute the matrix element and not just look it up. <>= procedure :: reset_core_kinematics => process_instance_reset_core_kinematics <>= subroutine process_instance_reset_core_kinematics (instance) class(process_instance_t), intent(inout) :: instance integer :: i if (instance%evaluation_status >= STAT_PASSED_CUTS) then do i = 1, size (instance%term) associate (term => instance%term(i)) if (term%active .and. term%passed) then if (allocated (term%core_state)) & call term%core_state%reset_new_kinematics () end if end associate end do end if end subroutine process_instance_reset_core_kinematics @ %def process_instance_reset_core_kinematics @ Sixth step of process evaluation: evaluate the matrix elements, and compute the trace (summed over quantum numbers) for all terms. Finally, sum up the terms, iterating over all active process components. <>= procedure :: evaluate_trace => process_instance_evaluate_trace <>= subroutine process_instance_evaluate_trace (instance, recover) class(process_instance_t), intent(inout) :: instance logical, intent(in), optional :: recover class(prc_core_t), pointer :: core => null () integer :: i, i_real_fin, i_core real(default) :: alpha_s, alpha_qed class(prc_core_t), pointer :: core_sub => null () class(model_data_t), pointer :: model => null () logical :: has_pdfs if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "process_instance_evaluate_trace") has_pdfs = instance%process%pcm_contains_pdfs () instance%sqme = zero call instance%reset_matrix_elements () if (instance%evaluation_status >= STAT_PASSED_CUTS) then do i = 1, size (instance%term) associate (term => instance%term(i)) if (term%active .and. term%passed) then core => instance%process%get_core_term (i) select type (pcm => instance%process%get_pcm_ptr ()) class is (pcm_nlo_t) i_core = pcm%get_i_core (pcm%i_sub) core_sub => instance%process%get_core_ptr (i_core) end select call term%evaluate_interaction (core) call term%evaluate_trace () i_real_fin = instance%process%get_associated_real_fin (1) if (instance%process%uses_real_partition ()) & call term%apply_real_partition (instance%process) if (term%config%i_component /= i_real_fin) then if ((term%nlo_type == NLO_REAL .and. term%k_term%emitter < 0) & .or. term%nlo_type == NLO_MISMATCH & .or. term%nlo_type == NLO_DGLAP) & call term%set_born_sqmes (core) if (term%is_subtraction () .or. & term%nlo_type == NLO_DGLAP) & call term%set_sf_factors (has_pdfs) if (term%nlo_type > BORN) then if (.not. (term%nlo_type == NLO_REAL .and. & term%k_term%emitter >= 0)) then select type (config => term%pcm_instance%config) type is (pcm_nlo_t) if (char (config%settings%nlo_correction_type) == "QCD" .or. & char (config%settings%nlo_correction_type) == "Full") & call term%evaluate_color_correlations (core_sub) if (char (config%settings%nlo_correction_type) == "EW" .or. & char (config%settings%nlo_correction_type) == "Full") & call term%evaluate_charge_correlations (core_sub) end select end if if (term%is_subtraction ()) then call term%evaluate_spin_correlations (core_sub) end if end if alpha_s = core%get_alpha_s (term%core_state) alpha_qed = core%get_alpha_qed () if (term%nlo_type > BORN) then select type (config => term%pcm_instance%config) type is (pcm_nlo_t) if (alpha_qed == -1 .and. (& char (config%settings%nlo_correction_type) == "EW" .or. & char (config%settings%nlo_correction_type) == "Full")) then call msg_bug("Attempting to compute EW corrections with alpha_qed = -1") end if end select end if if (present (recover)) then if (recover) return end if select case (term%nlo_type) case (NLO_REAL) call term%apply_fks (alpha_s, alpha_qed) case (NLO_VIRTUAL) call term%evaluate_sqme_virt (alpha_s, alpha_qed) case (NLO_MISMATCH) call term%evaluate_sqme_mismatch (alpha_s) case (NLO_DGLAP) call term%evaluate_sqme_dglap (alpha_s, alpha_qed) end select end if end if core_sub => null () instance%sqme = instance%sqme + real (sum (& term%connected%trace%get_matrix_element () * & term%weight)) end associate end do core => null () if (instance%pcm%is_valid ()) then instance%evaluation_status = STAT_EVALUATED_TRACE else instance%evaluation_status = STAT_FAILED_KINEMATICS end if else !!! Failed kinematics or failed cuts: set sqme to zero instance%sqme = zero end if end subroutine process_instance_evaluate_trace @ %def process_instance_evaluate_trace <>= procedure :: set_born_sqmes => term_instance_set_born_sqmes <>= subroutine term_instance_set_born_sqmes (term, core) class(term_instance_t), intent(inout) :: term class(prc_core_t), intent(in) :: core integer :: i_flv, ii_flv real(default) :: sqme select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) do i_flv = 1, term%connected%trace%get_qn_index_n_flv () ii_flv = term%connected%trace%get_qn_index (i_flv, i_sub = 0) sqme = real (term%connected%trace%get_matrix_element (ii_flv)) select case (term%nlo_type) case (NLO_REAL) pcm_instance%real_sub%sqme_born(i_flv) = sqme case (NLO_MISMATCH) pcm_instance%soft_mismatch%sqme_born(i_flv) = sqme case (NLO_DGLAP) pcm_instance%dglap_remnant%sqme_born(i_flv) = sqme end select end do end select end subroutine term_instance_set_born_sqmes @ %def term_instance_set_born_sqmes @ Calculates and then saves the ratio of the value of the (rescaled) real structure function chain of each ISR alpha region over the value of the corresponding underlying born flavor structure. In the case of emitter 0 we also need the rescaled ratio for emitter 1 and 2 in that region for the (soft-)collinear limits. Altough this procedure is implying functionality for general structure functions, it should be reviewed for anything else besides PDFs, as there might be complications in the details. The general idea of getting the ratio in this way should hold up in these cases as well, however. <>= procedure :: set_sf_factors => term_instance_set_sf_factors <>= subroutine term_instance_set_sf_factors (term, has_pdfs) class(term_instance_t), intent(inout) :: term logical, intent(in) :: has_pdfs type(interaction_t), pointer :: sf_chain_int real(default) :: factor_born, factor_real integer :: n_in, alr, em integer :: i_born, i_real select type (pcm_instance => term%pcm_instance) type is (pcm_instance_nlo_t) if (.not. has_pdfs) then pcm_instance%real_sub%sf_factors = one return end if select type (config => pcm_instance%config) type is (pcm_nlo_t) sf_chain_int => term%k_term%sf_chain%get_out_int_ptr () associate (reg_data => config%region_data) n_in = reg_data%get_n_in () do alr = 1, reg_data%n_regions em = reg_data%regions(alr)%emitter if (em <= n_in) then i_born = reg_data%regions(alr)%uborn_index i_real = reg_data%regions(alr)%real_index factor_born = sf_chain_int%get_matrix_element & (sf_chain_int%get_sf_qn_index_born (i_born, i_sub = 0)) factor_real = sf_chain_int%get_matrix_element & (sf_chain_int%get_sf_qn_index_real (i_real, i_sub = em)) call set_factor (pcm_instance, alr, em, factor_born, factor_real) if (em == 0) then do em = 1, 2 factor_real = sf_chain_int%get_matrix_element & (sf_chain_int%get_sf_qn_index_real (i_real, i_sub = em)) call set_factor (pcm_instance, alr, em, factor_born, factor_real) end do end if end if end do end associate end select end select contains subroutine set_factor (pcm_instance, alr, em, factor_born, factor_real) type(pcm_instance_nlo_t), intent(inout), target :: pcm_instance integer, intent(in) :: alr, em real(default), intent(in) :: factor_born, factor_real real(default) :: factor if (any (vanishes ([factor_real, factor_born], tiny(1._default), tiny(1._default)))) then factor = zero else factor = factor_real / factor_born end if select case (term%nlo_type) case (NLO_REAL) pcm_instance%real_sub%sf_factors(alr, em) = factor case (NLO_DGLAP) pcm_instance%dglap_remnant%sf_factors(alr, em) = factor end select end subroutine end subroutine term_instance_set_sf_factors @ %def term_instance_set_sf_factors @ <>= procedure :: apply_real_partition => process_instance_apply_real_partition <>= subroutine process_instance_apply_real_partition (instance) class(process_instance_t), intent(inout) :: instance integer :: i_component, i_term integer, dimension(:), allocatable :: i_terms associate (process => instance%process) i_component = process%get_first_real_component () if (process%component_is_selected (i_component) .and. & process%get_component_nlo_type (i_component) == NLO_REAL) then allocate (i_terms (size (process%get_component_i_terms (i_component)))) i_terms = process%get_component_i_terms (i_component) do i_term = 1, size (i_terms) call instance%term(i_terms(i_term))%apply_real_partition (process) end do end if if (allocated (i_terms)) deallocate (i_terms) end associate end subroutine process_instance_apply_real_partition @ %def process_instance_apply_real_partition @ <>= procedure :: set_i_mci_to_real_component => process_instance_set_i_mci_to_real_component <>= subroutine process_instance_set_i_mci_to_real_component (instance) class(process_instance_t), intent(inout) :: instance integer :: i_mci, i_component type(process_component_t), pointer :: component => null () select type (pcm_instance => instance%pcm) type is (pcm_instance_nlo_t) if (allocated (pcm_instance%i_mci_to_real_component)) then call msg_warning ("i_mci_to_real_component already allocated - replace it") deallocate (pcm_instance%i_mci_to_real_component) end if allocate (pcm_instance%i_mci_to_real_component (size (instance%mci_work))) do i_mci = 1, size (instance%mci_work) do i_component = 1, instance%process%get_n_components () component => instance%process%get_component_ptr (i_component) if (component%i_mci /= i_mci) cycle select case (component%component_type) case (COMP_MASTER, COMP_REAL) pcm_instance%i_mci_to_real_component (i_mci) = & component%config%get_associated_real () case (COMP_REAL_FIN) pcm_instance%i_mci_to_real_component (i_mci) = & component%config%get_associated_real_fin () case (COMP_REAL_SING) pcm_instance%i_mci_to_real_component (i_mci) = & component%config%get_associated_real_sing () end select end do end do component => null () end select end subroutine process_instance_set_i_mci_to_real_component @ %def process_instance_set_i_mci_to_real_component @ Final step of process evaluation: evaluate the matrix elements, and compute the trace (summed over quantum numbers) for all terms. Finally, sum up the terms, iterating over all active process components. If [[weight]] is provided, we already know the kinematical event weight (the MCI weight which depends on the kinematics sampling algorithm, but not on the matrix element), so we do not need to take it from the MCI record. <>= procedure :: evaluate_event_data => process_instance_evaluate_event_data <>= subroutine process_instance_evaluate_event_data (instance, weight) class(process_instance_t), intent(inout) :: instance real(default), intent(in), optional :: weight integer :: i if (instance%evaluation_status >= STAT_EVALUATED_TRACE) then do i = 1, size (instance%term) associate (term => instance%term(i)) if (term%active) then call term%evaluate_event_data () end if end associate end do if (present (weight)) then instance%weight = weight else instance%weight = & instance%mci_work(instance%i_mci)%mci%get_event_weight () instance%excess = & instance%mci_work(instance%i_mci)%mci%get_event_excess () end if instance%n_dropped = & instance%mci_work(instance%i_mci)%mci%get_n_event_dropped () instance%evaluation_status = STAT_EVENT_COMPLETE else !!! failed kinematics etc.: set weight to zero instance%weight = zero !!! Maybe we want to process and keep the event nevertheless if (instance%keep_failed_events ()) then do i = 1, size (instance%term) associate (term => instance%term(i)) if (term%active) then call term%evaluate_event_data () end if end associate end do ! do i = 1, size (instance%term) ! instance%term(i)%fac_scale = zero ! end do instance%evaluation_status = STAT_EVENT_COMPLETE end if end if end subroutine process_instance_evaluate_event_data @ %def process_instance_evaluate_event_data @ Computes the real-emission matrix element for externally supplied momenta for the term instance with index [[i_term]] and a phase space point set with index [[i_phs]]. In addition, for the real emission, each term instance corresponds to one emitter. Also, e.g. for Powheg, there is the possibility to supply an external $\alpha_s$. <>= procedure :: compute_sqme_rad => process_instance_compute_sqme_rad <>= subroutine process_instance_compute_sqme_rad & (instance, i_term, i_phs, is_subtraction, alpha_s_external) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term, i_phs logical, intent(in) :: is_subtraction real(default), intent(in), optional :: alpha_s_external class(prc_core_t), pointer :: core integer :: i_real_fin logical :: has_pdfs has_pdfs = instance%process%pcm_contains_pdfs () if (debug_on) call msg_debug2 (D_PROCESS_INTEGRATION, "process_instance_compute_sqme_rad") select type (pcm => instance%pcm) type is (pcm_instance_nlo_t) associate (term => instance%term(i_term)) core => instance%process%get_core_term (i_term) if (is_subtraction) then call pcm%set_subtraction_event () else call pcm%set_radiation_event () end if call term%int_hard%set_momenta (pcm%get_momenta & (i_phs = i_phs, born_phsp = is_subtraction)) if (allocated (term%core_state)) & call term%core_state%reset_new_kinematics () if (present (alpha_s_external)) & call term%set_alpha_qcd_forced (alpha_s_external) call term%compute_eff_kinematics () call term%evaluate_expressions () call term%evaluate_interaction (core) call term%evaluate_trace () if (term%is_subtraction ()) then call term%set_sf_factors (has_pdfs) select type (config => term%pcm_instance%config) type is (pcm_nlo_t) if (char (config%settings%nlo_correction_type) == "QCD" .or. & char (config%settings%nlo_correction_type) == "Full") & call term%evaluate_color_correlations (core) if (char (config%settings%nlo_correction_type) == "EW" .or. & char (config%settings%nlo_correction_type) == "Full") & call term%evaluate_charge_correlations (core) end select call term%evaluate_spin_correlations (core) end if i_real_fin = instance%process%get_associated_real_fin (1) if (term%config%i_component /= i_real_fin) & call term%apply_fks (core%get_alpha_s (term%core_state), & core%get_alpha_qed ()) if (instance%process%uses_real_partition ()) & call instance%apply_real_partition () end associate end select core => null () end subroutine process_instance_compute_sqme_rad @ %def process_instance_compute_sqme_rad @ For unweighted event generation, we should reset the reported event weight to unity (signed) or zero. The latter case is appropriate for an event which failed for whatever reason. <>= procedure :: normalize_weight => process_instance_normalize_weight <>= subroutine process_instance_normalize_weight (instance) class(process_instance_t), intent(inout) :: instance if (.not. vanishes (instance%weight)) then instance%weight = sign (1._default, instance%weight) end if end subroutine process_instance_normalize_weight @ %def process_instance_normalize_weight @ This is a convenience routine that performs the computations of the steps 1 to 5 in a single step. The arguments are the input for [[set_mcpar]]. After this, the evaluation status should be either [[STAT_FAILED_KINEMATICS]], [[STAT_FAILED_CUTS]] or [[STAT_EVALUATED_TRACE]]. Before calling this, we should call [[choose_mci]]. <>= procedure :: evaluate_sqme => process_instance_evaluate_sqme <>= subroutine process_instance_evaluate_sqme (instance, channel, x) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: channel real(default), dimension(:), intent(in) :: x call instance%reset () call instance%set_mcpar (x) call instance%select_channel (channel) call instance%compute_seed_kinematics () call instance%compute_hard_kinematics () call instance%compute_eff_kinematics () call instance%evaluate_expressions () call instance%compute_other_channels () call instance%evaluate_trace () end subroutine process_instance_evaluate_sqme @ %def process_instance_evaluate_sqme @ This is the inverse. Assuming that the final trace evaluator contains a valid momentum configuration, recover kinematics and recalculate the matrix elements and their trace. To be precise, we first recover kinematics for the given term and associated component, then recalculate from that all other terms and active components. The [[channel]] is not really required to obtain the matrix element, but it allows us to reconstruct the exact MC parameter set that corresponds to the given phase space point. Before calling this, we should call [[choose_mci]]. <>= procedure :: recover => process_instance_recover <>= subroutine process_instance_recover & (instance, channel, i_term, update_sqme, recover_phs, scale_forced) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: channel integer, intent(in) :: i_term logical, intent(in) :: update_sqme logical, intent(in) :: recover_phs real(default), intent(in), allocatable, optional :: scale_forced logical :: skip_phs, recover call instance%activate () instance%evaluation_status = STAT_EFF_KINEMATICS call instance%recover_hard_kinematics (i_term) call instance%recover_seed_kinematics (i_term) call instance%select_channel (channel) recover = instance%pcm%config%is_nlo () if (recover_phs) then call instance%recover_mcpar (i_term) call instance%recover_beam_momenta (i_term) call instance%compute_seed_kinematics & (recover = recover, skip_term = i_term) call instance%compute_hard_kinematics & (recover = recover, skip_term = i_term) call instance%compute_eff_kinematics (i_term) call instance%compute_other_channels (i_term) else call instance%recover_sfchain (i_term) end if call instance%evaluate_expressions (scale_forced) if (update_sqme) then call instance%reset_core_kinematics () call instance%evaluate_trace (recover) end if end subroutine process_instance_recover @ %def process_instance_recover @ The [[evaluate]] method is required by the [[sampler_t]] base type of which the process instance is an extension. The requirement is that after the process instance is evaluated, the integrand, the selected channel, the $x$ array, and the $f$ Jacobian array are exposed by the [[sampler_t]] object. We allow for the additional [[hook]] to be called, if associated, for outlying object to access information from the current state of the [[sampler]]. <>= procedure :: evaluate => process_instance_evaluate <>= subroutine process_instance_evaluate (sampler, c, x_in, val, x, f) class(process_instance_t), intent(inout) :: sampler integer, intent(in) :: c real(default), dimension(:), intent(in) :: x_in real(default), intent(out) :: val real(default), dimension(:,:), intent(out) :: x real(default), dimension(:), intent(out) :: f call sampler%evaluate_sqme (c, x_in) if (sampler%is_valid ()) then call sampler%fetch (val, x, f) end if call sampler%record_call () call sampler%evaluate_after_hook () end subroutine process_instance_evaluate @ %def process_instance_evaluate @ The phase-space point is valid if the event has valid kinematics and has passed the cuts. <>= procedure :: is_valid => process_instance_is_valid <>= function process_instance_is_valid (sampler) result (valid) class(process_instance_t), intent(in) :: sampler logical :: valid valid = sampler%evaluation_status >= STAT_PASSED_CUTS end function process_instance_is_valid @ %def process_instance_is_valid @ Add a [[process_instance_hook]] object.. <>= procedure :: append_after_hook => process_instance_append_after_hook <>= subroutine process_instance_append_after_hook (sampler, new_hook) class(process_instance_t), intent(inout), target :: sampler class(process_instance_hook_t), intent(inout), target :: new_hook class(process_instance_hook_t), pointer :: last if (associated (new_hook%next)) then call msg_bug ("process_instance_append_after_hook: reuse of SAME hook object is forbidden.") end if if (associated (sampler%hook)) then last => sampler%hook do while (associated (last%next)) last => last%next end do last%next => new_hook else sampler%hook => new_hook end if end subroutine process_instance_append_after_hook @ %def process_instance_append_after_evaluate_hook @ Evaluate the after hook as first in, last out. <>= procedure :: evaluate_after_hook => process_instance_evaluate_after_hook <>= subroutine process_instance_evaluate_after_hook (sampler) class(process_instance_t), intent(in) :: sampler class(process_instance_hook_t), pointer :: current current => sampler%hook do while (associated(current)) call current%evaluate (sampler) current => current%next end do end subroutine process_instance_evaluate_after_hook @ %def process_instance_evaluate_after_hook @ The [[rebuild]] method should rebuild the kinematics section out of the [[x_in]] parameter set. The integrand value [[val]] should not be computed, but is provided as input. <>= procedure :: rebuild => process_instance_rebuild <>= subroutine process_instance_rebuild (sampler, c, x_in, val, x, f) class(process_instance_t), intent(inout) :: sampler integer, intent(in) :: c real(default), dimension(:), intent(in) :: x_in real(default), intent(in) :: val real(default), dimension(:,:), intent(out) :: x real(default), dimension(:), intent(out) :: f call msg_bug ("process_instance_rebuild not implemented yet") x = 0 f = 0 end subroutine process_instance_rebuild @ %def process_instance_rebuild @ This is another method required by the [[sampler_t]] base type: fetch the data that are relevant for the MCI record. <>= procedure :: fetch => process_instance_fetch <>= subroutine process_instance_fetch (sampler, val, x, f) class(process_instance_t), intent(in) :: sampler real(default), intent(out) :: val real(default), dimension(:,:), intent(out) :: x real(default), dimension(:), intent(out) :: f integer, dimension(:), allocatable :: i_terms integer :: i, i_term_base, cc integer :: n_channel val = 0 associate (process => sampler%process) FIND_COMPONENT: do i = 1, process%get_n_components () if (sampler%process%component_is_selected (i)) then allocate (i_terms (size (process%get_component_i_terms (i)))) i_terms = process%get_component_i_terms (i) i_term_base = i_terms(1) associate (k => sampler%term(i_term_base)%k_term) n_channel = k%n_channel do cc = 1, n_channel call k%get_mcpar (cc, x(:,cc)) end do f = k%f val = sampler%sqme * k%phs_factor end associate if (allocated (i_terms)) deallocate (i_terms) exit FIND_COMPONENT end if end do FIND_COMPONENT end associate end subroutine process_instance_fetch @ %def process_instance_fetch @ Initialize and finalize event generation for the specified MCI entry. <>= procedure :: init_simulation => process_instance_init_simulation procedure :: final_simulation => process_instance_final_simulation <>= subroutine process_instance_init_simulation (instance, i_mci, & safety_factor, keep_failed_events) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_mci real(default), intent(in), optional :: safety_factor logical, intent(in), optional :: keep_failed_events call instance%mci_work(i_mci)%init_simulation (safety_factor, keep_failed_events) end subroutine process_instance_init_simulation subroutine process_instance_final_simulation (instance, i_mci) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_mci call instance%mci_work(i_mci)%final_simulation () end subroutine process_instance_final_simulation @ %def process_instance_init_simulation @ %def process_instance_final_simulation @ \subsubsection{Accessing the process instance} Once the seed kinematics is complete, we can retrieve the MC input parameters for all channels, not just the seed channel. Note: We choose the first active component. This makes sense only if the seed kinematics is identical for all active components. <>= procedure :: get_mcpar => process_instance_get_mcpar <>= subroutine process_instance_get_mcpar (instance, channel, x) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: channel real(default), dimension(:), intent(out) :: x integer :: i if (instance%evaluation_status >= STAT_SEED_KINEMATICS) then do i = 1, size (instance%term) if (instance%term(i)%active) then call instance%term(i)%k_term%get_mcpar (channel, x) return end if end do call msg_bug ("Process instance: get_mcpar: no active channels") else call msg_bug ("Process instance: get_mcpar: no seed kinematics") end if end subroutine process_instance_get_mcpar @ %def process_instance_get_mcpar @ Return true if the [[sqme]] value is known. This also implies that the event is kinematically valid and has passed all cuts. <>= procedure :: has_evaluated_trace => process_instance_has_evaluated_trace <>= function process_instance_has_evaluated_trace (instance) result (flag) class(process_instance_t), intent(in) :: instance logical :: flag flag = instance%evaluation_status >= STAT_EVALUATED_TRACE end function process_instance_has_evaluated_trace @ %def process_instance_has_evaluated_trace @ Return true if the event is complete. In particular, the event must be kinematically valid, passed all cuts, and the event data have been computed. <>= procedure :: is_complete_event => process_instance_is_complete_event <>= function process_instance_is_complete_event (instance) result (flag) class(process_instance_t), intent(in) :: instance logical :: flag flag = instance%evaluation_status >= STAT_EVENT_COMPLETE end function process_instance_is_complete_event @ %def process_instance_is_complete_event @ Select the term for the process instance that will provide the basic event record (used in [[evt_trivial_make_particle_set]]). It might be necessary to write out additional events corresponding to other terms (done in [[evt_nlo]]). <>= procedure :: select_i_term => process_instance_select_i_term <>= function process_instance_select_i_term (instance) result (i_term) integer :: i_term class(process_instance_t), intent(in) :: instance integer :: i_mci i_mci = instance%i_mci i_term = instance%process%select_i_term (i_mci) end function process_instance_select_i_term @ %def process_instance_select_i_term @ Return pointer to the master beam interaction. <>= procedure :: get_beam_int_ptr => process_instance_get_beam_int_ptr <>= function process_instance_get_beam_int_ptr (instance) result (ptr) class(process_instance_t), intent(in), target :: instance type(interaction_t), pointer :: ptr ptr => instance%sf_chain%get_beam_int_ptr () end function process_instance_get_beam_int_ptr @ %def process_instance_get_beam_int_ptr @ Return pointers to the matrix and flows interactions, given a term index. <>= procedure :: get_trace_int_ptr => process_instance_get_trace_int_ptr procedure :: get_matrix_int_ptr => process_instance_get_matrix_int_ptr procedure :: get_flows_int_ptr => process_instance_get_flows_int_ptr <>= function process_instance_get_trace_int_ptr (instance, i_term) result (ptr) class(process_instance_t), intent(in), target :: instance integer, intent(in) :: i_term type(interaction_t), pointer :: ptr ptr => instance%term(i_term)%connected%get_trace_int_ptr () end function process_instance_get_trace_int_ptr function process_instance_get_matrix_int_ptr (instance, i_term) result (ptr) class(process_instance_t), intent(in), target :: instance integer, intent(in) :: i_term type(interaction_t), pointer :: ptr ptr => instance%term(i_term)%connected%get_matrix_int_ptr () end function process_instance_get_matrix_int_ptr function process_instance_get_flows_int_ptr (instance, i_term) result (ptr) class(process_instance_t), intent(in), target :: instance integer, intent(in) :: i_term type(interaction_t), pointer :: ptr ptr => instance%term(i_term)%connected%get_flows_int_ptr () end function process_instance_get_flows_int_ptr @ %def process_instance_get_trace_int_ptr @ %def process_instance_get_matrix_int_ptr @ %def process_instance_get_flows_int_ptr @ Return the complete account of flavor combinations in the underlying interaction object, including beams, radiation, and hard interaction. <>= procedure :: get_state_flv => process_instance_get_state_flv <>= function process_instance_get_state_flv (instance, i_term) result (state_flv) class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term type(state_flv_content_t) :: state_flv state_flv = instance%term(i_term)%connected%get_state_flv () end function process_instance_get_state_flv @ %def process_instance_get_state_flv @ Return pointers to the parton states of a selected term. <>= procedure :: get_isolated_state_ptr => & process_instance_get_isolated_state_ptr procedure :: get_connected_state_ptr => & process_instance_get_connected_state_ptr <>= function process_instance_get_isolated_state_ptr (instance, i_term) & result (ptr) class(process_instance_t), intent(in), target :: instance integer, intent(in) :: i_term type(isolated_state_t), pointer :: ptr ptr => instance%term(i_term)%isolated end function process_instance_get_isolated_state_ptr function process_instance_get_connected_state_ptr (instance, i_term) & result (ptr) class(process_instance_t), intent(in), target :: instance integer, intent(in) :: i_term type(connected_state_t), pointer :: ptr ptr => instance%term(i_term)%connected end function process_instance_get_connected_state_ptr @ %def process_instance_get_isolated_state_ptr @ %def process_instance_get_connected_state_ptr @ Return the indices of the beam particles and incoming partons within the currently active state matrix, respectively. <>= procedure :: get_beam_index => process_instance_get_beam_index procedure :: get_in_index => process_instance_get_in_index <>= subroutine process_instance_get_beam_index (instance, i_term, i_beam) class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term integer, dimension(:), intent(out) :: i_beam call instance%term(i_term)%connected%get_beam_index (i_beam) end subroutine process_instance_get_beam_index subroutine process_instance_get_in_index (instance, i_term, i_in) class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term integer, dimension(:), intent(out) :: i_in call instance%term(i_term)%connected%get_in_index (i_in) end subroutine process_instance_get_in_index @ %def process_instance_get_beam_index @ %def process_instance_get_in_index @ Return squared matrix element and event weight, and event weight excess where applicable. [[n_dropped]] is a number that can be nonzero when a weighted event has been generated, dropping events with zero weight (failed cuts) on the fly. <>= procedure :: get_sqme => process_instance_get_sqme procedure :: get_weight => process_instance_get_weight procedure :: get_excess => process_instance_get_excess procedure :: get_n_dropped => process_instance_get_n_dropped <>= function process_instance_get_sqme (instance, i_term) result (sqme) real(default) :: sqme class(process_instance_t), intent(in) :: instance integer, intent(in), optional :: i_term if (instance%evaluation_status >= STAT_EVALUATED_TRACE) then if (present (i_term)) then sqme = instance%term(i_term)%connected%trace%get_matrix_element (1) else sqme = instance%sqme end if else sqme = 0 end if end function process_instance_get_sqme function process_instance_get_weight (instance) result (weight) real(default) :: weight class(process_instance_t), intent(in) :: instance if (instance%evaluation_status >= STAT_EVENT_COMPLETE) then weight = instance%weight else weight = 0 end if end function process_instance_get_weight function process_instance_get_excess (instance) result (excess) real(default) :: excess class(process_instance_t), intent(in) :: instance if (instance%evaluation_status >= STAT_EVENT_COMPLETE) then excess = instance%excess else excess = 0 end if end function process_instance_get_excess function process_instance_get_n_dropped (instance) result (n_dropped) integer :: n_dropped class(process_instance_t), intent(in) :: instance if (instance%evaluation_status >= STAT_EVENT_COMPLETE) then n_dropped = instance%n_dropped else n_dropped = 0 end if end function process_instance_get_n_dropped @ %def process_instance_get_sqme @ %def process_instance_get_weight @ %def process_instance_get_excess @ %def process_instance_get_n_dropped @ Return the currently selected MCI channel. <>= procedure :: get_channel => process_instance_get_channel <>= function process_instance_get_channel (instance) result (channel) integer :: channel class(process_instance_t), intent(in) :: instance channel = instance%selected_channel end function process_instance_get_channel @ %def process_instance_get_channel @ <>= procedure :: set_fac_scale => process_instance_set_fac_scale <>= subroutine process_instance_set_fac_scale (instance, fac_scale) class(process_instance_t), intent(inout) :: instance real(default), intent(in) :: fac_scale integer :: i_term i_term = 1 call instance%term(i_term)%set_fac_scale (fac_scale) end subroutine process_instance_set_fac_scale @ %def process_instance_set_fac_scale @ Return factorization scale and strong coupling. We have to select a term instance. <>= procedure :: get_fac_scale => process_instance_get_fac_scale procedure :: get_alpha_s => process_instance_get_alpha_s <>= function process_instance_get_fac_scale (instance, i_term) result (fac_scale) class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term real(default) :: fac_scale fac_scale = instance%term(i_term)%get_fac_scale () end function process_instance_get_fac_scale function process_instance_get_alpha_s (instance, i_term) result (alpha_s) real(default) :: alpha_s class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term class(prc_core_t), pointer :: core => null () core => instance%process%get_core_term (i_term) alpha_s = instance%term(i_term)%get_alpha_s (core) core => null () end function process_instance_get_alpha_s @ %def process_instance_get_fac_scale @ %def process_instance_get_alpha_s @ <>= procedure :: get_qcd => process_instance_get_qcd <>= function process_instance_get_qcd (process_instance) result (qcd) type(qcd_t) :: qcd class(process_instance_t), intent(in) :: process_instance qcd = process_instance%process%get_qcd () end function process_instance_get_qcd @ %def process_instance_get_qcd @ Counter. <>= procedure :: reset_counter => process_instance_reset_counter procedure :: record_call => process_instance_record_call procedure :: get_counter => process_instance_get_counter <>= subroutine process_instance_reset_counter (process_instance) class(process_instance_t), intent(inout) :: process_instance call process_instance%mci_work(process_instance%i_mci)%reset_counter () end subroutine process_instance_reset_counter subroutine process_instance_record_call (process_instance) class(process_instance_t), intent(inout) :: process_instance call process_instance%mci_work(process_instance%i_mci)%record_call & (process_instance%evaluation_status) end subroutine process_instance_record_call pure function process_instance_get_counter (process_instance) result (counter) class(process_instance_t), intent(in) :: process_instance type(process_counter_t) :: counter counter = process_instance%mci_work(process_instance%i_mci)%get_counter () end function process_instance_get_counter @ %def process_instance_reset_counter @ %def process_instance_record_call @ %def process_instance_get_counter @ Sum up the total number of calls for all MCI records. <>= procedure :: get_actual_calls_total => process_instance_get_actual_calls_total <>= pure function process_instance_get_actual_calls_total (process_instance) & result (n) class(process_instance_t), intent(in) :: process_instance integer :: n integer :: i type(process_counter_t) :: counter n = 0 do i = 1, size (process_instance%mci_work) counter = process_instance%mci_work(i)%get_counter () n = n + counter%total end do end function process_instance_get_actual_calls_total @ %def process_instance_get_actual_calls_total @ <>= procedure :: reset_matrix_elements => process_instance_reset_matrix_elements <>= subroutine process_instance_reset_matrix_elements (instance) class(process_instance_t), intent(inout) :: instance integer :: i_term do i_term = 1, size (instance%term) call instance%term(i_term)%connected%trace%set_matrix_element (cmplx (0, 0, default)) call instance%term(i_term)%connected%matrix%set_matrix_element (cmplx (0, 0, default)) end do end subroutine process_instance_reset_matrix_elements @ %def process_instance_reset_matrix_elements @ <>= procedure :: get_test_phase_space_point & => process_instance_get_test_phase_space_point <>= subroutine process_instance_get_test_phase_space_point (instance, & i_component, i_core, p) type(vector4_t), dimension(:), allocatable, intent(out) :: p class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_component, i_core real(default), dimension(:), allocatable :: x logical :: success integer :: i_term instance%i_mci = i_component i_term = instance%process%get_i_term (i_core) associate (term => instance%term(i_term)) allocate (x (instance%mci_work(i_component)%config%n_par)) x = 0.5_default call instance%set_mcpar (x, .true.) call instance%select_channel (1) call term%compute_seed_kinematics & (instance%mci_work(i_component), 1, success) call instance%term(i_term)%evaluate_radiation_kinematics & (instance%mci_work(instance%i_mci)%get_x_process ()) call instance%term(i_term)%compute_hard_kinematics (success = success) allocate (p (size (term%p_hard))) p = term%int_hard%get_momenta () end associate end subroutine process_instance_get_test_phase_space_point @ %def process_instance_get_test_phase_space_point @ <>= procedure :: get_p_hard => process_instance_get_p_hard <>= pure function process_instance_get_p_hard (process_instance, i_term) & result (p_hard) type(vector4_t), dimension(:), allocatable :: p_hard class(process_instance_t), intent(in) :: process_instance integer, intent(in) :: i_term allocate (p_hard (size (process_instance%term(i_term)%get_p_hard ()))) p_hard = process_instance%term(i_term)%get_p_hard () end function process_instance_get_p_hard @ %def process_instance_get_p_hard @ <>= procedure :: get_first_active_i_term => process_instance_get_first_active_i_term <>= function process_instance_get_first_active_i_term (instance) result (i_term) integer :: i_term class(process_instance_t), intent(in) :: instance integer :: i i_term = 0 do i = 1, size (instance%term) if (instance%term(i)%active) then i_term = i exit end if end do end function process_instance_get_first_active_i_term @ %def process_instance_get_first_active_i_term @ <>= procedure :: get_real_of_mci => process_instance_get_real_of_mci <>= function process_instance_get_real_of_mci (instance) result (i_real) integer :: i_real class(process_instance_t), intent(in) :: instance select type (pcm => instance%pcm) type is (pcm_instance_nlo_t) i_real = pcm%i_mci_to_real_component (instance%i_mci) end select end function process_instance_get_real_of_mci @ %def process_instance_get_real_of_mci @ <>= procedure :: get_connected_states => process_instance_get_connected_states <>= function process_instance_get_connected_states (instance, i_component) result (connected) type(connected_state_t), dimension(:), allocatable :: connected class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_component connected = instance%process%get_connected_states (i_component, & instance%term(:)%connected) end function process_instance_get_connected_states @ %def process_instance_get_connected_states @ Get the hadronic center-of-mass energy <>= procedure :: get_sqrts => process_instance_get_sqrts <>= function process_instance_get_sqrts (instance) result (sqrts) class(process_instance_t), intent(in) :: instance real(default) :: sqrts sqrts = instance%process%get_sqrts () end function process_instance_get_sqrts @ %def process_instance_get_sqrts @ Get the polarizations <>= procedure :: get_polarization => process_instance_get_polarization <>= function process_instance_get_polarization (instance) result (pol) class(process_instance_t), intent(in) :: instance real(default), dimension(2) :: pol pol = instance%process%get_polarization () end function process_instance_get_polarization @ %def process_instance_get_polarization @ Get the beam spectrum <>= procedure :: get_beam_file => process_instance_get_beam_file <>= function process_instance_get_beam_file (instance) result (file) class(process_instance_t), intent(in) :: instance type(string_t) :: file file = instance%process%get_beam_file () end function process_instance_get_beam_file @ %def process_instance_get_beam_file @ Get the process name <>= procedure :: get_process_name => process_instance_get_process_name <>= function process_instance_get_process_name (instance) result (name) class(process_instance_t), intent(in) :: instance type(string_t) :: name name = instance%process%get_id () end function process_instance_get_process_name @ %def process_instance_get_process_name @ \subsubsection{Particle sets} Here we provide two procedures that convert the process instance from/to a particle set. The conversion applies to the trace evaluator which has no quantum-number information, thus it involves only the momenta and the parent-child relations. We keep virtual particles. If [[n_incoming]] is provided, the status code of the first [[n_incoming]] particles will be reset to incoming. Otherwise, they would be classified as virtual. Nevertheless, it is possible to reconstruct the complete structure from a particle set. The reconstruction implies a re-evaluation of the structure function and matrix-element codes. The [[i_term]] index is needed for both input and output, to select among different active trace evaluators. In both cases, the [[instance]] object must be properly initialized. NB: The [[recover_beams]] option should be used only when the particle set originates from an external event file, and the user has asked for it. It should be switched off when reading from raw event file. <>= procedure :: get_trace => process_instance_get_trace procedure :: set_trace => process_instance_set_trace <>= subroutine process_instance_get_trace (instance, pset, i_term, n_incoming) class(process_instance_t), intent(in), target :: instance type(particle_set_t), intent(out) :: pset integer, intent(in) :: i_term integer, intent(in), optional :: n_incoming type(interaction_t), pointer :: int logical :: ok int => instance%get_trace_int_ptr (i_term) call pset%init (ok, int, int, FM_IGNORE_HELICITY, & [0._default, 0._default], .false., .true., n_incoming) end subroutine process_instance_get_trace subroutine process_instance_set_trace & (instance, pset, i_term, recover_beams, check_match, success) class(process_instance_t), intent(inout), target :: instance type(particle_set_t), intent(in) :: pset integer, intent(in) :: i_term logical, intent(in), optional :: recover_beams, check_match logical, intent(out), optional :: success type(interaction_t), pointer :: int integer :: n_in int => instance%get_trace_int_ptr (i_term) n_in = instance%process%get_n_in () call pset%fill_interaction (int, n_in, & recover_beams = recover_beams, & check_match = check_match, & state_flv = instance%get_state_flv (i_term), & success = success) end subroutine process_instance_set_trace @ %def process_instance_get_trace @ %def process_instance_set_trace @ This procedure allows us to override any QCD setting of the WHIZARD process and directly set the coupling value that comes together with a particle set. <>= procedure :: set_alpha_qcd_forced => process_instance_set_alpha_qcd_forced <>= subroutine process_instance_set_alpha_qcd_forced (instance, i_term, alpha_qcd) class(process_instance_t), intent(inout) :: instance integer, intent(in) :: i_term real(default), intent(in) :: alpha_qcd call instance%term(i_term)%set_alpha_qcd_forced (alpha_qcd) end subroutine process_instance_set_alpha_qcd_forced @ %def process_instance_set_alpha_qcd_forced @ <>= procedure :: has_nlo_component => process_instance_has_nlo_component <>= function process_instance_has_nlo_component (instance) result (nlo) class(process_instance_t), intent(in) :: instance logical :: nlo nlo = instance%process%is_nlo_calculation () end function process_instance_has_nlo_component @ %def process_instance_has_nlo_component @ <>= procedure :: keep_failed_events => process_instance_keep_failed_events <>= function process_instance_keep_failed_events (instance) result (keep) logical :: keep class(process_instance_t), intent(in) :: instance keep = instance%mci_work(instance%i_mci)%keep_failed_events end function process_instance_keep_failed_events @ %def process_instance_keep_failed_events @ <>= procedure :: get_term_indices => process_instance_get_term_indices <>= function process_instance_get_term_indices (instance, nlo_type) result (i_term) integer, dimension(:), allocatable :: i_term class(process_instance_t), intent(in) :: instance integer :: nlo_type allocate (i_term (count (instance%term%nlo_type == nlo_type))) i_term = pack (instance%term%get_i_term_global (), instance%term%nlo_type == nlo_type) end function process_instance_get_term_indices @ %def process_instance_get_term_indices @ <>= procedure :: get_boost_to_lab => process_instance_get_boost_to_lab <>= function process_instance_get_boost_to_lab (instance, i_term) result (lt) type(lorentz_transformation_t) :: lt class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term lt = instance%term(i_term)%get_boost_to_lab () end function process_instance_get_boost_to_lab @ %def process_instance_get_boost_to_lab @ <>= procedure :: get_boost_to_cms => process_instance_get_boost_to_cms <>= function process_instance_get_boost_to_cms (instance, i_term) result (lt) type(lorentz_transformation_t) :: lt class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term lt = instance%term(i_term)%get_boost_to_cms () end function process_instance_get_boost_to_cms @ %def process_instance_get_boost_to_cms @ <>= procedure :: lab_is_cm => process_instance_lab_is_cm <>= function process_instance_lab_is_cm (instance, i_term) result (lab_is_cm) logical :: lab_is_cm class(process_instance_t), intent(in) :: instance integer, intent(in) :: i_term lab_is_cm = instance%term(i_term)%k_term%phs%lab_is_cm () end function process_instance_lab_is_cm @ %def process_instance_lab_is_cm @ The [[pacify]] subroutine has the purpose of setting numbers to zero which are (by comparing with a [[tolerance]] parameter) considered equivalent with zero. We do this in some unit tests. Here, we a apply this to the phase space subobject of the process instance. <>= public :: pacify <>= interface pacify module procedure pacify_process_instance end interface pacify <>= subroutine pacify_process_instance (instance) type(process_instance_t), intent(inout) :: instance integer :: i do i = 1, size (instance%term) call pacify (instance%term(i)%k_term%phs) end do end subroutine pacify_process_instance @ %def pacify @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Unit tests} Test module, followed by the corresponding implementation module. <<[[processes_ut.f90]]>>= <> module processes_ut use unit_tests use processes_uti <> <> <> contains <> end module processes_ut @ %def processes_ut @ <<[[processes_uti.f90]]>>= <> module processes_uti <> <> use format_utils, only: write_separator use constants, only: TWOPI4 use physics_defs, only: CONV use os_interface use sm_qcd use lorentz use pdg_arrays use model_data use models use var_base, only: vars_t use variables, only: var_list_t use model_testbed, only: prepare_model use particle_specifiers, only: new_prt_spec use flavors use interactions, only: reset_interaction_counter use particles use rng_base use mci_base use mci_none, only: mci_none_t use mci_midpoint use sf_mappings use sf_base use phs_base use phs_single use phs_forests, only: syntax_phs_forest_init, syntax_phs_forest_final use phs_wood, only: phs_wood_config_t use resonances, only: resonance_history_set_t use process_constants use prc_core_def, only: prc_core_def_t use prc_core use prc_test, only: prc_test_create_library use prc_template_me, only: template_me_def_t use process_libraries use prc_test_core use process_counter use process_config, only: process_term_t use process, only: process_t use instances, only: process_instance_t, process_instance_hook_t use rng_base_ut, only: rng_test_factory_t use sf_base_ut, only: sf_test_data_t use mci_base_ut, only: mci_test_t use phs_base_ut, only: phs_test_config_t <> <> <> <> contains <> <> end module processes_uti @ %def processes_uti @ API: driver for the unit tests below. <>= public :: processes_test <>= subroutine processes_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine processes_test @ %def processes_test \subsubsection{Write an empty process object} The most trivial test is to write an uninitialized process object. <>= call test (processes_1, "processes_1", & "write an empty process object", & u, results) <>= public :: processes_1 <>= subroutine processes_1 (u) integer, intent(in) :: u type(process_t) :: process write (u, "(A)") "* Test output: processes_1" write (u, "(A)") "* Purpose: display an empty process object" write (u, "(A)") call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Test output end: processes_1" end subroutine processes_1 @ %def processes_1 @ \subsubsection{Initialize a process object} Initialize a process and display it. <>= call test (processes_2, "processes_2", & "initialize a simple process object", & u, results) <>= public :: processes_2 <>= subroutine processes_2 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable :: process class(mci_t), allocatable :: mci_template class(phs_config_t), allocatable :: phs_config_template write (u, "(A)") "* Test output: processes_2" write (u, "(A)") "* Purpose: initialize a simple process object" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") libname = "processes2" procname = libname call os_data%init () call prc_test_create_library (libname, lib) write (u, "(A)") "* Initialize a process object" write (u, "(A)") call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%set_run_id (var_str ("run_2")) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) call process%setup_mci (dispatch_mci_empty) call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_2" end subroutine processes_2 @ %def processes_2 @ Trivial for testing: do not allocate the MCI record. <>= subroutine dispatch_mci_empty (mci, var_list, process_id, is_nlo) class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo end subroutine dispatch_mci_empty @ %def dispatch_mci_empty @ \subsubsection{Compute a trivial matrix element} Initialize a process, retrieve some information and compute a matrix element. We use the same trivial process as for the previous test. All momentum and state dependence is trivial, so we just test basic functionality. <>= call test (processes_3, "processes_3", & "retrieve a trivial matrix element", & u, results) <>= public :: processes_3 <>= subroutine processes_3 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable :: process class(phs_config_t), allocatable :: phs_config_template type(process_constants_t) :: data type(vector4_t), dimension(:), allocatable :: p write (u, "(A)") "* Test output: processes_3" write (u, "(A)") "* Purpose: create a process & &and compute a matrix element" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") libname = "processes3" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) call process%setup_mci (dispatch_mci_test3) write (u, "(A)") "* Return the number of process components" write (u, "(A)") write (u, "(A,I0)") "n_components = ", process%get_n_components () write (u, "(A)") write (u, "(A)") "* Return the number of flavor states" write (u, "(A)") data = process%get_constants (1) write (u, "(A,I0)") "n_flv(1) = ", data%n_flv write (u, "(A)") write (u, "(A)") "* Return the first flavor state" write (u, "(A)") write (u, "(A,4(1x,I0))") "flv_state(1) =", data%flv_state (:,1) write (u, "(A)") write (u, "(A)") "* Set up kinematics & &[arbitrary, the matrix element is constant]" allocate (p (4)) write (u, "(A)") write (u, "(A)") "* Retrieve the matrix element" write (u, "(A)") write (u, "(A,F5.3,' + ',F5.3,' I')") "me (1, p, 1, 1, 1) = ", & process%compute_amplitude (1, 1, 1, p, 1, 1, 1) write (u, "(A)") write (u, "(A)") "* Cleanup" call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_3" end subroutine processes_3 @ %def processes_3 @ MCI record with some contents. <>= subroutine dispatch_mci_test3 (mci, var_list, process_id, is_nlo) class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo allocate (mci_test_t :: mci) select type (mci) type is (mci_test_t) call mci%set_dimensions (2, 2) call mci%set_divisions (100) end select end subroutine dispatch_mci_test3 @ %def dispatch_mci_test3 @ \subsubsection{Generate a process instance} Initialize a process and process instance, choose a sampling point and fill the process instance. We use the same trivial process as for the previous test. All momentum and state dependence is trivial, so we just test basic functionality. <>= call test (processes_4, "processes_4", & "create and fill a process instance (partonic event)", & u, results) <>= public :: processes_4 <>= subroutine processes_4 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance type(particle_set_t) :: pset write (u, "(A)") "* Test output: processes_4" write (u, "(A)") "* Purpose: create a process & &and fill a process instance" write (u, "(A)") write (u, "(A)") "* Build and initialize a test process" write (u, "(A)") libname = "processes4" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call reset_interaction_counter () call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_empty) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Inject a set of random numbers" write (u, "(A)") call process_instance%choose_mci (1) call process_instance%set_mcpar ([0._default, 0._default]) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Set up hard kinematics" write (u, "(A)") call process_instance%select_channel (1) call process_instance%compute_seed_kinematics () call process_instance%compute_hard_kinematics () call process_instance%compute_eff_kinematics () call process_instance%evaluate_expressions () call process_instance%compute_other_channels () write (u, "(A)") "* Evaluate matrix element and square" write (u, "(A)") call process_instance%evaluate_trace () call process_instance%write (u) call process_instance%get_trace (pset, 1) call process_instance%final () deallocate (process_instance) write (u, "(A)") write (u, "(A)") "* Particle content:" write (u, "(A)") call write_separator (u) call pset%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Recover process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1, check_match = .false.) call process_instance%activate () process_instance%evaluation_status = STAT_EFF_KINEMATICS call process_instance%recover_hard_kinematics (i_term = 1) call process_instance%recover_seed_kinematics (i_term = 1) call process_instance%select_channel (1) call process_instance%recover_mcpar (i_term = 1) call process_instance%compute_seed_kinematics (skip_term = 1) call process_instance%compute_hard_kinematics (skip_term = 1) call process_instance%compute_eff_kinematics (skip_term = 1) call process_instance%evaluate_expressions () call process_instance%compute_other_channels (skip_term = 1) call process_instance%evaluate_trace () call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call pset%final () call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_4" end subroutine processes_4 @ %def processes_4 @ \subsubsection{Structure function configuration} Configure structure functions (multi-channel) in a process object. <>= call test (processes_7, "processes_7", & "process configuration with structure functions", & u, results) <>= public :: processes_7 <>= subroutine processes_7 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(pdg_array_t) :: pdg_in class(sf_data_t), allocatable, target :: data type(sf_config_t), dimension(:), allocatable :: sf_config type(sf_channel_t), dimension(2) :: sf_channel write (u, "(A)") "* Test output: processes_7" write (u, "(A)") "* Purpose: initialize a process with & &structure functions" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes7" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Set beam, structure functions, and mappings" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () pdg_in = 25 allocate (sf_test_data_t :: data) select type (data) type is (sf_test_data_t) call data%init (process%get_model_ptr (), pdg_in) end select allocate (sf_config (2)) call sf_config(1)%init ([1], data) call sf_config(2)%init ([2], data) call process%init_sf_chain (sf_config) deallocate (sf_config) call process%test_allocate_sf_channels (3) call sf_channel(1)%init (2) call sf_channel(1)%activate_mapping ([1,2]) call process%set_sf_channel (2, sf_channel(1)) call sf_channel(2)%init (2) call sf_channel(2)%set_s_mapping ([1,2]) call process%set_sf_channel (3, sf_channel(2)) call process%setup_mci (dispatch_mci_empty) call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_7" end subroutine processes_7 @ %def processes_7 @ \subsubsection{Evaluating a process with structure function} Configure structure functions (single-channel) in a process object, create an instance, compute kinematics and evaluate. Note the order of operations when setting up structure functions and phase space. The beams are first, they determine the [[sqrts]] value. We can also set up the chain of structure functions. We then configure the phase space. From this, we can obtain information about special configurations (resonances, etc.), which we need for allocating the possible structure-function channels (parameterizations and mappings). Finally, we match phase-space channels onto structure-function channels. In the current example, this matching is trivial; we only have one structure-function channel. <>= call test (processes_8, "processes_8", & "process evaluation with structure functions", & u, results) <>= public :: processes_8 <>= subroutine processes_8 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance type(pdg_array_t) :: pdg_in class(sf_data_t), allocatable, target :: data type(sf_config_t), dimension(:), allocatable :: sf_config type(sf_channel_t) :: sf_channel type(particle_set_t) :: pset write (u, "(A)") "* Test output: processes_8" write (u, "(A)") "* Purpose: evaluate a process with & &structure functions" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes8" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call reset_interaction_counter () call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Set beam, structure functions, and mappings" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) pdg_in = 25 allocate (sf_test_data_t :: data) select type (data) type is (sf_test_data_t) call data%init (process%get_model_ptr (), pdg_in) end select allocate (sf_config (2)) call sf_config(1)%init ([1], data) call sf_config(2)%init ([2], data) call process%init_sf_chain (sf_config) deallocate (sf_config) call process%configure_phs () call process%test_allocate_sf_channels (1) call sf_channel%init (2) call sf_channel%activate_mapping ([1,2]) call process%set_sf_channel (1, sf_channel) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_mci (dispatch_mci_empty) call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) write (u, "(A)") "* Set up kinematics and evaluate" write (u, "(A)") call process_instance%choose_mci (1) call process_instance%evaluate_sqme (1, & [0.8_default, 0.8_default, 0.1_default, 0.2_default]) call process_instance%write (u) call process_instance%get_trace (pset, 1) call process_instance%final () deallocate (process_instance) write (u, "(A)") write (u, "(A)") "* Particle content:" write (u, "(A)") call write_separator (u) call pset%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Recover process instance" write (u, "(A)") call reset_interaction_counter (2) allocate (process_instance) call process_instance%init (process) call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1, check_match = .false.) call process_instance%recover & (channel = 1, i_term = 1, update_sqme = .true., recover_phs = .true.) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call pset%final () call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_8" end subroutine processes_8 @ %def processes_8 @ \subsubsection{Multi-channel phase space and structure function} This is an extension of the previous example. This time, we have two distinct structure-function channels which are matched to the two distinct phase-space channels. <>= call test (processes_9, "processes_9", & "multichannel kinematics and structure functions", & u, results) <>= public :: processes_9 <>= subroutine processes_9 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance type(pdg_array_t) :: pdg_in class(sf_data_t), allocatable, target :: data type(sf_config_t), dimension(:), allocatable :: sf_config type(sf_channel_t) :: sf_channel real(default), dimension(4) :: x_saved type(particle_set_t) :: pset write (u, "(A)") "* Test output: processes_9" write (u, "(A)") "* Purpose: evaluate a process with & &structure functions" write (u, "(A)") "* in a multi-channel configuration" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes9" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call reset_interaction_counter () call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Set beam, structure functions, and mappings" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) pdg_in = 25 allocate (sf_test_data_t :: data) select type (data) type is (sf_test_data_t) call data%init (process%get_model_ptr (), pdg_in) end select allocate (sf_config (2)) call sf_config(1)%init ([1], data) call sf_config(2)%init ([2], data) call process%init_sf_chain (sf_config) deallocate (sf_config) call process%configure_phs () call process%test_allocate_sf_channels (2) call sf_channel%init (2) call process%set_sf_channel (1, sf_channel) call sf_channel%init (2) call sf_channel%activate_mapping ([1,2]) call process%set_sf_channel (2, sf_channel) call process%test_set_component_sf_channel ([1, 2]) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_mci (dispatch_mci_empty) call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) write (u, "(A)") "* Set up kinematics in channel 1 and evaluate" write (u, "(A)") call process_instance%choose_mci (1) call process_instance%evaluate_sqme (1, & [0.8_default, 0.8_default, 0.1_default, 0.2_default]) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Extract MC input parameters" write (u, "(A)") write (u, "(A)") "Channel 1:" call process_instance%get_mcpar (1, x_saved) write (u, "(2x,9(1x,F7.5))") x_saved write (u, "(A)") "Channel 2:" call process_instance%get_mcpar (2, x_saved) write (u, "(2x,9(1x,F7.5))") x_saved write (u, "(A)") write (u, "(A)") "* Set up kinematics in channel 2 and evaluate" write (u, "(A)") call process_instance%evaluate_sqme (2, x_saved) call process_instance%write (u) call process_instance%get_trace (pset, 1) call process_instance%final () deallocate (process_instance) write (u, "(A)") write (u, "(A)") "* Recover process instance for channel 2" write (u, "(A)") call reset_interaction_counter (2) allocate (process_instance) call process_instance%init (process) call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1, check_match = .false.) call process_instance%recover & (channel = 2, i_term = 1, update_sqme = .true., recover_phs = .true.) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call pset%final () call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_9" end subroutine processes_9 @ %def processes_9 @ \subsubsection{Event generation} Activate the MC integrator for the process object and use it to generate a single event. Note that the test integrator does not require integration in preparation for generating events. <>= call test (processes_10, "processes_10", & "event generation", & u, results) <>= public :: processes_10 <>= subroutine processes_10 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(mci_t), pointer :: mci class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance write (u, "(A)") "* Test output: processes_10" write (u, "(A)") "* Purpose: generate events for a process without & &structure functions" write (u, "(A)") "* in a multi-channel configuration" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes10" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call reset_interaction_counter () call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_test10) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) write (u, "(A)") "* Generate weighted event" write (u, "(A)") call process%test_get_mci_ptr (mci) select type (mci) type is (mci_test_t) ! This ensures that the next 'random' numbers are 0.3, 0.5, 0.7 call mci%rng%init (3) ! Include the constant PHS factor in the stored maximum of the integrand call mci%set_max_factor (conv * twopi4 & / (2 * sqrt (lambda (sqrts **2, 125._default**2, 125._default**2)))) end select call process_instance%generate_weighted_event (1) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Generate unweighted event" write (u, "(A)") call process_instance%generate_unweighted_event (1) call process%test_get_mci_ptr (mci) select type (mci) type is (mci_test_t) write (u, "(A,I0)") " Success in try ", mci%tries write (u, "(A)") end select call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_10" end subroutine processes_10 @ %def processes_10 @ MCI record with some contents. <>= subroutine dispatch_mci_test10 (mci, var_list, process_id, is_nlo) class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo allocate (mci_test_t :: mci) select type (mci) type is (mci_test_t); call mci%set_divisions (100) end select end subroutine dispatch_mci_test10 @ %def dispatch_mci_test10 @ \subsubsection{Integration} Activate the MC integrator for the process object and use it to integrate over phase space. <>= call test (processes_11, "processes_11", & "integration", & u, results) <>= public :: processes_11 <>= subroutine processes_11 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(mci_t), allocatable :: mci_template class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t), allocatable, target :: process_instance write (u, "(A)") "* Test output: processes_11" write (u, "(A)") "* Purpose: integrate a process without & &structure functions" write (u, "(A)") "* in a multi-channel configuration" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes11" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call reset_interaction_counter () call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_test10) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) write (u, "(A)") "* Integrate with default test parameters" write (u, "(A)") call process_instance%integrate (1, n_it=1, n_calls=10000) call process%final_integration (1) call process%write (.false., u) write (u, "(A)") write (u, "(A,ES13.7)") " Integral divided by phs factor = ", & process%get_integral (1) & / process_instance%term(1)%k_term%phs_factor write (u, "(A)") write (u, "(A)") "* Cleanup" call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_11" end subroutine processes_11 @ %def processes_11 @ \subsubsection{Complete events} For the purpose of simplifying further tests, we implement a convenience routine that initializes a process and prepares a single event. This is a wrapup of the test [[processes_10]]. The procedure is re-exported by the [[processes_ut]] module. <>= public :: prepare_test_process <>= subroutine prepare_test_process & (process, process_instance, model, var_list, run_id) type(process_t), intent(out), target :: process type(process_instance_t), intent(out), target :: process_instance class(model_data_t), intent(in), target :: model type(var_list_t), intent(inout), optional :: var_list type(string_t), intent(in), optional :: run_id type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), allocatable, target :: process_model class(mci_t), pointer :: mci class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts libname = "processes_test" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call reset_interaction_counter () allocate (process_model) call process_model%init (model%get_name (), & model%get_n_real (), & model%get_n_complex (), & model%get_n_field (), & model%get_n_vtx ()) call process_model%copy_from (model) call process%init (procname, lib, os_data, process_model, var_list) if (present (run_id)) call process%set_run_id (run_id) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_test10) call process%setup_terms () call process_instance%init (process) call process%test_get_mci_ptr (mci) select type (mci) type is (mci_test_t) ! This ensures that the next 'random' numbers are 0.3, 0.5, 0.7 call mci%rng%init (3) ! Include the constant PHS factor in the stored maximum of the integrand call mci%set_max_factor (conv * twopi4 & / (2 * sqrt (lambda (sqrts **2, 125._default**2, 125._default**2)))) end select call process%reset_library_ptr () ! avoid dangling pointer call process_model%final () end subroutine prepare_test_process @ %def prepare_test_process @ Here we do the cleanup of the process and process instance emitted by the previous routine. <>= public :: cleanup_test_process <>= subroutine cleanup_test_process (process, process_instance) type(process_t), intent(inout) :: process type(process_instance_t), intent(inout) :: process_instance call process_instance%final () call process%final () end subroutine cleanup_test_process @ %def cleanup_test_process @ This is the actual test. Prepare the test process and event, fill all evaluators, and display the results. Use a particle set as temporary storage, read kinematics and recalculate the event. <>= call test (processes_12, "processes_12", & "event post-processing", & u, results) <>= public :: processes_12 <>= subroutine processes_12 (u) integer, intent(in) :: u type(process_t), allocatable, target :: process type(process_instance_t), allocatable, target :: process_instance type(particle_set_t) :: pset type(model_data_t), target :: model write (u, "(A)") "* Test output: processes_12" write (u, "(A)") "* Purpose: generate a complete partonic event" write (u, "(A)") call model%init_test () write (u, "(A)") "* Build and initialize process and process instance & &and generate event" write (u, "(A)") allocate (process) allocate (process_instance) call prepare_test_process (process, process_instance, model, & run_id = var_str ("run_12")) call process_instance%setup_event_data (i_core = 1) call process%prepare_simulation (1) call process_instance%init_simulation (1) call process_instance%generate_weighted_event (1) call process_instance%evaluate_event_data () call process_instance%write (u) call process_instance%get_trace (pset, 1) call process_instance%final_simulation (1) call process_instance%final () deallocate (process_instance) write (u, "(A)") write (u, "(A)") "* Recover kinematics and recalculate" write (u, "(A)") call reset_interaction_counter (2) allocate (process_instance) call process_instance%init (process) call process_instance%setup_event_data () call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1, check_match = .false.) call process_instance%recover & (channel = 1, i_term = 1, update_sqme = .true., recover_phs = .true.) call process_instance%recover_event () call process_instance%evaluate_event_data () call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call cleanup_test_process (process, process_instance) deallocate (process_instance) deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_12" end subroutine processes_12 @ %def processes_12 @ \subsubsection{Colored interaction} This test specifically checks the transformation of process data (flavor, helicity, and color) into an interaction in a process term. We use the [[test_t]] process core (which has no nontrivial particles), but call only the [[is_allowed]] method, which always returns true. <>= call test (processes_13, "processes_13", & "colored interaction", & u, results) <>= public :: processes_13 <>= subroutine processes_13 (u) integer, intent(in) :: u type(os_data_t) :: os_data type(model_data_t), target :: model type(process_term_t) :: term class(prc_core_t), allocatable :: core write (u, "(A)") "* Test output: processes_13" write (u, "(A)") "* Purpose: initialized a colored interaction" write (u, "(A)") write (u, "(A)") "* Set up a process constants block" write (u, "(A)") call os_data%init () call model%init_sm_test () allocate (test_t :: core) associate (data => term%data) data%n_in = 2 data%n_out = 3 data%n_flv = 2 data%n_hel = 2 data%n_col = 2 data%n_cin = 2 allocate (data%flv_state (5, 2)) data%flv_state (:,1) = [ 1, 21, 1, 21, 21] data%flv_state (:,2) = [ 2, 21, 2, 21, 21] allocate (data%hel_state (5, 2)) data%hel_state (:,1) = [1, 1, 1, 1, 0] data%hel_state (:,2) = [1,-1, 1,-1, 0] allocate (data%col_state (2, 5, 2)) data%col_state (:,:,1) = & reshape ([[1, 0], [2,-1], [3, 0], [2,-3], [0,0]], [2,5]) data%col_state (:,:,2) = & reshape ([[1, 0], [2,-3], [3, 0], [2,-1], [0,0]], [2,5]) allocate (data%ghost_flag (5, 2)) data%ghost_flag(1:4,:) = .false. data%ghost_flag(5,:) = .true. end associate write (u, "(A)") "* Set up the interaction" write (u, "(A)") call reset_interaction_counter () call term%setup_interaction (core, model) call term%int%basic_write (u) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_13" end subroutine processes_13 @ %def processes_13 @ \subsubsection{MD5 sums} Configure a process with structure functions (multi-channel) and compute MD5 sums <>= call test (processes_14, "processes_14", & "process configuration and MD5 sum", & u, results) <>= public :: processes_14 <>= subroutine processes_14 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(pdg_array_t) :: pdg_in class(sf_data_t), allocatable, target :: data type(sf_config_t), dimension(:), allocatable :: sf_config type(sf_channel_t), dimension(3) :: sf_channel write (u, "(A)") "* Test output: processes_14" write (u, "(A)") "* Purpose: initialize a process with & &structure functions" write (u, "(A)") "* and compute MD5 sum" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes7" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call lib%compute_md5sum () call model%init_test () allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_test_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Set beam, structure functions, and mappings" write (u, "(A)") sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () pdg_in = 25 allocate (sf_test_data_t :: data) select type (data) type is (sf_test_data_t) call data%init (process%get_model_ptr (), pdg_in) end select call process%test_allocate_sf_channels (3) allocate (sf_config (2)) call sf_config(1)%init ([1], data) call sf_config(2)%init ([2], data) call process%init_sf_chain (sf_config) deallocate (sf_config) call sf_channel(1)%init (2) call process%set_sf_channel (1, sf_channel(1)) call sf_channel(2)%init (2) call sf_channel(2)%activate_mapping ([1,2]) call process%set_sf_channel (2, sf_channel(2)) call sf_channel(3)%init (2) call sf_channel(3)%set_s_mapping ([1,2]) call process%set_sf_channel (3, sf_channel(3)) call process%setup_mci (dispatch_mci_empty) call process%compute_md5sum () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_14" end subroutine processes_14 @ %def processes_14 @ \subsubsection{Decay Process Evaluation} Initialize an evaluate a decay process. <>= call test (processes_15, "processes_15", & "decay process", & u, results) <>= public :: processes_15 <>= subroutine processes_15 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template type(process_instance_t), allocatable, target :: process_instance type(particle_set_t) :: pset write (u, "(A)") "* Test output: processes_15" write (u, "(A)") "* Purpose: initialize a decay process object" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") libname = "processes15" procname = libname call os_data%init () call prc_test_create_library (libname, lib, scattering = .false., & decay = .true.) 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"))) write (u, "(A)") "* Initialize a process object" write (u, "(A)") allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_single_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") call process%setup_beams_decay (i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_empty) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") call reset_interaction_counter (3) allocate (process_instance) call process_instance%init (process) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Inject a set of random numbers" write (u, "(A)") call process_instance%choose_mci (1) call process_instance%set_mcpar ([0._default, 0._default]) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Set up hard kinematics" write (u, "(A)") call process_instance%select_channel (1) call process_instance%compute_seed_kinematics () call process_instance%compute_hard_kinematics () write (u, "(A)") "* Evaluate matrix element and square" write (u, "(A)") call process_instance%compute_eff_kinematics () call process_instance%evaluate_expressions () call process_instance%compute_other_channels () call process_instance%evaluate_trace () call process_instance%write (u) call process_instance%get_trace (pset, 1) call process_instance%final () deallocate (process_instance) write (u, "(A)") write (u, "(A)") "* Particle content:" write (u, "(A)") call write_separator (u) call pset%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Recover process instance" write (u, "(A)") call reset_interaction_counter (3) allocate (process_instance) call process_instance%init (process) call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1, check_match = .false.) call process_instance%recover (1, 1, .true., .true.) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call pset%final () call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_15" end subroutine processes_15 @ %def processes_15 @ \subsubsection{Integration: decay} Activate the MC integrator for the decay object and use it to integrate over phase space. <>= call test (processes_16, "processes_16", & "decay integration", & u, results) <>= public :: processes_16 <>= subroutine processes_16 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template type(process_instance_t), allocatable, target :: process_instance write (u, "(A)") "* Test output: processes_16" write (u, "(A)") "* Purpose: integrate a process without & &structure functions" write (u, "(A)") "* in a multi-channel configuration" write (u, "(A)") write (u, "(A)") "* Build and initialize a process object" write (u, "(A)") libname = "processes16" procname = libname call os_data%init () call prc_test_create_library (libname, lib, scattering = .false., & decay = .true.) call reset_interaction_counter () 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"))) allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_single_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") call process%setup_beams_decay (i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_test_midpoint) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") allocate (process_instance) call process_instance%init (process) write (u, "(A)") "* Integrate with default test parameters" write (u, "(A)") call process_instance%integrate (1, n_it=1, n_calls=10000) call process%final_integration (1) call process%write (.false., u) write (u, "(A)") write (u, "(A,ES13.7)") " Integral divided by phs factor = ", & process%get_integral (1) & / process_instance%term(1)%k_term%phs_factor write (u, "(A)") write (u, "(A)") "* Cleanup" call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_16" end subroutine processes_16 @ %def processes_16 @ MCI record prepared for midpoint integrator. <>= subroutine dispatch_mci_test_midpoint (mci, var_list, process_id, is_nlo) class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo allocate (mci_midpoint_t :: mci) end subroutine dispatch_mci_test_midpoint @ %def dispatch_mci_test_midpoint @ \subsubsection{Decay Process Evaluation} Initialize an evaluate a decay process for a moving particle. <>= call test (processes_17, "processes_17", & "decay of moving particle", & u, results) <>= public :: processes_17 <>= subroutine processes_17 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template type(process_instance_t), allocatable, target :: process_instance type(particle_set_t) :: pset type(flavor_t) :: flv_beam real(default) :: m, p, E write (u, "(A)") "* Test output: processes_17" write (u, "(A)") "* Purpose: initialize a decay process object" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") libname = "processes17" procname = libname call os_data%init () call prc_test_create_library (libname, lib, scattering = .false., & decay = .true.) write (u, "(A)") "* Initialize a process object" 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"))) allocate (process) call process%init (procname, lib, os_data, model) call process%setup_test_cores () allocate (phs_single_config_t :: phs_config_template) call process%init_components (phs_config_template) write (u, "(A)") "* Prepare a trivial beam setup" write (u, "(A)") call process%setup_beams_decay (rest_frame = .false., i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_empty) write (u, "(A)") "* Complete process initialization" write (u, "(A)") call process%setup_terms () call process%write (.false., u) write (u, "(A)") write (u, "(A)") "* Create a process instance" write (u, "(A)") call reset_interaction_counter (3) allocate (process_instance) call process_instance%init (process) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Set parent momentum and random numbers" write (u, "(A)") call process_instance%choose_mci (1) call process_instance%set_mcpar ([0._default, 0._default]) call flv_beam%init (25, process%get_model_ptr ()) m = flv_beam%get_mass () p = 3 * m / 4 E = sqrt (m**2 + p**2) call process_instance%set_beam_momenta ([vector4_moving (E, p, 3)]) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Set up hard kinematics" write (u, "(A)") call process_instance%select_channel (1) call process_instance%compute_seed_kinematics () call process_instance%compute_hard_kinematics () write (u, "(A)") "* Evaluate matrix element and square" write (u, "(A)") call process_instance%compute_eff_kinematics () call process_instance%evaluate_expressions () call process_instance%compute_other_channels () call process_instance%evaluate_trace () call process_instance%write (u) call process_instance%get_trace (pset, 1) call process_instance%final () deallocate (process_instance) write (u, "(A)") write (u, "(A)") "* Particle content:" write (u, "(A)") call write_separator (u) call pset%write (u) call write_separator (u) write (u, "(A)") write (u, "(A)") "* Recover process instance" write (u, "(A)") call reset_interaction_counter (3) allocate (process_instance) call process_instance%init (process) call process_instance%choose_mci (1) call process_instance%set_trace (pset, 1, check_match = .false.) call process_instance%recover (1, 1, .true., .true.) call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call pset%final () call process_instance%final () deallocate (process_instance) call process%final () deallocate (process) call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_17" end subroutine processes_17 @ %def processes_17 @ \subsubsection{Resonances in Phase Space} This test demonstrates the extraction of the resonance-history set from the generated phase space. We need a nontrivial process, but no matrix element. This is provided by the [[prc_template]] method, using the [[SM]] model. We also need the [[phs_wood]] method, otherwise we would not have resonances in the phase space configuration. <>= call test (processes_18, "processes_18", & "extract resonance history set", & u, results) <>= public :: processes_18 <>= subroutine processes_18 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(string_t) :: model_name type(os_data_t) :: os_data class(model_data_t), pointer :: model class(vars_t), pointer :: vars type(process_t), pointer :: process type(resonance_history_set_t) :: res_set integer :: i write (u, "(A)") "* Test output: processes_18" write (u, "(A)") "* Purpose: extra resonance histories" write (u, "(A)") write (u, "(A)") "* Build and load a test library with one process" write (u, "(A)") libname = "processes_18_lib" procname = "processes_18_p" call os_data%init () call syntax_phs_forest_init () model_name = "SM" model => null () call prepare_model (model, model_name, vars) write (u, "(A)") "* Initialize a process library with one process" write (u, "(A)") select type (model) class is (model_t) call prepare_resonance_test_library (lib, libname, procname, model, os_data, u) end select write (u, "(A)") write (u, "(A)") "* Initialize a process object with phase space" allocate (process) select type (model) class is (model_t) call prepare_resonance_test_process (process, lib, procname, model, os_data) end select write (u, "(A)") write (u, "(A)") "* Extract resonance history set" write (u, "(A)") call process%extract_resonance_history_set (res_set) call res_set%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call process%final () deallocate (process) call model%final () deallocate (model) call syntax_phs_forest_final () write (u, "(A)") write (u, "(A)") "* Test output end: processes_18" end subroutine processes_18 @ %def processes_18 @ Auxiliary subroutine that constructs the process library for the above test. <>= subroutine prepare_resonance_test_library & (lib, libname, procname, model, os_data, u) type(process_library_t), target, intent(out) :: lib type(string_t), intent(in) :: libname type(string_t), intent(in) :: procname type(model_t), intent(in), target :: model type(os_data_t), intent(in) :: os_data integer, intent(in) :: u type(string_t), dimension(:), allocatable :: prt_in, prt_out class(prc_core_def_t), allocatable :: def type(process_def_entry_t), pointer :: entry call lib%init (libname) allocate (prt_in (2), prt_out (3)) prt_in = [var_str ("e+"), var_str ("e-")] prt_out = [var_str ("d"), var_str ("ubar"), var_str ("W+")] allocate (template_me_def_t :: def) select type (def) type is (template_me_def_t) call def%init (model, prt_in, prt_out, unity = .false.) end select allocate (entry) call entry%init (procname, & model_name = model%get_name (), & n_in = 2, n_components = 1) call entry%import_component (1, n_out = size (prt_out), & prt_in = new_prt_spec (prt_in), & prt_out = new_prt_spec (prt_out), & method = var_str ("template"), & variant = def) call entry%write (u) call lib%append (entry) call lib%configure (os_data) call lib%write_makefile (os_data, force = .true., verbose = .false.) call lib%clean (os_data, distclean = .false.) call lib%write_driver (force = .true.) call lib%load (os_data) end subroutine prepare_resonance_test_library @ %def prepare_resonance_test_library @ We want a test process which has been initialized up to the point where we can evaluate the matrix element. This is in fact rather complicated. We copy the steps from [[integration_setup_process]] in the [[integrate]] module, which is not available at this point. <>= subroutine prepare_resonance_test_process & (process, lib, procname, model, os_data) class(process_t), intent(out), target :: process type(process_library_t), intent(in), target :: lib type(string_t), intent(in) :: procname type(model_t), intent(in), target :: model type(os_data_t), intent(in) :: os_data class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts call process%init (procname, lib, os_data, model) allocate (phs_wood_config_t :: phs_config_template) call process%init_components (phs_config_template) call process%setup_test_cores (type_string = var_str ("template")) sqrts = 1000 call process%setup_beams_sqrts (sqrts, i_core = 1) call process%configure_phs () call process%setup_mci (dispatch_mci_none) call process%setup_terms () end subroutine prepare_resonance_test_process @ %def prepare_resonance_test_process @ MCI record prepared for the none (dummy) integrator. <>= subroutine dispatch_mci_none (mci, var_list, process_id, is_nlo) class(mci_t), allocatable, intent(out) :: mci type(var_list_t), intent(in) :: var_list type(string_t), intent(in) :: process_id logical, intent(in), optional :: is_nlo allocate (mci_none_t :: mci) end subroutine dispatch_mci_none @ %def dispatch_mci_none @ \subsubsection{Add after evaluate hook(s)} Initialize a process and process instance, add a trivial process hook, choose a sampling point and fill the process instance. We use the same trivial process as for the previous test. All momentum and state dependence is trivial, so we just test basic functionality. <>= type, extends(process_instance_hook_t) :: process_instance_hook_test_t integer :: unit character(len=15) :: name contains procedure :: init => process_instance_hook_test_init procedure :: final => process_instance_hook_test_final procedure :: evaluate => process_instance_hook_test_evaluate end type process_instance_hook_test_t @ <>= subroutine process_instance_hook_test_init (hook, var_list, instance) class(process_instance_hook_test_t), intent(inout), target :: hook type(var_list_t), intent(in) :: var_list class(process_instance_t), intent(in), target :: instance end subroutine process_instance_hook_test_init subroutine process_instance_hook_test_final (hook) class(process_instance_hook_test_t), intent(inout) :: hook end subroutine process_instance_hook_test_final subroutine process_instance_hook_test_evaluate (hook, instance) class(process_instance_hook_test_t), intent(inout) :: hook class(process_instance_t), intent(in), target :: instance write (hook%unit, "(A)") "Execute hook:" write (hook%unit, "(2X,A,1X,A,I0,A)") hook%name, "(", len (trim (hook%name)), ")" end subroutine process_instance_hook_test_evaluate @ <>= call test (processes_19, "processes_19", & "add trivial hooks to a process instance ", & u, results) <>= public :: processes_19 <>= subroutine processes_19 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data class(model_data_t), pointer :: model type(process_t), allocatable, target :: process class(phs_config_t), allocatable :: phs_config_template real(default) :: sqrts type(process_instance_t) :: process_instance class(process_instance_hook_t), allocatable, target :: process_instance_hook, process_instance_hook2 type(particle_set_t) :: pset write (u, "(A)") "* Test output: processes_19" write (u, "(A)") "* Purpose: allocate process instance & &and add an after evaluate hook" write (u, "(A)") write (u, "(A)") write (u, "(A)") "* Allocate a process instance" write (u, "(A)") call process_instance%write (u) write (u, "(A)") write (u, "(A)") "* Allocate hook and add to process instance" write (u, "(A)") allocate (process_instance_hook_test_t :: process_instance_hook) call process_instance%append_after_hook (process_instance_hook) allocate (process_instance_hook_test_t :: process_instance_hook2) call process_instance%append_after_hook (process_instance_hook2) select type (process_instance_hook) type is (process_instance_hook_test_t) process_instance_hook%unit = u process_instance_hook%name = "Hook 1" end select select type (process_instance_hook2) type is (process_instance_hook_test_t) process_instance_hook2%unit = u process_instance_hook2%name = "Hook 2" end select write (u, "(A)") "* Evaluate matrix element and square" write (u, "(A)") call process_instance%evaluate_after_hook () write (u, "(A)") write (u, "(A)") "* Cleanup" call process_instance_hook%final () deallocate (process_instance_hook) write (u, "(A)") write (u, "(A)") "* Test output end: processes_19" end subroutine processes_19 @ %def processes_19 @ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Process Stacks} For storing and handling multiple processes, we define process stacks. These are ordinary stacks where new process entries are pushed onto the top. We allow for multiple entries with identical process ID, but distinct run ID. The implementation is essentially identical to the [[prclib_stacks]] module above. Unfortunately, Fortran supports no generic programming, so we do not make use of this fact. When searching for a specific process ID, we will get (a pointer to) the topmost process entry with that ID on the stack, which was entered last. Usually, this is the best version of the process (in terms of integral, etc.) Thus the stack terminology makes sense. <<[[process_stacks.f90]]>>= <> module process_stacks <> <> use io_units use format_utils, only: write_separator use diagnostics use os_interface use sm_qcd use model_data use rng_base use variables use observables use process_libraries use process <> <> <> contains <> end module process_stacks @ %def process_stacks @ \subsection{The process entry type} A process entry is a process object, augmented by a pointer to the next entry. We do not need specific methods, all relevant methods are inherited. On higher level, processes should be prepared as process entry objects. <>= public :: process_entry_t <>= type, extends (process_t) :: process_entry_t type(process_entry_t), pointer :: next => null () end type process_entry_t @ %def process_entry_t @ \subsection{The process stack type} For easy conversion and lookup it is useful to store the filling number in the object. The content is stored as a linked list. The [[var_list]] component stores process-specific results, so they can be retrieved as (pseudo) variables. The process stack can be linked to another one. This allows us to work with stacks of local scope. <>= public :: process_stack_t <>= type :: process_stack_t integer :: n = 0 type(process_entry_t), pointer :: first => null () type(var_list_t), pointer :: var_list => null () type(process_stack_t), pointer :: next => null () contains <> end type process_stack_t @ %def process_stack_t @ Finalize partly: deallocate the process stack and variable list entries, but keep the variable list as an empty object. This way, the variable list links are kept. <>= procedure :: clear => process_stack_clear <>= subroutine process_stack_clear (stack) class(process_stack_t), intent(inout) :: stack type(process_entry_t), pointer :: process if (associated (stack%var_list)) then call stack%var_list%final () end if do while (associated (stack%first)) process => stack%first stack%first => process%next call process%final () deallocate (process) end do stack%n = 0 end subroutine process_stack_clear @ %def process_stack_clear @ Finalizer. Clear and deallocate the variable list. <>= procedure :: final => process_stack_final <>= subroutine process_stack_final (object) class(process_stack_t), intent(inout) :: object call object%clear () if (associated (object%var_list)) then deallocate (object%var_list) end if end subroutine process_stack_final @ %def process_stack_final @ Output. The processes on the stack will be ordered LIFO, i.e., backwards. <>= procedure :: write => process_stack_write <>= recursive subroutine process_stack_write (object, unit, pacify) class(process_stack_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: pacify type(process_entry_t), pointer :: process integer :: u u = given_output_unit (unit) call write_separator (u, 2) select case (object%n) case (0) write (u, "(1x,A)") "Process stack: [empty]" call write_separator (u, 2) case default write (u, "(1x,A)") "Process stack:" process => object%first do while (associated (process)) call process%write (.false., u, pacify = pacify) process => process%next end do end select if (associated (object%next)) then write (u, "(1x,A)") "[Processes from context environment:]" call object%next%write (u, pacify) end if end subroutine process_stack_write @ %def process_stack_write @ The variable list is printed by a separate routine, since it should be linked to the global variable list, anyway. <>= procedure :: write_var_list => process_stack_write_var_list <>= subroutine process_stack_write_var_list (object, unit) class(process_stack_t), intent(in) :: object integer, intent(in), optional :: unit if (associated (object%var_list)) then call var_list_write (object%var_list, unit) end if end subroutine process_stack_write_var_list @ %def process_stack_write_var_list @ Short output. Since this is a stack, the default output ordering for each stack will be last-in, first-out. To enable first-in, first-out, which is more likely to be requested, there is an optional [[fifo]] argument. <>= procedure :: show => process_stack_show <>= recursive subroutine process_stack_show (object, unit, fifo) class(process_stack_t), intent(in) :: object integer, intent(in), optional :: unit logical, intent(in), optional :: fifo type(process_entry_t), pointer :: process logical :: reverse integer :: u, i, j u = given_output_unit (unit) reverse = .false.; if (present (fifo)) reverse = fifo select case (object%n) case (0) case default if (.not. reverse) then process => object%first do while (associated (process)) call process%show (u, verbose=.false.) process => process%next end do else do i = 1, object%n process => object%first do j = 1, object%n - i process => process%next end do call process%show (u, verbose=.false.) end do end if end select if (associated (object%next)) call object%next%show () end subroutine process_stack_show @ %def process_stack_show @ \subsection{Link} Link the current process stack to a global one. <>= procedure :: link => process_stack_link <>= subroutine process_stack_link (local_stack, global_stack) class(process_stack_t), intent(inout) :: local_stack type(process_stack_t), intent(in), target :: global_stack local_stack%next => global_stack end subroutine process_stack_link @ %def process_stack_link @ Initialize the process variable list and link the main variable list to it. <>= procedure :: init_var_list => process_stack_init_var_list <>= subroutine process_stack_init_var_list (stack, var_list) class(process_stack_t), intent(inout) :: stack type(var_list_t), intent(inout), optional :: var_list allocate (stack%var_list) if (present (var_list)) call var_list%link (stack%var_list) end subroutine process_stack_init_var_list @ %def process_stack_init_var_list @ Link the process variable list to a global variable list. <>= procedure :: link_var_list => process_stack_link_var_list <>= subroutine process_stack_link_var_list (stack, var_list) class(process_stack_t), intent(inout) :: stack type(var_list_t), intent(in), target :: var_list call stack%var_list%link (var_list) end subroutine process_stack_link_var_list @ %def process_stack_link_var_list @ \subsection{Push} We take a process pointer and push it onto the stack. The previous pointer is nullified. Subsequently, the process is `owned' by the stack and will be finalized when the stack is deleted. <>= procedure :: push => process_stack_push <>= subroutine process_stack_push (stack, process) class(process_stack_t), intent(inout) :: stack type(process_entry_t), intent(inout), pointer :: process process%next => stack%first stack%first => process process => null () stack%n = stack%n + 1 end subroutine process_stack_push @ %def process_stack_push @ Inverse: Remove the last process pointer in the list and return it. <>= procedure :: pop_last => process_stack_pop_last <>= subroutine process_stack_pop_last (stack, process) class(process_stack_t), intent(inout) :: stack type(process_entry_t), intent(inout), pointer :: process type(process_entry_t), pointer :: previous integer :: i select case (stack%n) case (:0) process => null () case (1) process => stack%first stack%first => null () stack%n = 0 case (2:) process => stack%first do i = 2, stack%n previous => process process => process%next end do previous%next => null () stack%n = stack%n - 1 end select end subroutine process_stack_pop_last @ %def process_stack_pop_last @ Initialize process variables for a given process ID, without setting values. <>= procedure :: init_result_vars => process_stack_init_result_vars <>= subroutine process_stack_init_result_vars (stack, id) class(process_stack_t), intent(inout) :: stack type(string_t), intent(in) :: id call var_list_init_num_id (stack%var_list, id) call var_list_init_process_results (stack%var_list, id) end subroutine process_stack_init_result_vars @ %def process_stack_init_result_vars @ Fill process variables with values. This is executed after the integration pass. Note: We set only integral and error. With multiple MCI records possible, the results for [[n_calls]], [[chi2]] etc. are not necessarily unique. (We might set the efficiency, though.) <>= procedure :: fill_result_vars => process_stack_fill_result_vars <>= subroutine process_stack_fill_result_vars (stack, id) class(process_stack_t), intent(inout) :: stack type(string_t), intent(in) :: id type(process_t), pointer :: process process => stack%get_process_ptr (id) if (associated (process)) then call var_list_init_num_id (stack%var_list, id, process%get_num_id ()) if (process%has_integral ()) then call var_list_init_process_results (stack%var_list, id, & integral = process%get_integral (), & error = process%get_error ()) end if else call msg_bug ("process_stack_fill_result_vars: unknown process ID") end if end subroutine process_stack_fill_result_vars @ %def process_stack_fill_result_vars @ If one of the result variables has a local image in [[var_list_local]], update the value there as well. <>= procedure :: update_result_vars => process_stack_update_result_vars <>= subroutine process_stack_update_result_vars (stack, id, var_list_local) class(process_stack_t), intent(inout) :: stack type(string_t), intent(in) :: id type(var_list_t), intent(inout) :: var_list_local call update ("integral(" // id // ")") call update ("error(" // id // ")") contains subroutine update (var_name) type(string_t), intent(in) :: var_name real(default) :: value if (var_list_local%contains (var_name, follow_link = .false.)) then value = stack%var_list%get_rval (var_name) call var_list_local%set_real (var_name, value, is_known = .true.) end if end subroutine update end subroutine process_stack_update_result_vars @ %def process_stack_update_result_vars @ \subsection{Data Access} Tell if a process exists. <>= procedure :: exists => process_stack_exists <>= function process_stack_exists (stack, id) result (flag) class(process_stack_t), intent(in) :: stack type(string_t), intent(in) :: id logical :: flag type(process_t), pointer :: process process => stack%get_process_ptr (id) flag = associated (process) end function process_stack_exists @ %def process_stack_exists @ Return a pointer to a process with specific ID. Look also at a linked stack, if necessary. <>= procedure :: get_process_ptr => process_stack_get_process_ptr <>= recursive function process_stack_get_process_ptr (stack, id) result (ptr) class(process_stack_t), intent(in) :: stack type(string_t), intent(in) :: id type(process_t), pointer :: ptr type(process_entry_t), pointer :: entry ptr => null () entry => stack%first do while (associated (entry)) if (entry%get_id () == id) then ptr => entry%process_t return end if entry => entry%next end do if (associated (stack%next)) ptr => stack%next%get_process_ptr (id) end function process_stack_get_process_ptr @ %def process_stack_get_process_ptr @ \subsection{Unit tests} Test module, followed by the corresponding implementation module. <<[[process_stacks_ut.f90]]>>= <> module process_stacks_ut use unit_tests use process_stacks_uti <> <> contains <> end module process_stacks_ut @ %def process_stacks_ut @ <<[[process_stacks_uti.f90]]>>= <> module process_stacks_uti <> use os_interface use sm_qcd use models use model_data use variables, only: var_list_t use process_libraries use rng_base use prc_test, only: prc_test_create_library use process, only: process_t use instances, only: process_instance_t use processes_ut, only: prepare_test_process use process_stacks use rng_base_ut, only: rng_test_factory_t <> <> contains <> end module process_stacks_uti @ %def process_stacks_uti @ API: driver for the unit tests below. <>= public :: process_stacks_test <>= subroutine process_stacks_test (u, results) integer, intent(in) :: u type(test_results_t), intent(inout) :: results <> end subroutine process_stacks_test @ %def process_stacks_test @ \subsubsection{Write an empty process stack} The most trivial test is to write an uninitialized process stack. <>= call test (process_stacks_1, "process_stacks_1", & "write an empty process stack", & u, results) <>= public :: process_stacks_1 <>= subroutine process_stacks_1 (u) integer, intent(in) :: u type(process_stack_t) :: stack write (u, "(A)") "* Test output: process_stacks_1" write (u, "(A)") "* Purpose: display an empty process stack" write (u, "(A)") call stack%write (u) write (u, "(A)") write (u, "(A)") "* Test output end: process_stacks_1" end subroutine process_stacks_1 @ %def process_stacks_1 @ \subsubsection{Fill a process stack} Fill a process stack with two (identical) processes. <>= call test (process_stacks_2, "process_stacks_2", & "fill a process stack", & u, results) <>= public :: process_stacks_2 <>= subroutine process_stacks_2 (u) integer, intent(in) :: u type(process_stack_t) :: stack type(process_library_t), target :: lib type(string_t) :: libname type(string_t) :: procname type(os_data_t) :: os_data type(model_t), target :: model type(var_list_t) :: var_list type(process_entry_t), pointer :: process => null () write (u, "(A)") "* Test output: process_stacks_2" write (u, "(A)") "* Purpose: fill a process stack" write (u, "(A)") write (u, "(A)") "* Build, initialize and store two test processes" write (u, "(A)") libname = "process_stacks2" procname = libname call os_data%init () call prc_test_create_library (libname, lib) call model%init_test () call var_list%append_string (var_str ("$run_id")) call var_list%append_log (var_str ("?alphas_is_fixed"), .true.) call var_list%append_int (var_str ("seed"), 0) allocate (process) call var_list%set_string & (var_str ("$run_id"), var_str ("run1"), is_known=.true.) call process%init (procname, lib, os_data, model, var_list) call stack%push (process) allocate (process) call var_list%set_string & (var_str ("$run_id"), var_str ("run2"), is_known=.true.) call process%init (procname, lib, os_data, model, var_list) call stack%push (process) call stack%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call stack%final () call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: process_stacks_2" end subroutine process_stacks_2 @ %def process_stacks_2 @ \subsubsection{Fill a process stack} Fill a process stack with two (identical) processes. <>= call test (process_stacks_3, "process_stacks_3", & "process variables", & u, results) <>= public :: process_stacks_3 <>= subroutine process_stacks_3 (u) integer, intent(in) :: u type(process_stack_t) :: stack type(model_t), target :: model type(string_t) :: procname type(process_entry_t), pointer :: process => null () type(process_instance_t), target :: process_instance write (u, "(A)") "* Test output: process_stacks_3" write (u, "(A)") "* Purpose: setup process variables" write (u, "(A)") write (u, "(A)") "* Initialize process variables" write (u, "(A)") procname = "processes_test" call model%init_test () write (u, "(A)") "* Initialize process variables" write (u, "(A)") call stack%init_var_list () call stack%init_result_vars (procname) call stack%write_var_list (u) write (u, "(A)") write (u, "(A)") "* Build and integrate a test process" write (u, "(A)") allocate (process) call prepare_test_process (process%process_t, process_instance, model) call process_instance%integrate (1, 1, 1000) call process_instance%final () call process%final_integration (1) call stack%push (process) write (u, "(A)") "* Fill process variables" write (u, "(A)") call stack%fill_result_vars (procname) call stack%write_var_list (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call stack%final () call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: process_stacks_3" end subroutine process_stacks_3 @ %def process_stacks_3 @ \subsubsection{Linked a process stack} Fill two process stack, linked to each other. <>= call test (process_stacks_4, "process_stacks_4", & "linked stacks", & u, results) <>= public :: process_stacks_4 <>= subroutine process_stacks_4 (u) integer, intent(in) :: u type(process_library_t), target :: lib type(process_stack_t), target :: stack1, stack2 type(model_t), target :: model type(string_t) :: libname type(string_t) :: procname1, procname2 type(os_data_t) :: os_data type(process_entry_t), pointer :: process => null () write (u, "(A)") "* Test output: process_stacks_4" write (u, "(A)") "* Purpose: link process stacks" write (u, "(A)") write (u, "(A)") "* Initialize process variables" write (u, "(A)") libname = "process_stacks_4_lib" procname1 = "process_stacks_4a" procname2 = "process_stacks_4b" call os_data%init () write (u, "(A)") "* Initialize first process" write (u, "(A)") call prc_test_create_library (procname1, lib) call model%init_test () allocate (process) call process%init (procname1, lib, os_data, model) call stack1%push (process) write (u, "(A)") "* Initialize second process" write (u, "(A)") call stack2%link (stack1) call prc_test_create_library (procname2, lib) allocate (process) call process%init (procname2, lib, os_data, model) call stack2%push (process) write (u, "(A)") "* Show linked stacks" write (u, "(A)") call stack2%write (u) write (u, "(A)") write (u, "(A)") "* Cleanup" call stack2%final () call stack1%final () call model%final () write (u, "(A)") write (u, "(A)") "* Test output end: process_stacks_4" end subroutine process_stacks_4 @ %def process_stacks_4 @ Index: trunk/ChangeLog =================================================================== --- trunk/ChangeLog (revision 8753) +++ trunk/ChangeLog (revision 8754) @@ -1,2263 +1,2267 @@ ChangeLog -- Summary of changes to the WHIZARD package Use svn log to see detailed changes. Version 3.0.1+ +2021-10-21 + NLO (QCD) differential distributions supported for full + lepton collider setup: polarization, QED ISR, beamstrahlung + 2021-10-15 SINDARIN now has a sum and product function of expressions, SINDARIN supports observables defined on full (sub)events First application: transverse mass Bug fix: 2HDM did not allow H+, H- as external particles 2021-10-14 CT18 PDFs included (NLO, NNLO) 2021-09-30 Bug fix: keep non-recombined photons in the event record 2021-09-13 Modular NLO event generation with real partition 2021-08-20 Bug fix: correctly reading in NLO fixed order events 2021-08-06 Generalize optional partitioning of the NLO real phase space ################################################################## 2021-07-08 RELEASE: version 3.0.1 2021-07-06 MPI parallelization now comes with two incarnations: - standard MPI parallelization ("simple", default) - MPI with load balancer ("load") 2021-07-05 Bug fix for C++17 default compilers w/ HepMC3/ROOT interface 2021-07-02 Improvement for POWHEG matching: - implement massless recoil case - enable reading in existing POWHEG grids - support kinematic cuts at generator level 2021-07-01 Distinguish different cases of photons in NLO EW corrections 2021-06-21 Option to keep negative PDF entries or set them zero 2021-05-31 Full LCIO MC production files can be properly recasted 2021-05-24 Use defaults for UFO models without propagators.py 2021-05-21 Bug fix: prevent invalid code for UFO models containing hyphens 2021-05-20 UFO files with scientific notation float constants allowed UFO files: max. n-arity of vertices bound by process multiplicity ################################################################## 2021-04-27 RELEASE: version 3.0.0 2021-04-20 Minimal required OCaml version is now 4.05.0. Bug fix for tau polarization from stau decays 2021-04-19 NLO EW splitting functions and collinear remnants completed Photon recombination implemented 2021-04-14 Bug fix for vertices/status codes with HepMC2/3 event format 2021-04-08 Correct Lorentz statistics for UFO model with Majorana fermions 2021-04-06 Bug fix for rare script failure in system_dependencies.f90.in Kappa factor for quartic Higgs coupling in SM_ac(_CKM) model 2021-04-04 Support for UFO extensions in SMEFTSim 3.0 2021-02-25 Enable VAMP and VAMP2 channel equivalences for NLO integrations 2021-02-04 Bug fix if user does not set a prefix at configuration 2020-12-10 Generalize NLO calculations to non-CMS lab frames 2020-12-08 Bug fix in expanded p-wave form factor for top threshold 2020-12-06 Patch for macOS Big Sur shared library handling due to libtool; the patch also demands gcc/gfortran 11.0/10.3/9.4/8.5 2020-12-04 O'Mega only inserts non-vanishing couplings from UFO models 2020-11-21 Bug fix for fractional hypercharges in UFO models 2020-11-11 Enable PYTHIA6 settings for eh collisions (enable-pythia6_eh) 2020-11-09 Correct flavor assignment for NLO fixed-order events 2020-11-05 Bug fix for ISR handler not working with unstable particles 2020-10-08 Bug fix in LHAPDF interface for photon PDFs 2020-10-07 Bug fix for structure function setup with asymmetric beams 2020-10-02 Python/Cython layer for WHIZARD API 2020-09-30 Allow mismatches of Python and name attributes in UFO models 2020-09-26 Support for negative PDG particles from certain UFO models 2020-09-24 Allow for QNUMBERS blocks in BSM SLHA files 2020-09-22 Full support for compilation with clang(++) on Darwin/macOS More documentation in the manual Minor clean-ups 2020-09-16 Bug fix enables reading LCIO events with LCIO v2.15+ ################################################################## 2020-09-16 RELEASE: version 2.8.5 2020-09-11 Bug fix for H->tau tau transverse polarization with PYTHIA6 (thanks to Junping Tian / Akiya Miyamoto) 2020-09-09 Fix a long standing bug (since 2.0) in the calculation of color factors when particles of different color were combined in a particle class. NB: O'Mega never produced a wrong number, it only declared all processes as invalid. 2020-09-08 Enable Openloops matrix element equivalences for optimization 2020-09-02 Compatibility fix for PYTHIA v8.301+ interface 2020-09-01 Support exclusive jet clustering in ee for Fastjet interface ################################################################## 2020-08-30 RELEASE: version 3.0.0_beta 2020-08-27 Major revision of NLO distributions and events for processes with structure functions: - Use parton momenta/flavors (instead of beams) for events - Bug fix for Lorentz boosts and Lorentz frames of momenta - Bug fix: apply cuts to virtual NLO component in correct frame - Correctly assign ISR radiation momenta in data structures - Refactoring on quantum numbers for NLO event data structures - Functional tests for hadron collider NLO distributions - many minor bug fixes regarding NLO hadron collider physics 2020-08-11 Bug fix for linking problem with OpenMPI 2020-08-07 New WHIZARD API: WHIZARD can be externally linked as a library, added examples for Fortran, C, C++ programs ################################################################## 2020-07-08 RELEASE: version 2.8.4 2020-07-07 Bug fix: steering of UFO Majorana models from WHIZARD ################################################################## 2020-07-06 Combined integration also for hadron collider processes at NLO 2020-07-05 Bug fix: correctly steer e+e- FastJet clustering algorithms Major revision of NLO differential distributions and events: - Correctly assign quantum numbers to NLO fixed-order events - Correctly assign weights to NLO fixed-order events for combined simulation - Cut all NLO fixed-order subevents in event groups individually - Only allow "sigma" normalization for NLO fixed-order events - Use correct PDF setup for NLO counter events - Several technical fixes and updates of the NLO testsuite ################################################################## 2020-07-03 RELEASE: version 2.8.3 2020-07-02 Feature-complete UFO implementation for Majorana fermions 2020-06-22 Running width scheme supported for O'Mega matrix elements 2020-06-20 Adding H-s-s coupling to SM_Higgs(_CKM) models 2020-06-17 Completion of ILC 2->6 fermion extended test suite 2020-06-15 Bug fix: PYTHIA6/Tauola, correctly assign tau spins for stau decays 2020-06-09 Bug fix: correctly update calls for additional VAMP/2 iterations Bug fix: correct assignment for tau spins from PYTHIA6 interface 2020-06-04 Bug fix: cascades2 tree merge with empty subtree(s) 2020-05-31 Switch $epa_mode for different EPA implementations 2020-05-26 Bug fix: spin information transferred for resonance histories 2020-04-13 HepMC: correct weighted events for non-xsec event normalizations 2020-04-04 Improved HepMC3 interface: HepMC3 Root/RootTree interface 2020-03-24 ISR: Fix on-shell kinematics for events with ?isr_handler=true (set ?isr_handler_keep_mass=false for old behavior) 2020-03-11 Beam masses are correctly passed to hard matrix element for CIRCE2 EPA with polarized beams: double-counting corrected ################################################################## 2020-03-03 RELEASE: version 3.0.0_alpha 2020-02-25 Bug fix: Scale and alphas can be retrieved from internal event format to external formats 2020-02-17 Bug fix: ?keep_failed_events now forces output of actual event data Bug fix: particle-set reconstruction (rescanning events w/o radiation) 2020-01-28 Bug fix for left-over EPA parameter epa_e_max (replaced by epa_q_max) 2020-01-23 Bug fix for real components of NLO QCD 2->1 processes 2020-01-22 Bug fix: correct random number sequencing during parallel MPI event generation with rng_stream 2020-01-21 Consistent distribution of events during parallel MPI event generation 2020-01-20 Bug fix for configure setup for automake v1.16+ 2020-01-18 General SLHA parameter files for UFO models supported 2020-01-08 Bug fix: correctly register RECOLA processes with flavor sums 2019-12-19 Support for UFO customized propagators O'Mega unit tests for fermion-number violating interactions 2019-12-10 For distribution building: check for graphviz/dot version 2.40 or newer 2019-11-21 Bug fix: alternate setups now work correctly Infrastructure for accessing alpha_QED event-by-event Guard against tiny numbers that break ASCII event output Enable inverse hyperbolic functions as SINDARIN observables Remove old compiler bug workarounds 2019-11-20 Allow quoted -e argument, implemented -f option 2019-11-19 Bug fix: resonance histories now work also with UFO models Fix in numerical precision of ASCII VAMP2 grids 2019-11-06 Add squared matrix elements to the LCIO event header 2019-11-05 Do not include RNG state in MD5 sum for CIRCE1/2 2019-11-04 Full CIRCE2 ILC 250 and 500 GeV beam spectra added Minor update on LCIO event header information 2019-10-30 NLO QCD for final states completed When using Openloops, v2.1.1+ mandatory 2019-10-25 Binary grid files for VAMP2 integrator ################################################################## 2019-10-24 RELEASE: version 2.8.2 2019-10-20 Bug fix for HepMC linker flags 2019-10-19 Support for spin-2 particles from UFO files 2019-09-27 LCIO event format allows rescan and alternate weights 2019-09-24 Compatibility fix for OCaml v4.08.0+ ################################################################## 2019-09-21 RELEASE: version 2.8.1 2019-09-19 Carriage return characters in UFO models can be parsed Mathematica symbols in UFO models possible Unused/undefined parameters in UFO models handled 2019-09-13 New extended NLO test suite for ee and pp processes 2019-09-09 Photon isolation (separation of perturbative and fragmentation part a la Frixione) 2019-09-05 Major progress on NLO QCD for hadron collisions: - correctly assign flavor structures for alpha regions - fix crossing of particles for initial state splittings - correct assignment for PDF factors for real subtractions - fix kinematics for collinear splittings - bug fix for integrated virtual subtraction terms 2019-09-03 b and c jet selection in cuts and analysis 2019-08-27 Support for Intel MPI 2019-08-20 Complete (preliminary) HepMC3 support (incl. backwards HepMC2 write/read mode) 2019-08-08 Bug fix: handle carriage returns in UFO files (non-Unix OS) ################################################################## 2019-08-07 RELEASE: version 2.8.0 2019-07-31 Complete WHIZARD UFO interface: - general Lorentz structures - matrix element support for general color factors - missing features: Majorana fermions and SLHA 2019-07-20 Make WHIZARD compatible with OCaml 4.08.0+ 2019-07-19 Fix version testing for LHAPDF 6.2.3 and newer Minimal required OCaml version is now 4.02.3. 2019-04-18 Correctly generate ordered FKS tuples for alpha regions from all possible underlying Born processes 2019-04-08 Extended O'Mega/Recola matrix element test suite 2019-03-29 Correct identical particle symmetry factors for FKS subtraction 2019-03-28 Correct assertion of spin-correlated matrix elements for hadron collisions 2019-03-27 Bug fix for cut-off parameter delta_i for collinear plus/minus regions ################################################################## 2019-03-27 RELEASE: version 2.7.1 2019-02-19 Further infrastructure for HepMC3 interface (v3.01.00) 2019-02-07 Explicit configure option for using debugging options Bug fix for performance by removing unnecessary debug operations 2019-01-29 Bug fix for DGLAP remnants with cut-off parameter delta_i 2019-01-24 Radiative decay neu2 -> neu1 A added to MSSM_Hgg model ################################################################## 2019-01-21 RELEASE: version 2.7.0 2018-12-18 Support RECOLA for integrated und unintegrated subtractions 2018-12-11 FCNC top-up sector in model SM_top_anom 2018-12-05 Use libtirpc instead of SunRPC on Arch Linux etc. 2018-11-30 Display rescaling factor for weighted event samples with cuts 2018-11-29 Reintroduce check against different masses in flavor sums Bug fix for wrong couplings in the Littlest Higgs model(s) 2018-11-22 Bug fix for rescanning events with beam structure 2018-11-09 Major refactoring of internal process data 2018-11-02 PYTHIA8 interface 2018-10-29 Flat phase space parametrization with RAMBO (on diet) implemented 2018-10-17 Revise extended test suite 2018-09-27 Process container for RECOLA processes 2018-09-15 Fixes by M. Berggren for PYTHIA6 interface 2018-09-14 First fixes after HepForge modernization ################################################################## 2018-08-23 RELEASE: version 2.6.4 2018-08-09 Infrastructure to check colored subevents 2018-07-10 Infrastructure for running WHIZARD in batch mode 2018-07-04 MPI available from distribution tarball 2018-06-03 Support Intel Fortran Compiler under MAC OS X 2018-05-07 FKS slicing parameter delta_i (initial state) implementend 2018-05-03 Refactor structure function assignment for NLO 2018-05-02 FKS slicing parameter xi_cut, delta_0 implemented 2018-04-20 Workspace subdirectory for process integration (grid/phs files) Packing/unpacking of files at job end/start Exporting integration results from scan loops 2018-04-13 Extended QCD NLO test suite 2018-04-09 Bug fix for Higgs Singlet Extension model 2018-04-06 Workspace subdirectory for process generation and compilation --job-id option for creating job-specific names 2018-03-20 Bug fix for color flow matching in hadron collisions with identical initial state quarks 2018-03-08 Structure functions quantum numbers correctly assigned for NLO 2018-02-24 Configure setup includes 'pgfortran' and 'flang' 2018-02-21 Include spin-correlated matrix elements in interactions 2018-02-15 Separate module for QED ISR structure functions ################################################################## 2018-02-10 RELEASE: version 2.6.3 2018-02-08 Improvements in memory management for PS generation 2018-01-31 Partial refactoring: quantum number assigment NLO Initial-state QCD splittings for hadron collisions 2018-01-25 Bug fix for weighted events with VAMP2 2018-01-17 Generalized interface for Recola versions 1.3+ and 2.1+ 2018-01-15 Channel equivalences also for VAMP2 integrator 2018-01-12 Fix for OCaml compiler 4.06 (and newer) 2017-12-19 RECOLA matrix elements with flavor sums can be integrated 2017-12-18 Bug fix for segmentation fault in empty resonance histories 2017-12-16 Fixing a bug in PYTHIA6 PYHEPC routine by omitting CMShowers from transferral between PYTHIA and WHIZARD event records 2017-12-15 Event index for multiple processes in event file correct ################################################################## 2017-12-13 RELEASE: version 2.6.2 2017-12-07 User can set offset in event numbers 2017-11-29 Possibility to have more than one RECOLA process in one file 2017-11-23 Transversal/mixed (and unitarized) dim-8 operators 2017-11-16 epa_q_max replaces epa_e_max (trivial factor 2) 2017-11-15 O'Mega matrix element compilation silent now 2017-11-14 Complete expanded P-wave form factor for top threshold 2017-11-10 Incoming particles can be accessed in SINDARIN 2017-11-08 Improved handling of resonance insertion, additional parameters 2017-11-04 Added Higgs-electron coupling (SM_Higgs) ################################################################## 2017-11-03 RELEASE: version 2.6.1 2017-10-20 More than 5 NLO components possible at same time 2017-10-19 Gaussian cutoff for shower resonance matching 2017-10-12 Alternative (more efficient) method to generate phase space file 2017-10-11 Bug fix for shower resonance histories for processes with multiple components 2017-09-25 Bug fix for process libraries in shower resonance histories 2017-09-21 Correctly generate pT distribution for EPA remnants 2017-09-20 Set branching ratios for unstable particles also by hand 2017-09-14 Correctly generate pT distribution for ISR photons ################################################################## 2017-09-08 RELEASE: version 2.6.0 2017-09-05 Bug fix for initial state NLO QCD flavor structures Real and virtual NLO QCD hadron collider processes work with internal interactions 2017-09-04 Fully validated MPI integration and event generation 2017-09-01 Resonance histories for shower: full support Bug fix in O'Mega model constraints O'Mega allows to output a parsable form of the DAG 2017-08-24 Resonance histories in events for transferral to parton shower (e.g. in ee -> jjjj) 2017-08-01 Alpha version of HepMC v3 interface (not yet really functional) 2017-07-31 Beta version for RECOLA OLP support 2017-07-06 Radiation generator fix for LHC processes 2017-06-30 Fix bug for NLO with structure functions and/or polarization 2017-06-23 Collinear limit for QED corrections works 2017-06-17 POWHEG grids generated already during integration 2017-06-12 Soft limit for QED corrections works 2017-05-16 Beta version of full MPI parallelization (VAMP2) Check consistency of POWHEG grid files Logfile config-summary.log for configure summary 2017-05-12 Allow polarization in top threshold 2017-05-09 Minimal demand automake 1.12.2 Silent rules for make procedures 2017-05-07 Major fix for POWHEG damping Correctly initialize FKS ISR phasespace ################################################################## 2017-05-06 RELEASE: version 2.5.0 2017-05-05 Full UFO support (SM-like models) Fixed-beam ISR FKS phase space 2017-04-26 QED splittings in radiation generator 2017-04-10 Retire deprecated O'Mega vertex cache files ################################################################## 2017-03-24 RELEASE: version 2.4.1 2017-03-16 Distinguish resonance charge in phase space channels Keep track of resonance histories in phase space Complex mass scheme default for OpenLoops amplitudes 2017-03-13 Fix helicities for polarized OpenLoops calculations 2017-03-09 Possibility to advance RNG state in rng_stream 2017-03-04 General setup for partitioning real emission phase space 2017-03-06 Bug fix on rescan command for converting event files 2017-02-27 Alternative multi-channel VEGAS implementation VAMP2: serial backbone for MPI setup Smoothstep top threshold matching 2017-02-25 Single-beam structure function with s-channel mapping supported Safeguard against invalid process libraries 2017-02-16 Radiation generator for photon emission 2017-02-10 Fixes for NLO QCD processes (color correlations) 2017-01-16 LCIO variable takes precedence over LCIO_DIR 2017-01-13 Alternative random number generator rng_stream (cf. L'Ecuyer et al.) 2017-01-01 Fix for multi-flavor BLHA tree matrix elements 2016-12-31 Grid path option for VAMP grids 2016-12-28 Alpha version of Recola OLP support 2016-12-27 Dalitz plots for FKS phase space 2016-12-14 NLO multi-flavor events possible 2016-12-09 LCIO event header information added 2016-12-02 Alpha version of RECOLA interface Bug fix for generator status in LCIO ################################################################## 2016-11-28 RELEASE: version 2.4.0 2016-11-24 Bug fix for OpenLoops interface: EW scheme is set by WHIZARD Bug fixes for top threshold implementation 2016-11-11 Refactoring of dispatching 2016-10-18 Bug fix for LCIO output 2016-10-10 First implementation for collinear soft terms 2016-10-06 First full WHIZARD models from UFO files 2016-10-05 WHIZARD does not support legacy gcc 4.7.4 any longer 2016-09-30 Major refactoring of process core and NLO components 2016-09-23 WHIZARD homogeneous entity: discarding subconfigures for CIRCE1/2, O'Mega, VAMP subpackages; these are reconstructable by script projectors 2016-09-06 Introduce main configure summary 2016-08-26 Fix memory leak in event generation ################################################################## 2016-08-25 RELEASE: version 2.3.1 2016-08-19 Bug fix for EW-scheme dependence of gluino propagators 2016-08-01 Beta version of complex mass scheme support 2016-07-26 Fix bug in POWHEG damping for the matching ################################################################## 2016-07-21 RELEASE: version 2.3.0 2016-07-20 UFO file support (alpha version) in O'Mega 2016-07-13 New (more) stable of WHIZARD GUI Support for EW schemes for OpenLoops Factorized NLO top decays for threshold model 2016-06-15 Passing factorization scale to PYTHIA6 Adding charge and neutral observables 2016-06-14 Correcting angular distribution/tweaked kinematics in non-collinear structure functions splittings 2016-05-10 Include (Fortran) TAUOLA/PHOTOS for tau decays via PYTHIA6 (backwards validation of LC CDR/TDR samples) 2016-04-27 Within OpenLoops virtuals: support for Collier library 2016-04-25 O'Mega vertex tables only loaded at first usage 2016-04-21 New CJ15 PDF parameterizations added 2016-04-21 Support for hadron collisions at NLO QCD 2016-04-05 Support for different (parameter) schemes in model files 2016-03-31 Correct transferral of lifetime/vertex from PYTHIA/TAUOLA into the event record 2016-03-21 New internal implementation of polarization via Bloch vectors, remove pointer constructions 2016-03-13 Extension of cascade syntax for processes: exclude propagators/vertices etc. possible 2016-02-24 Full support for OpenLoops QCD NLO matrix elements, inclusion in test suite 2016-02-12 Substantial progress on QCD NLO support 2016-02-02 Automated resonance mapping for FKS subtraction 2015-12-17 New BSM model WZW for diphoton resonances ################################################################## 2015-11-22 RELEASE: version 2.2.8 2015-11-21 Bug fix for fixed-order NLO events 2015-11-20 Anomalous FCNC top-charm vertices 2015-11-19 StdHEP output via HEPEVT/HEPEV4 supported 2015-11-18 Full set of electroweak dim-6 operators included 2015-10-22 Polarized one-loop amplitudes supported 2015-10-21 Fixes for event formats for showered events 2015-10-14 Callback mechanism for event output 2015-09-22 Bypass matrix elements in pure event sample rescans StdHep frozen final version v5.06.01 included internally 2015-09-21 configure option --with-precision to demand 64bit, 80bit, or 128bit Fortran and bind C precision types 2015-09-07 More extensive tests of NLO infrastructure and POWHEG matching 2015-09-01 NLO decay infrastructure User-defined squared matrix elements Inclusive FastJet algorithm plugin Numerical improvement for small boosts ################################################################## 2015-08-11 RELEASE: version 2.2.7 2015-08-10 Infrastructure for damped POWHEG Massive emitters in POWHEG Born matrix elements via BLHA GoSam filters via SINDARIN Minor running coupling bug fixes Fixed-order NLO events 2015-08-06 CT14 PDFs included (LO, NLO, NNLL) 2015-07-07 Revalidation of ILC WHIZARD-PYTHIA event chain Extended test suite for showered events Alpha version of massive FSR for POWHEG 2015-06-09 Fix memory leak in interaction for long cascades Catch mismatch between beam definition and CIRCE2 spectrum 2015-06-08 Automated POWHEG matching: beta version Infrastructure for GKS matching Alpha version of fixed-order NLO events CIRCE2 polarization averaged spectra with explicitly polarized beams 2015-05-12 Abstract matching type: OO structure for matching/merging 2015-05-07 Bug fix in event record WHIZARD-PYTHIA6 transferral Gaussian beam spectra for lepton colliders ################################################################## 2015-05-02 RELEASE: version 2.2.6 2015-05-01 Models for (unitarized) tensor resonances in VBS 2015-04-28 Bug fix in channel weights for event generation. 2015-04-18 Improved event record transfer WHIZARD/PYTHIA6 2015-03-19 POWHEG matching: alpha version ################################################################## 2015-02-27 RELEASE: version 2.2.5 2015-02-26 Abstract types for quantum numbers 2015-02-25 Read-in of StdHEP events, self-tests 2015-02-22 Bug fix for mother-daughter relations in showered/hadronized events 2015-02-20 Projection on polarization in intermediate states 2015-02-13 Correct treatment of beam remnants in event formats (also LC remnants) ################################################################## 2015-02-06 RELEASE: version 2.2.4 2015-02-06 Bug fix in event output 2015-02-05 LCIO event format supported 2015-01-30 Including state matrices in WHIZARD's internal IO Versioning for WHIZARD's internal IO Libtool update from 2.4.3 to 2.4.5 LCIO event output (beta version) 2015-01-27 Progress on NLO integration Fixing a bug for multiple processes in a single event file when using beam event files 2015-01-19 Bug fix for spin correlations evaluated in the rest frame of the mother particle 2015-01-17 Regression fix for statically linked processes from SARAH and FeynRules 2015-01-10 NLO: massive FKS emitters supported (experimental) 2015-01-06 MMHT2014 PDF sets included 2015-01-05 Handling mass degeneracies in auto_decays 2014-12-19 Fixing bug in rescan of event files ################################################################## 2014-11-30 RELEASE: version 2.2.3 2014-11-29 Beta version of LO continuum/NLL-threshold matched top threshold model for e+e- physics 2014-11-28 More internal refactoring: disentanglement of module dependencies 2014-11-21 OVM: O'Mega Virtual Machine, bytecode instructions instead of compiled Fortran code 2014-11-01 Higgs Singlet extension model included 2014-10-18 Internal restructuring of code; half-way WHIZARD main code file disassembled 2014-07-09 Alpha version of NLO infrastructure ################################################################## 2014-07-06 RELEASE: version 2.2.2 2014-07-05 CIRCE2: correlated LC beam spectra and GuineaPig Interface to LC machine parameters 2014-07-01 Reading LHEF for decayed/factorized/showered/ hadronized events 2014-06-25 Configure support for GoSAM/Ninja/Form/QGraf 2014-06-22 LHAPDF6 interface 2014-06-18 Module for automatic generation of radiation and loop infrastructure code 2014-06-11 Improved internal directory structure ################################################################## 2014-06-03 RELEASE: version 2.2.1 2014-05-30 Extensions of internal PDG arrays 2014-05-26 FastJet interface 2014-05-24 CJ12 PDFs included 2014-05-20 Regression fix for external models (via SARAH or FeynRules) ################################################################## 2014-05-18 RELEASE: version 2.2.0 2014-04-11 Multiple components: inclusive process definitions, syntax: process A + B + ... 2014-03-13 Improved PS mappings for e+e- ISR ILC TDR and CLIC spectra included in CIRCE1 2014-02-23 New models: AltH w\ Higgs for exclusion purposes, SM_rx for Dim 6-/Dim-8 operators, SSC for general strong interactions (w/ Higgs), and NoH_rx (w\ Higgs) 2014-02-14 Improved s-channel mapping, new on-shell production mapping (e.g. Drell-Yan) 2014-02-03 PRE-RELEASE: version 2.2.0_beta 2014-01-26 O'Mega: Feynman diagram generation possible (again) 2013-12-16 HOPPET interface for b parton matching 2013-11-15 PRE-RELEASE: version 2.2.0_alpha-4 2013-10-27 LHEF standards 1.0/2.0/3.0 implemented 2013-10-15 PRE-RELEASE: version 2.2.0_alpha-3 2013-10-02 PRE-RELEASE: version 2.2.0_alpha-2 2013-09-25 PRE-RELEASE: version 2.2.0_alpha-1 2013-09-12 PRE-RELEASE: version 2.2.0_alpha 2013-09-03 General 2HDM implemented 2013-08-18 Rescanning/recalculating events 2013-06-07 Reconstruction of complete event from 4-momenta possible 2013-05-06 Process library stacks 2013-05-02 Process stacks 2013-04-29 Single-particle phase space module 2013-04-26 Abstract interface for random number generator 2013-04-24 More object-orientation on modules Midpoint-rule integrator 2013-04-05 Object-oriented integration and event generation 2013-03-12 Processes recasted object-oriented: MEs, scales, structure functions First infrastructure for general Lorentz structures 2013-01-17 Object-orientated reworking of library and process core, more variable internal structure, unit tests 2012-12-14 Update Pythia version to 6.4.27 2012-12-04 Fix the phase in HAZ vertices 2012-11-21 First O'Mega unit tests, some infrastructure 2012-11-13 Bug fix in anom. HVV Lorentz structures ################################################################## 2012-09-18 RELEASE: version 2.1.1 2012-09-11 Model MSSM_Hgg with Hgg and HAA vertices 2012-09-10 First version of implementation of multiple interactions in WHIZARD 2012-09-05 Infrastructure for internal CKKW matching 2012-09-02 C, C++, Python API 2012-07-19 Fixing particle numbering in HepMC format ################################################################## 2012-06-15 RELEASE: version 2.1.0 2012-06-14 Analytical and kT-ordered shower officially released PYTHIA interface officially released 2012-05-09 Intrisince PDFs can be used for showering 2012-05-04 Anomalous Higgs couplings a la hep-ph/9902321 ################################################################## 2012-03-19 RELEASE: version 2.0.7 2012-03-15 Run IDs are available now More event variables in analysis Modified raw event format (compatibility mode exists) 2012-03-12 Bug fix in decay-integration order MLM matching steered completely internally now 2012-03-09 Special phase space mapping for narrow resonances decaying to 4-particle final states with far off-shell intermediate states Running alphas from PDF collaborations with builtin PDFs 2012-02-16 Bug fix in cascades decay infrastructure 2012-02-04 WHIZARD documentation compatible with TeXLive 2011 2012-02-01 Bug fix in FeynRules interface with --prefix flag 2012-01-29 Bug fix with name clash of O'Mega variable names 2012-01-27 Update internal PYTHIA to version 6.4.26 Bug fix in LHEF output 2012-01-21 Catching stricter automake 1.11.2 rules 2011-12-23 Bug fix in decay cascade setup 2011-12-20 Bug fix in helicity selection rules 2011-12-16 Accuracy goal reimplemented 2011-12-14 WHIZARD compatible with TeXLive 2011 2011-12-09 Option --user-target added ################################################################## 2011-12-07 RELEASE: version 2.0.6 2011-12-07 Bug fixes in SM_top_anom Added missing entries to HepMC format 2011-12-06 Allow to pass options to O'Mega Bug fix for HEPEVT block for showered/hadronized events 2011-12-01 Reenabled user plug-in for external code for cuts, structure functions, routines etc. 2011-11-29 Changed model SM_Higgs for Higgs phenomenology 2011-11-25 Supporting a Y, (B-L) Z' model 2011-11-23 Make WHIZARD compatible for MAC OS X Lion/XCode 4 2011-09-25 WHIZARD paper published: Eur.Phys.J. C71 (2011) 1742 2011-08-16 Model SM_QCD: QCD with one EW insertion 2011-07-19 Explicit output channel for dvips avoids printing 2011-07-10 Test suite for WHIZARD unit tests 2011-07-01 Commands for matrix element tests More OpenMP parallelization of kinematics Added unit tests 2011-06-23 Conversion of CIRCE2 from F77 to F90, major clean-up 2011-06-14 Conversion of CIRCE1 from F77 to F90 2011-06-10 OpenMP parallelization of channel kinematics (by Matthias Trudewind) 2011-05-31 RELEASE: version 1.97 2011-05-24 Minor bug fixes: update grids and elsif statement. ################################################################## 2011-05-10 RELEASE: version 2.0.5 2011-05-09 Fixed bug in final state flavor sums Minor improvements on phase-space setup 2011-05-05 Minor bug fixes 2011-04-15 WHIZARD as a precompiled 64-bit binary available 2011-04-06 Wall clock instead of cpu time for time estimates 2011-04-05 Major improvement on the phase space setup 2011-04-02 OpenMP parallelization for helicity loop in O'Mega matrix elements 2011-03-31 Tools for relocating WHIZARD and use in batch environments 2011-03-29 Completely static builds possible, profiling options 2011-03-28 Visualization of integration history 2011-03-27 Fixed broken K-matrix implementation 2011-03-23 Including the GAMELAN manual in the distribution 2011-01-26 WHIZARD analysis can handle hadronized event files 2011-01-17 MSTW2008 and CT10 PDF sets included 2010-12-23 Inclusion of NMSSM with Hgg couplings 2010-12-21 Advanced options for integration passes 2010-11-16 WHIZARD supports CTEQ6 and possibly other PDFs directly; data files included in the distribution ################################################################## 2010-10-26 RELEASE: version 2.0.4 2010-10-06 Bug fix in MSSM implementation 2010-10-01 Update to libtool 2.4 2010-09-29 Support for anomalous top couplings (form factors etc.) Bug fix for running gauge Yukawa SUSY couplings 2010-09-28 RELEASE: version 1.96 2010-09-21 Beam remnants and pT spectra for lepton collider re-enabled Restructuring subevt class 2010-09-16 Shower and matching are disabled by default PYTHIA as a conditional on these two options 2010-09-14 Possibility to read in beam spectra re-enabled (e.g. Guinea Pig) 2010-09-13 Energy scan as (pseudo-) structure functions re-implemented 2010-09-10 CIRCE2 included again in WHIZARD 2 and validated 2010-09-02 Re-implementation of asymmetric beam energies and collision angles, e-p collisions work, inclusion of a HERA DIS test case ################################################################## 2010-10-18 RELEASE: version 2.0.3 2010-08-08 Bug in CP-violating anomalous triple TGCs fixed 2010-08-06 Solving backwards compatibility problem with O'Caml 3.12.0 2010-07-12 Conserved quantum numbers speed up O'Mega code generation 2010-07-07 Attaching full ISR/FSR parton shower and MPI/ISR module Added SM model containing Hgg, HAA, HAZ vertices 2010-07-02 Matching output available as LHEF and STDHEP 2010-06-30 Various bug fixes, missing files, typos 2010-06-26 CIRCE1 completely re-enabled Chaining structure functions supported 2010-06-25 Partial support for conserved quantum numbers in O'Mega 2010-06-21 Major upgrade of the graphics package: error bars, smarter SINDARIN steering, documentation, and all that... 2010-06-17 MLM matching with PYTHIA shower included 2010-06-16 Added full CIRCE1 and CIRCE2 versions including full documentation and miscellanea to the trunk 2010-06-12 User file management supported, improved variable and command structure 2010-05-24 Improved handling of variables in local command lists 2010-05-20 PYTHIA interface re-enabled 2010-05-19 ASCII file formats for interfacing ROOT and gnuplot in data analysis ################################################################## 2010-05-18 RELEASE: version 2.0.2 2010-05-14 Reimplementation of visualization of phase space channels Minor bug fixes 2010-05-12 Improved phase space - elimination of redundancies 2010-05-08 Interface for polarization completed: polarized beams etc. 2010-05-06 Full quantum numbers appear in process log Integration results are usable as user variables Communication with external programs 2010-05-05 Split module commands into commands, integration, simulation modules 2010-05-04 FSR+ISR for the first time connected to the WHIZARD 2 core ################################################################## 2010-04-25 RELEASE: version 2.0.1 2010-04-23 Automatic compile and integrate if simulate is called Minor bug fixes in O'Mega 2010-04-21 Checkpointing for event generation Flush statements to use WHIZARD inside a pipe 2010-04-20 Reimplementation of signal handling in WGIZARD 2.0 2010-04-19 VAMP is now a separately configurable and installable unit of WHIZARD, included VAMP self-checks Support again compilation in quadruple precision 2010-04-06 Allow for logarithmic plots in GAMELAN, reimplement the possibility to set the number of bins 2010-04-15 Improvement on time estimates for event generation ################################################################## 2010-04-12 RELEASE: version 2.0.0 2010-04-09 Per default, the code for the amplitudes is subdivided to allow faster compiler optimization More advanced and unified and straightforward command language syntax Final bug fixes 2010-04-07 Improvement on SINDARIN syntax; printf, sprintf function thorugh a C interface 2010-04-05 Colorizing DAGs instead of model vertices: speed boost in colored code generation 2010-03-31 Generalized options for normalization of weighted and unweighted events Grid and weight histories added again to log files Weights can be used in analyses 2010-03-28 Cascade decays completely implemented including color and spin correlations 2010-03-07 Added new WHIZARD header with logo 2010-03-05 Removed conflict in O'Mega amplitudes between flavour sums and cascades StdHEP interface re-implemented 2010-03-03 RELEASE: version 2.0.0rc3 Several bug fixes for preventing abuse in input files OpenMP support for amplitudes Reimplementation of WHIZARD 1 HEPEVT ASCII event formats FeynRules interface successfully passed MSSM test 2010-02-26 Eliminating ghost gluons from multi-gluon amplitudes 2010-02-25 RELEASE: version 1.95 HEPEVT format from WHIZARD 1 re-implemented in WHIZARD 2 2010-02-23 Running alpha_s implemented in the FeynRules interface 2010-02-19 MSSM (semi-) automatized self-tests finalized 2010-02-17 RELEASE: version 1.94 2010-02-16 Closed memory corruption in WHIZARD 1 Fixed problems of old MadGraph and CompHep drivers with modern compilers Uncolored vertex selection rules for colored amplitudes in O'Mega 2010-02-15 Infrastructure for color correlation computation in O'Mega finished Forbidden processes are warned about, but treated as non-fatal 2010-02-14 Color correlation computation in O'Mega finalized 2010-02-10 Improving phase space mappings for identical particles in initial and final states Introduction of more extended multi-line error message 2010-02-08 First O'Caml code for computation of color correlations in O'Mega 2010-02-07 First MLM matching with e+ e- -> jets ################################################################## 2010-02-06 RELEASE: version 2.0.0rc2 2010-02-05 Reconsidered the Makefile structure and more extended tests Catch a crash between WHIZARD and O'Mega for forbidden processes Tensor products of arbitrary color structures in jet definitions 2010-02-04 Color correlation computation in O'Mega finalized ################################################################## 2010-02-03 RELEASE: version 2.0.0rc1 ################################################################## 2010-01-31 Reimplemented numerical helicity selection rules Phase space functionality of version 1 restored and improved 2009-12-05 NMSSM validated with FeynRules in WHIZARD 1 (Felix Braam) 2009-12-04 RELEASE: version 2.0.0alpha ################################################################## 2009-04-16 RELEASE: version 1.93 2009-04-15 Clean-up of Makefiles and configure scripts Reconfiguration of BSM model implementation extended supersymmetric models 2008-12-23 New model NMSSM (Felix Braam) SLHA2 added Bug in LHAPDF interface fixed 2008-08-16 Bug fixed in K matrix implementation Gravitino option in the MSSM added 2008-03-20 Improved color and flavor sums ################################################################## 2008-03-12 RELEASE: version 1.92 LHEF (Les Houches Event File) format added Fortran 2003 command-line interface (if supported by the compiler) Automated interface to colored models More bug fixes and workarounds for compiler compatibility ################################################################## 2008-03-06 RELEASE: version 1.91 New model K-matrix (resonances and anom. couplings in WW scattering) EWA spectrum Energy-scan pseudo spectrum Preliminary parton shower module (only from final-state quarks) Cleanup and improvements of configure process Improvements for O'Mega parameter files Quadruple precision works again More plotting options: lines, symbols, errors Documentation with PDF bookmarks enabled Various bug fixes 2007-11-29 New model UED ################################################################## 2007-11-23 RELEASE: version 1.90 O'Mega now part of the WHIZARD tree Madgraph/CompHEP disabled by default (but still usable) Support for LHAPDF (preliminary) Added new models: SMZprime, SM_km, Template Improved compiler recognition and compatibility Minor bug fixes ################################################################## 2006-06-15 RELEASE: version 1.51 Support for anomaly-type Higgs couplings (to gluon and photon/Z) Support for spin 3/2 and spin 2 New models: Little Higgs (4 versions), toy models for extra dimensions and gravitinos Fixes to the whizard.nw source documentation to run through LaTeX Intel 9.0 bug workaround (deallocation of some arrays) 2006-05-15 O'Mega RELEASE: version 0.11 merged JRR's O'Mega extensions ################################################################## 2006-02-07 RELEASE: version 1.50 To avoid confusion: Mention outdated manual example in BUGS file O'Mega becomes part of the WHIZARD generator 2006-02-02 [bug fix update] Bug fix: spurious error when writing event files for weighted events Bug fix: 'r' option for omega produced garbage for some particle names Workaround for ifort90 bug (crash when compiling whizard_event) Workaround for ifort90 bug (crash when compiling hepevt_common) 2006-01-27 Added process definition files for MSSM 2->2 processes Included beam recoil for EPA (T.Barklow) Updated STDHEP byte counts (for STDHEP 5.04.02) Fixed STDHEP compatibility (avoid linking of incomplete .so libs) Fixed issue with comphep requiring Xlibs on Opteron Fixed issue with ifort 8.x on Opteron (compiling 'signal' interface) Fixed color-flow code: was broken for omega with option 'c' and 'w' Workaround hacks for g95 compatibility 2005-11-07 O'Mega RELEASE: version 0.10 O'Mega, merged JRR's and WK's color hack for WHiZard O'Mega, EXPERIMENTAL: cache fusion tables (required for colors a la JRR/WK) O'Mega, make JRR's MSSM official ################################################################## 2005-10-25 RELEASE: version 1.43 Minor fixes in MSSM couplings (Higgs/3rd gen squarks). This should be final, since the MSSM results agree now completely with Madgraph and Sherpa User-defined lower and upper limits for split event file count Allow for counters (events, bytes) exceeding $2^{31}$ Revised checksum treatment and implementation (now MD5) Bug fix: missing process energy scale in raw event file ################################################################## 2005-09-30 RELEASE: version 1.42 Graphical display of integration history ('make history') Allow for switching off signals even if supported (configure option) 2005-09-29 Revised phase space generation code, in particular for flavor sums Negative cut and histogram codes use initial beams instead of initial parton momenta. This allows for computing, e.g., E_miss Support constant-width and zero-width options for O'Mega Width options now denoted by w:X (X=f,c,z). f option obsolescent Bug fix: colorized code: flipped indices could screw up result Bug fix: O'Mega with 'c' and 'w:f' option together (still some problem) Bug fix: dvips on systems where dvips defaults to lpr Bug fix: integer overflow if too many events are requested 2005-07-29 Allow for 2 -> 1 processes (if structure functions are on) 2005-07-26 Fixed and expanded the 'test' matrix element: Unit matrix element with option 'u' / default: normalized phase space ################################################################## 2005-07-15 RELEASE: version 1.41 Bug fix: no result for particle decay processes with width=0 Bug fix: line breaks in O'Mega files with color decomposition 2005-06-02 New self-tests (make test-QED / test-QCD / test-SM) check lists of 2->2 processes Bug fix: HELAS calling convention for wwwwxx and jwwwxx (4W-Vertex) 2005-05-25 Revised Makefile structure Eliminated obsolete references to ISAJET/SUSY (superseded by SLHA) 2005-05-19 Support for color in O'Mega (using color flow decomposition) New model QCD Parameter file changes that correspond to replaced SM module in O'Mega Bug fixes in MSSM (O'Mega) parameter file 2005-05-18 New event file formats, useful for LHC applications: ATHENA and Les Houches Accord (external fragmentation) Naive (i.e., leading 1/N) color factor now implemented both for incoming and outgoing partons 2005-01-26 include missing HELAS files for bundle pgf90 compatibility issues [note: still internal error in pgf90] ################################################################## 2004-12-13 RELEASE: version 1.40 compatibility fix: preprocessor marks in helas code now commented out minor bug fix: format string in madgraph source 2004-12-03 support for arbitray beam energies and directions allow for pT kick in structure functions bug fix: rounding error could result in zero cross section (compiler-dependent) 2004-10-07 simulate decay processes list fraction (of total width/cross section) instead of efficiency in process summary new cut/analysis parameters AA, AAD, CTA: absolute polar angle 2004-10-04 Replaced Madgraph I by Madgraph II. Main improvement: model no longer hardcoded introduced parameter reset_seed_each_process (useful for debugging) bug fix: color initialization for some processes was undefined 2004-09-21 don't compile unix_args module if it is not required ################################################################## 2004-09-20 RELEASE: version 1.30 g95 compatibility issues resolved some (irrelevant) memory leaks closed removed obsolete warning in circe1 manual update (essentially) finished 2004-08-03 O'Mega RELEASE: version 0.9 O'Mega, src/trie.mli, src/trie.ml: make interface compatible with the O'Caml 3.08 library (remains compatible with older versions). Implementation of unused functions still incomplete. 2004-07-26 minor fixes and improvements in make process 2004-06-29 workarounds for new Intel compiler bugs ... no rebuild of madgraph/comphep executables after 'make clean' bug fix in phase space routine: wrong energy for massive initial particles bug fix in (new) model interface: name checks for antiparticles pre-run checks for comphep improved ww-strong model file extended Model files particle name fixes, chep SM vertices included 2004-06-22 O'Mega RELEASE: version 0.8 O'Mega MSSM: sign of W+/W-/A and W+/W-/Z couplings 2004-05-05 Fixed bug in PDFLIB interface: p+pbar was initialized as p+p (ThO) NAG compiler: set number of continuation lines to 200 as default Extended format for cross section summary; appears now in whizard.out Fixed 'bundle' feature 2004-04-28 Fixed compatibility with revised O'Mega SM_ac model Fixed problem with x=0 or x=1 when calling PDFLIB (ThO) Fixed bug in comphep module: Vtb was overlooked ################################################################## 2004-04-15 RELEASE: version 1.28 Fixed bug: Color factor was missing for O'Mega processes with four quarks and more Manual partially updated 2004-04-08 Support for grid files in binary format New default value show_histories=F (reduce output file size) Revised phase space switches: removed annihilation_lines, removed s_channel_resonance, changed meaning of extra_off_shell_lines, added show_deleted_channels Bug fixed which lead to omission of some phase space channels Color flow guessed only if requested by guess_color_flow 2004-03-10 New model interface: Only one model name specified in whizard.prc All model-dependent files reside in conf/models (modellib removed) 2004-03-03 Support for input/output in SUSY Les Houches Accord format Split event files if requested Support for overall time limit Support for CIRCE and CIRCE2 generator mode Support for reading beam events from file 2004-02-05 Fixed compiler problems with Intel Fortran 7.1 and 8.0 Support for catching signals ################################################################## 2003-08-06 RELEASE: version 1.27 User-defined PDF libraries as an alternative to the standard PDFLIB 2003-07-23 Revised phase space module: improved mappings for massless particles, equivalences of phase space channels are exploited Improved mapping for PDF (hadron colliders) Madgraph module: increased max number of color flows from 250 to 1000 ################################################################## 2003-06-23 RELEASE: version 1.26 CIRCE2 support Fixed problem with 'TC' integer kind [Intel compiler complained] 2003-05-28 Support for drawing histograms of grids Bug fixes for MSSM definitions ################################################################## 2003-05-22 RELEASE: version 1.25 Experimental MSSM support with ISAJET interface Improved capabilities of generating/analyzing weighted events Optional drawing phase space diagrams using FeynMF ################################################################## 2003-01-31 RELEASE: version 1.24 A few more fixes and workarounds (Intel and Lahey compiler) 2003-01-15 Fixes and workarounds needed for WHIZARD to run with Intel compiler Command-line option interface for the Lahey compiler Bug fix: problem with reading whizard.phs ################################################################## 2002-12-10 RELEASE: version 1.23 Command-line options (on some systems) Allow for initial particles in the event record, ordered: [beams, initials] - [remnants] - outgoing partons Support for PYTHIA 6.2: Les Houches external process interface String pythia_parameters can be up to 1000 characters long Select color flow states in (internal) analysis Bug fix in color flow content of raw event files Support for transversal polarization of fermion beams Cut codes: PHI now for absolute azimuthal angle, DPHI for distance 'Test' matrix elements optionally respect polarization User-defined code can be inserted for spectra, structure functions and fragmentation Time limits can be specified for adaptation and simulation User-defined file names and file directory Initial weights in input file no longer supported Bug fix in MadGraph (wave function counter could overflow) Bug fix: Gamelan (graphical analysis) was not built if noweb absent ################################################################## 2002-03-16 RELEASE: version 1.22 Allow for beam remnants in the event record 2002-03-01 Handling of aliases in whizard.prc fixed (aliases are whole tokens) 2002-02-28 Optimized phase space handling routines (total execution time reduced by 20-60%, depending on process) ################################################################## 2002-02-26 RELEASE: version 1.21 Fixed ISR formula (ISR was underestimated in previous versions). New version includes ISR in leading-log approximation up to third order. Parameter ISR_sqrts renamed to ISR_scale. ################################################################## 2002-02-19 RELEASE: version 1.20 New process-generating method 'test' (dummy matrix element) Compatibility with autoconf 2.50 and current O'Mega version 2002-02-05 Prevent integration channels from being dropped (optionally) New internal mapping for structure functions improves performance Old whizard.phx file deleted after recompiling (could cause trouble) 2002-01-24 Support for user-defined cuts and matrix element reweighting STDHEP output now written by write_events_format=20 (was 3) 2002-01-16 Improved structure function handling; small changes in user interface: new parameter structured_beams in &process_input parameter fixed_energy in &beam_input removed Support for multiple initial states Eta-phi (cone) cut possible (hadron collider applications) Fixed bug: Whizard library was not always recompiled when necessary Fixed bug: Default cuts were insufficient in some cases Fixed bug: Unusable phase space mappings generated in some cases 2001-12-06 Reorganized document source 2001-12-05 Preliminary CIRCE2 support (no functionality yet) 2001-11-27 Intel compiler support (does not yet work because of compiler bugs) New cut and analysis mode cos-theta* and related Fixed circular jetset_interface dependency warning Some broadcast routines removed (parallel support disabled anyway) Minor shifts in cleanup targets (Makefiles) Modified library search, check for pdflib8* 2001-08-06 Fixed bug: I/O unit number could be undefined when reading phase space Fixed bug: Unitialized variable could cause segfault when event generation was disabled Fixed bug: Undefined subroutine in CIRCE replacement module Enabled feature: TGCs in O'Mega (not yet CompHEP!) matrix elements (CompHEP model sm-GF #5, O'Mega model SM_ac) Fixed portability issue: Makefile did rely on PWD environment variable Fixed portability issue: PYTHIA library search ambiguity resolved 2001-08-01 Default whizard.prc and whizard.in depend on activated modules Fixed bug: TEX=latex was not properly enabled when making plots 2001-07-20 Fixed output settings in PERL script calls Cache enabled in various configure checks 2001-07-13 Support for multiple processes in a single WHIZARD run. The integrations are kept separate, but the generated events are mixed The whizard.evx format has changed (incompatible), including now the color flow information for PYTHIA fragmentation Output files are now process-specific, except for the event file Phase space file whizard.phs (if present) is used only as input, program-generated phase space is now in whizard.phx 2001-07-10 Bug fix: Undefined parameters in parameters_SM_ac.f90 removed 2001-07-04 Bug fix: Compiler options for the case OMEGA is disabled Small inconsistencies in whizard.out format fixed 2001-07-01 Workaround for missing PDFLIB dummy routines in PYTHIA library ################################################################## 2001-06-30 RELEASE: version 1.13 Default path /cern/pro/lib in configure script 2001-06-20 New fragmentation option: Interface for PYTHIA with full color flow information, beam remnants etc. 2001-06-18 Severe bug fixed in madgraph interface: 3-gluon coupling was missing Enabled color flow information in madgraph 2001-06-11 VAMP interface module rewritten Revised output format: Multiple VAMP iterations count as one WHIZARD iteration in integration passes 1 and 3 Improved message and error handling Bug fix in VAMP: handle exceptional cases in rebinning_weights 2001-05-31 new parameters for grid adaptation: accuracy_goal and efficiency_goal ################################################################## 2001-05-29 RELEASE: version 1.12 bug fixes (compilation problems): deleted/modified unused functions 2001-05-16 diagram selection improved and documented 2001-05-06 allow for disabling packages during configuration 2001-05-03 slight changes in whizard.out format; manual extended ################################################################## 2001-04-20 RELEASE: version 1.11 fixed some configuration and compilation problems (PDFLIB etc.) 2001-04-18 linked PDFLIB: support for quark/gluon structure functions 2001-04-05 parameter interface written by PERL script SM_ac model file: fixed error in continuation line 2001-03-13 O'Mega, O'Caml 3.01: incompatible changes O'Mega, src/trie.mli: add covariance annotation to T.t This breaks O'Caml 3.00, but is required for O'Caml 3.01. O'Mega, many instances: replace `sig include Module.T end' by `Module.T', since the bug is fixed in O'Caml 3.01 2001-02-28 O'Mega, src/model.mli: new field Model.vertices required for model functors, will retire Model.fuse2, Model.fuse3, Model.fusen soon. ################################################################## 2001-03-27 RELEASE: version 1.10 reorganized the modules as libraries linked PYTHIA: support for parton fragmentation 2000-12-14 fixed some configuration problems (if noweb etc. are absent) ################################################################## 2000-12-01 RELEASE of first public version: version 1.00beta