From f70363442f684eb5f86b63d3d1e2c1cc3a11265d Mon Sep 17 00:00:00 2001 From: Uwe Hernandez Acosta Date: Thu, 7 Mar 2024 20:27:21 +0100 Subject: [PATCH] Refactoring of differential cross section functionality (#38) # Problem statement The current implementation only allows implementing `differential_cross_section` directly for given processes. Furthermore, the interface does not allow the implementation of different phase-space coordinate systems, e.g. to model $d\sigma/dE$ **and** $d\sigma/d\cos\theta$ for the same process. # Suggested solution ## Advanced process interface I suggest to disassemble the differential cross-sections interface into a more flexible interface: ```mermaid flowchart TD _differential_cross_section --> _differential_probability _differential_cross_section --> _incident_flux* _differential_probability --> _avg_matrix_element_square _differential_probability --> _phase_space_factor* _differential_probability -.-> _is_in_phasespace* _avg_matrix_element_square --> _matrix_element* _avg_matrix_element_square --> _avg_normalization* ``` where the functions with the asterisk give the new interface for the calculation of differential cross-sections: * `_matrix_element`: the matrix element of the process * `_phase_space_factor`: the value of the pre-differential factor of the phase-space measure * `_is_in_phasespace`: checks if a given input is physical, see below * `_incident_flux`: the incident particle flux, used to normalize the probability * `_avg_normalization`: normalization for averaging of squared matrix elements The underscore of those functions implies, that they are not exported, and no input validation check is performed. For the functions `_differential_cross_section` and `_differential_probability` there are versions `differential_cross_section` and `differential_probability`, where input validation is performed. ## Phase space definition To attack the original problem using different coordinate systems for the same process, this PR adds a simple system for coordinate systems and frames of reference. Those are propagated through a composite type: ```Julia PhasespaceDefinition{ CS<:AbstractCoordinateSystem, F<: AbstractFrameOfReference } ``` Currently, the given example implementations are very limited, but this deserves an interface in the future. A dedicated issue will be opened during the review process of this PR (see todos below). ## Phase space check **Not included** in the input validation is the check if given incoming and outgoing phase-spaces are physical, i.e. if all momenta are on-shell and fulfill some sort of energy-momentum conservation. Therefore, I suggest to add another interface function ```Julia _is_in_phasespace( in_ps_def::AbstractPhasespaceDefinition, in_ps::AbstractVector{T}, out_ps_def::AbstractPhasespaceDefinition, out_ps::AbstractVector{T}, ) where {T<:QEDbase.AbstractFourMomentum} ``` which returns `true` if the input is physical, and `false` otherwise. Consequently, there are unsafe versions of cross-sections and probabilities, which don't perform the check given by the function above, and safe versions, which return null if the condition given by `_is_in_phasespace` is not met. # Final remarks To conclude, the different versions of cross-section and probability functions, derived from the interface above, are ```Julia _unsafe_differential_cross_section # without input validation, without phase-space check _differential_cross_section # without input validation, with phase-space check unsafe_differential_cross_section # with input validation, without phase-space check differential_cross_section # with input validation, with phase-space check _unsafe_differential_probability # without input validation, without phase-space check _differential_probability # without input validation, with phase-space check unsafe_differential_probability # with input validation, without phase-space check differential_probability # with input validation, with phase-space check ``` where only the functions without an underscore are exported, for all of those functions, there are implementations for all combinations of vectors and matrices for the incoming and outgoing phase space. ## Todos: - [x] write tests for `(_(unsafe_))probability` - [x] make total cross-section an interface function - [x] make energy-momentum check for *safe implementations* an interface function - [ ] Write a gist about the broadcasted implementations - [x] rename `_[unsafe_]probability` to `_[unsafe]differential_probability` - [x] cleanup in Project.toml - [x] open issue on `PhasespaceDefinition` --------- Co-authored-by: Uwe Hernandez Acosta Co-authored-by: Tom Jungnickel <140055258+tjungni@users.noreply.github.com> --- Project.toml | 2 - src/QEDprocesses.jl | 18 +- src/cross_sections.jl | 304 +++++++++++++++++ src/interfaces/process_interface.jl | 319 +++++++++--------- src/phase_spaces.jl | 20 ++ src/probabilities.jl | 291 ++++++++++++++++ test/Project.toml | 1 + test/cross_sections.jl | 245 ++++++++++++++ test/interfaces/model_interface.jl | 11 +- test/interfaces/process_interface.jl | 283 +++++----------- test/interfaces/setup_interface.jl | 5 +- test/runtests.jl | 5 +- .../test_implementation/TestImplementation.jl | 38 +++ test/test_implementation/groundtruths.jl | 260 ++++++++++++++ test/test_implementation/random_momenta.jl | 88 +++++ test/test_implementation/test_model.jl | 5 + test/test_implementation/test_process.jl | 91 +++++ test/test_implementation/utils.jl | 5 + 18 files changed, 1616 insertions(+), 375 deletions(-) create mode 100644 src/cross_sections.jl create mode 100644 src/phase_spaces.jl create mode 100644 src/probabilities.jl create mode 100644 test/cross_sections.jl create mode 100644 test/test_implementation/TestImplementation.jl create mode 100644 test/test_implementation/groundtruths.jl create mode 100644 test/test_implementation/random_momenta.jl create mode 100644 test/test_implementation/test_model.jl create mode 100644 test/test_implementation/test_process.jl create mode 100644 test/test_implementation/utils.jl diff --git a/Project.toml b/Project.toml index cfab2e8..ebdec6e 100644 --- a/Project.toml +++ b/Project.toml @@ -4,10 +4,8 @@ authors = ["Uwe Hernandez Acosta ", "Simeon Ehrig", "Klaus version = "0.1.0" [deps] -DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" QEDbase = "10e22c08-3ccb-4172-bfcf-7d7aa3d04d93" [compat] -DocStringExtensions = "0.9" QEDbase = "0.1" julia = "1.6" diff --git a/src/QEDprocesses.jl b/src/QEDprocesses.jl index bf9096f..6d6ddb3 100644 --- a/src/QEDprocesses.jl +++ b/src/QEDprocesses.jl @@ -7,7 +7,14 @@ export AbstractModelDefinition, fundamental_interaction_type export AbstractProcessDefinition, incoming_particles, outgoing_particles export in_phasespace_dimension, out_phasespace_dimension export number_incoming_particles, number_outgoing_particles -export differential_cross_section, total_cross_section + +# probabilities +export differential_probability, unsafe_differential_probability +export total_probability + +# differential cross section +export differential_cross_section, unsafe_differential_cross_section +export total_cross_section # Abstract setup interface export AbstractComputationSetup, InvalidInputError, compute @@ -16,7 +23,11 @@ export AbstractProcessSetup, scattering_process, physical_model # propagator export propagator -using DocStringExtensions +# phase space +export AbstractCoordinateSystem, SphericalCoordinateSystem +export AbstractFrameOfReference, CenterOfMomentumFrame, ElectronRestFrame +export AbstractPhasespaceDefinition, PhasespaceDefinition + using QEDbase include("utils.jl") @@ -24,4 +35,7 @@ include("interfaces/model_interface.jl") include("interfaces/process_interface.jl") include("interfaces/setup_interface.jl") include("propagators.jl") +include("phase_spaces.jl") +include("probabilities.jl") +include("cross_sections.jl") end diff --git a/src/cross_sections.jl b/src/cross_sections.jl new file mode 100644 index 0000000..7a3c947 --- /dev/null +++ b/src/cross_sections.jl @@ -0,0 +1,304 @@ +######################## +# differential cross sections and probabilities. +# +# This file contains default implementations for differential and total cross +# sections based on the scattering process interface +######################## + +############ +# +# differential cross sections +# +############ + +# differential cross sections without energy momentum conservation check +# single in phase space point/ single out phase space point +function _unsafe_differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + I = 1 / (4 * _incident_flux(proc, model, in_phase_space)) + + return I * _unsafe_differential_probability( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +# differential cross sections without energy momentum conservation check +# single in phase space point/ several out phase space points +function _unsafe_differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractMatrix{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Vector{eltype(T)}(undef, size(out_phase_space, 2)) + for i in 1:size(out_phase_space, 2) + res[i] = _unsafe_differential_cross_section( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + view(out_phase_space, :, i), + ) + end + return res +end + +# differential cross sections without energy momentum conservation check +# several in phase space points/ one or several out phase space points +function _unsafe_differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Matrix{eltype(T)}(undef, size(in_phase_space, 2), size(out_phase_space, 2)) + for i in 1:size(in_phase_space, 2) + res[i, :] .= _unsafe_differential_cross_section( + proc, + model, + in_phase_space_def, + view(in_phase_space, :, i), + out_phase_space_def, + out_phase_space, + ) + end + return res +end + +""" + + function unsafe_differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, + ) where {T<:QEDbase.AbstractFourMomentum} + +Return the differential cross section without checking if the given phase space(s) are physical. +""" +function unsafe_differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + size(in_phase_space, 1) == number_incoming_particles(proc) || throw( + DimensionMismatch( + "The number of incoming particles <$(number_incoming_particles(proc))> is inconsistent with input size <$(size(in_phase_space,1))>", + ), + ) + + size(out_phase_space, 1) == number_outgoing_particles(proc) || throw( + DimensionMismatch( + "The number of outgoing particles <$(number_outgoing_particles(proc))> is inconsistent with input size <$(size(out_phase_space,1))>", + ), + ) + + return _unsafe_differential_cross_section( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +# differential cross sections with energy momentum conservation check +# single in phase space point/ single out phase space point +function _differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + if !_is_in_phasespace( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) + return zero(eltype(T)) + end + + return _unsafe_differential_cross_section( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +# differential cross sections with energy momentum conservation check +# single in phase space point/ several out phase space points +function _differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractMatrix{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Vector{eltype(T)}(undef, size(out_phase_space, 2)) + for i in 1:size(out_phase_space, 2) + res[i] = _differential_cross_section( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + view(out_phase_space, :, i), + ) + end + return res +end + +# differential cross sections with energy momentum conservation check +# several in phase space points/ one or several out phase space points +function _differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Matrix{eltype(T)}(undef, size(in_phase_space, 2), size(out_phase_space, 2)) + for i in 1:size(in_phase_space, 2) + res[i, :] .= _differential_cross_section( + proc, + model, + in_phase_space_def, + view(in_phase_space, :, i), + out_phase_space_def, + out_phase_space, + ) + end + return res +end + +""" + differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, + ) where {T<:QEDbase.AbstractFourMomentum} + +If the given phase spaces are physical, return differential cross section. Zero otherwise + +""" +function differential_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + size(in_phase_space, 1) == number_incoming_particles(proc) || throw( + DimensionMismatch( + "The number of incoming particles <$(number_incoming_particles(proc))> is inconsistent with input size <$(size(in_phase_space,1))>", + ), + ) + + size(out_phase_space, 1) == number_outgoing_particles(proc) || throw( + DimensionMismatch( + "The number of outgoing particles <$(number_outgoing_particles(proc))> is inconsistent with input size <$(size(out_phase_space,1))>", + ), + ) + + return _differential_cross_section( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +############ +# Total cross sections +############ + +# total cross section on single phase space point +function _total_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + I = 1 / (4 * _incident_flux(proc, model, in_phase_space)) + + return I * _total_probability(proc, model, in_phase_space_def, in_phase_space) +end + +# total cross section on several phase space points +function _total_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Vector{eltype(T)}(undef, size(in_phase_space, 2)) + for i in 1:size(in_phase_space, 2) + res[i] = _total_cross_section( + proc, model, in_phase_space_def, view(in_phase_space, :, i) + ) + end + return res +end + +""" + total_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + ) where {T<:QEDbase.AbstractFourMomentum} + +Return the total cross section for a given combination of scattering process and compute model. +""" +function total_cross_section( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + size(in_phase_space, 1) == number_incoming_particles(proc) || throw( + DimensionMismatch( + "The number of incoming particles <$(number_incoming_particles(proc))> is inconsistent with input size <$(size(in_phase_space,1))>", + ), + ) + + return _total_cross_section(proc, model, in_phase_space_def, in_phase_space) +end diff --git a/src/interfaces/process_interface.jl b/src/interfaces/process_interface.jl index 0fe1796..4e721be 100644 --- a/src/interfaces/process_interface.jl +++ b/src/interfaces/process_interface.jl @@ -16,6 +16,61 @@ outgoing_particles(proc_def::AbstractProcessDefinition) ``` which return a tuple of the incoming and outgoing particles, respectively. + +Furthermore, to calculate scattering probabilities and differential cross sections, the following +interface functions need to be implemented + +```Julia + _incident_flux( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} + + _matrix_element( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space::AbstractVector{T} + out_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} + + _averaging_norm( + proc::AbstractProcessDefinition + ) + + _is_in_phasespace( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_ps_def::InPhasespaceDefinition, + in_phase_space::AbstractVector{T} + out_ps_def::OutPhasespaceDefinition, + out_phase_space::AbstractVector{T} + ) + + _phase_space_factor( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_ps_def::InPhasespaceDefinition, + in_phase_space::AbstractVector{T} + out_ps_def::OutPhasespaceDefinition, + out_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} +``` + +Optional is the implementation of + +```Julia + + _total_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_ps_def::InPhasespaceDefinition, + in_phase_space::AbstractVector{T} + ) where {T<: QEDbase.AbstractFourMomentum} + +``` +to enable the calculation of total probabilities and cross sections. + """ abstract type AbstractProcessDefinition end @@ -38,176 +93,144 @@ This function needs to be given to implement the scattering process interface. function outgoing_particles end """ + _incident_flux( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} - $(TYPEDSIGNATURES) +Interface function, which returns the incident flux of the given scattering process for a given incoming phase-space. -Return the number of incoming particles of a given process. """ -@inline function number_incoming_particles(proc_def::AbstractProcessDefinition) - return length(incoming_particles(proc_def)) -end +function _incident_flux end """ + _matrix_element( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space::AbstractVector{T} + out_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} - $(TYPEDSIGNATURES) - -Return the number of outgoing particles of a given process. +Interface function, which returns a tuple of scattering matrix elements for each spin and polarisation combination of `proc`. """ -@inline function number_outgoing_particles(proc_def::AbstractProcessDefinition) - return length(outgoing_particles(proc_def)) +function _matrix_element end + +function _matrix_element_square( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + mat_el = _matrix_element(proc, model, in_phase_space, out_phase_space) + return abs2.(mat_el) end """ + _averaging_norm( + proc::AbstractProcessDefinition + ) - _differential_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::AbstractVector{T}, - out_phasespace::AbstractVector{T}, - ) where {T<:QEDbase.AbstractFourMomentum} - -Interface function for the combination of scattering processes and physical models. Return the differential cross section of a -given process and physical model for a passed initial and final phase space. The elements of the `AbstractVector` representing the phase spaces -are the momenta of the respective particles. The implementation of this function for a concrete process and model must not -check if the length of the passed phase spaces match the respective number of particles. - -!!! note "differential cross section interface" +Interface function, which returns a normalization for the averaging of the squared matrix elements over spins and polarizations. +""" +function _averaging_norm end - Given an implementation of this method, the following *unsafe* generic implementations are provided: +""" - ```julia + _is_in_phasespace( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_ps_def::InPhasespaceDefinition, + in_phase_space::AbstractVector{T} + out_ps_def::OutPhasespaceDefinition, + out_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} - _differential_cross_section(proc_def,model_def,in_phasespace::AbstractVector{T},finial_phasespace::AbstractMatrix{T}) - _differential_cross_section(proc_def,model_def,in_phasespace::AbstractMatrix{T},finial_phasespace::AbstractVector{T}) - _differential_cross_section(proc_def,model_def,in_phasespace::AbstractMatrix{T},finial_phasespace::AbstractMatrix{T}) +Interface function, which returns `true`, if the combination of the given incoming and outgoing phase space +is physical, i.e. all momenta are on-shell and some sort of energy-momentum conservation holds. +""" +function _is_in_phasespace end - ``` +""" + _phase_space_factor( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_ps_def::InPhasespaceDefinition, + in_phase_space::AbstractVector{T} + out_ps_def::OutPhasespaceDefinition, + out_phase_space::AbstractVector{T} + ) where {T<:QEDbase.AbstractFourMomentum} - where `T<:QEDbase.AbstractFourMomentum`. Although, any combinations of initial and final phase space types given by *single set of points* (AbstractVector{T}) and *mutiple set of points* (AbstractMatrix{T}) - is implemented. Furthermore, a safe version of `_differential_cross_section` is also implemented: [`differential_cross_section`](@ref). +Interface function, which returns the pre-differential factor of the invariant phase space intergral measure. -!!! note "unsafe implementation" - - Each instance of this function does not check the validity of the input. - Therefore, these functions are not exported and should be used with caution. To add a method in order to implement the cross section interface, - it is recommented to directly use `QEDprocesses._differential_cross_section` instead of globally `using QEDprocesses: _differential_cross_section`. +!!! note "Convention" + It is assumed, that this function returns the value of + ```math + \\mathrm{d}\\Pi_n:= \\prod_{i=1}^N \\frac{\\mathrm{d}^3p_i}{(2\\pi)^3 2 p_i^0} H(P_t, p_1, \\dots, p_N), + ``` +where ``H(\\dots)`` is a characteristic function (or distribution) which constrains the phase space, e.g. ``\\delta^{(4)}(P_t - \\sum_i p_i)``. """ -function _differential_cross_section end +function _phase_space_factor end + +####################### +# +# utility functions +# +####################### """ - - differential_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::Union{AbstractVector{T},AbstractMatrix{T}}, - out_phasespace::Union{AbstractVector{T},AbstractMatrix{T}}, - ) where {T<:QEDbase.AbstractFourMomentum} -Return the differential cross section for a given combination of a scattering process -and model definition evaluated on the passed inital and final phase space points. + number_incoming_particles(proc_def::AbstractProcessDefinition) -This function will eventually call the respective interface function [`_differential_cross_section`](@ref). +Return the number of incoming particles of a given process. """ -function differential_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::Union{AbstractVector{T},AbstractMatrix{T}}, - out_phasespace::Union{AbstractVector{T},AbstractMatrix{T}}, -) where {T<:QEDbase.AbstractFourMomentum} - size(in_phasespace, 1) == number_incoming_particles(proc_def) || throw( - DimensionMismatch( - "The number of momenta in the initial phasespace <{length(in_phasespace)}> does not match the number of incoming particles of the process <{number_incoming_particles(proc_def)}>.", - ), - ) - size(out_phasespace, 1) == number_outgoing_particles(proc_def) || throw( - DimensionMismatch( - "The number of momenta in the final phasespace <{length(out_phasespace)}> does not match the number of outgoing particles of the process <{number_outgoing_particles(proc_def)}>.", - ), - ) - return _differential_cross_section(proc_def, model_def, in_phasespace, out_phasespace) +@inline function number_incoming_particles(proc_def::AbstractProcessDefinition) + return length(incoming_particles(proc_def)) end -# returns diffCS for single `initPS` and several `finalPS` points without input-check -function _differential_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::AbstractVector{T}, - out_phasespace::AbstractMatrix{T}, -) where {T<:QEDbase.AbstractFourMomentum} - res = Vector{_base_component_type(in_phasespace)}(undef, size(out_phasespace, 2)) - for i in 1:size(out_phasespace, 2) - res[i] = _differential_cross_section( - proc_def, model_def, in_phasespace, view(out_phasespace, :, i) - ) - end - return res -end +""" -function _differential_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::AbstractMatrix{T}, - out_phasespace::AbstractVector{T}, -) where {T<:QEDbase.AbstractFourMomentum} - res = Vector{_base_component_type(in_phasespace)}(undef, size(in_phasespace, 2)) - for i in 1:size(in_phasespace, 2) - res[i] = _differential_cross_section( - proc_def, model_def, view(in_phasespace, :, i), out_phasespace - ) - end - return res -end + number_outgoing_particles(proc_def::AbstractProcessDefinition) -function _differential_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::AbstractMatrix{T}, - out_phasespace::AbstractMatrix{T}, -) where {T<:QEDbase.AbstractFourMomentum} - res = Matrix{_base_component_type(in_phasespace)}( - undef, size(in_phasespace, 2), size(out_phasespace, 2) - ) - for init_idx in 1:size(in_phasespace, 2) - for final_idx in 1:size(out_phasespace, 2) - res[init_idx, final_idx] = _differential_cross_section( - proc_def, - model_def, - view(in_phasespace, :, init_idx), - view(out_phasespace, :, final_idx), - ) - end - end - return res +Return the number of outgoing particles of a given process. +""" +@inline function number_outgoing_particles(proc_def::AbstractProcessDefinition) + return length(outgoing_particles(proc_def)) end """ + _total_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_ps_def::InPhasespaceDefinition, + in_phase_space::AbstractVector{T} + ) where {T<: QEDbase.AbstractFourMomentum} - _total_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::AbstractVector{T}, - ) where {T<:QEDbase.AbstractFourMomentum} end +Interface function for the combination of a scattering process and a physical model. Return the total of a +given process and model for a passed initial phase space definition and point. The elements of `in_phase_space`, +which represent the initial phase space, are the momenta of the respective particles. +The implementation of this function for a concrete process and model must not check if the length of +the passed initial phase spaces match the number of incoming particles. -Interface function for the combination of scattering processes and physical models. Return the total cross section of a -given process and model for a passed initial phase space. The elements of the `AbstractVector` representing the initial phase space -are the momenta of the respective particles. The implementation of this function for a concrete process and model must not -check if the length of the passed initial phase spaces match number of incoming particles. +!!! note "probability interface" -!!! note "cross section interface" - - Given an implementation of this method, the following *unsafe* generic implementation is provided: + Given an implementation of this method, the following generic implementation without input check is provided: ```julia - _total_cross_section(proc_def,model_def,in_phasespace::AbstractMatrix{T}) + _total_probability(proc_def,model_def,in_phasespace::AbstractMatrix{T}) ``` - where `T<:QEDbase.AbstractFourMomentum`. Although, `_total_cross_section` is also implemented for a vector of initial phase space points. - Furthermore, a safe version of `_total_cross_section` is also implemented: [`total_cross_section`](@ref). + where `T<:QEDbase.AbstractFourMomentum`, i.e. `_total_probability` is also implemented for a vector of initial phase space points. + Furthermore, a safe version of `_total_probability` is also implemented: [`total_probability`](@ref). +!!! note "total cross section" + + Given an implementaion of this method and [`_incident_flux`](@ref), the respective functions for the total cross section are available, + i.e. `_total_cross_section` (unsafe and not exported), and [`total_cross_section`](@ref), respectively. !!! note @@ -216,42 +239,4 @@ check if the length of the passed initial phase spaces match number of incoming it is recommented to directly use `QEDprocesses._total_cross_section` instead of globally `using QEDprocesses: _total_cross_section`. """ -function _total_cross_section end - -function _total_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::AbstractMatrix{T}, -) where {T<:QEDbase.AbstractFourMomentum} - res = Vector{_base_component_type(in_phasespace)}(undef, size(in_phasespace, 2)) - for i in 1:size(in_phasespace, 2) - res[i] = _total_cross_section(proc_def, model_def, view(in_phasespace, :, i)) - end - return res -end - -""" - - total_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::Union{AbstractVector{T},AbstractMatrix{T}}, - ) where {T<:QEDbase.AbstractFourMomentum} - -Return the total cross section for a combination of a scattering process and a physical model evaluated on a given initial phase space. - -This function will eventually call the respective interface function [`_total_cross_section`](@ref). - -""" -function total_cross_section( - proc_def::AbstractProcessDefinition, - model_def::AbstractModelDefinition, - in_phasespace::Union{AbstractVector{T},AbstractMatrix{T}}, -) where {T<:QEDbase.AbstractFourMomentum} - size(in_phasespace, 1) == number_incoming_particles(proc_def) || throw( - DimensionMismatch( - "The number of momenta in the initial phasespace <{length(in_phasespace)}> does not match the number of incoming particles of the process <{number_incoming_particles(proc_def)}>.", - ), - ) - return _total_cross_section(proc_def, model_def, in_phasespace) -end +function _total_probability end diff --git a/src/phase_spaces.jl b/src/phase_spaces.jl new file mode 100644 index 0000000..ba2f64a --- /dev/null +++ b/src/phase_spaces.jl @@ -0,0 +1,20 @@ +##################### +# phase spaces +# +# This file contains a collection of types and functions to handle phase spaces +# for scattering processes. +##################### + +abstract type AbstractCoordinateSystem end +struct SphericalCoordinateSystem <: AbstractCoordinateSystem end + +abstract type AbstractFrameOfReference end +struct CenterOfMomentumFrame <: AbstractFrameOfReference end +struct ElectronRestFrame <: AbstractFrameOfReference end + +abstract type AbstractPhasespaceDefinition end +struct PhasespaceDefinition{CS<:AbstractCoordinateSystem,F<:AbstractFrameOfReference} <: + AbstractPhasespaceDefinition + coord_sys::CS + frame::F +end diff --git a/src/probabilities.jl b/src/probabilities.jl new file mode 100644 index 0000000..798a0bc --- /dev/null +++ b/src/probabilities.jl @@ -0,0 +1,291 @@ +############ +# scattering probabilities +# +# This file contains implementations of the scattering probability based on the +# process interface with and without input validation and/or phase space +# constraint. +############ + +# differential probability without energy momentum conservation check +# single in phase space points/ single out phase space point +function _unsafe_differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + matrix_elements_sq = _matrix_element_square( + proc, model, in_phase_space, out_phase_space + ) + + normalization = _averaging_norm(proc) + + ps_fac = _phase_space_factor( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) + + return normalization * sum(matrix_elements_sq) * ps_fac +end + +# differential probability without energy momentum conservation check +# single in phase space points/ several out phase space point +function _unsafe_differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractMatrix{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Vector{eltype(T)}(undef, size(out_phase_space, 2)) + for i in 1:size(out_phase_space, 2) + res[i] = _unsafe_differential_probability( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + view(out_phase_space, :, i), + ) + end + return res +end + +# differential probability without energy momentum conservation check +# several in phase space points/ one or several out phase space point +function _unsafe_differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Matrix{eltype(T)}(undef, size(in_phase_space, 2), size(out_phase_space, 2)) + for i in 1:size(in_phase_space, 2) + res[i, :] .= _unsafe_differential_probability( + proc, + model, + in_phase_space_def, + view(in_phase_space, :, i), + out_phase_space_def, + out_phase_space, + ) + end + return res +end + +""" + unsafe_differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, + ) where {T<:QEDbase.AbstractFourMomentum} + +Return differential probability without checking if the given phase space(s) are physical. +""" +function unsafe_differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + size(in_phase_space, 1) == number_incoming_particles(proc) || throw( + DimensionMismatch( + "The number of incoming particles <$(number_incoming_particles(proc))> is inconsistent with input size <$(size(in_phase_space,1))>", + ), + ) + + size(out_phase_space, 1) == number_outgoing_particles(proc) || throw( + DimensionMismatch( + "The number of outgoing particles <$(number_outgoing_particles(proc))> is inconsistent with input size <$(size(out_phase_space,1))>", + ), + ) + + return _unsafe_differential_probability( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +# differential probability with energy momentum conservation check +# one in phase space points/ one out phase space point +function _differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + if !_is_in_phasespace( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) + return zero(eltype(T)) + end + + return _unsafe_differential_probability( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +# differential probability with energy momentum conservation check +# one in phase space points/ several out phase space point +function _differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVector{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractMatrix{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Vector{eltype(T)}(undef, size(out_phase_space, 2)) + for i in 1:size(out_phase_space, 2) + res[i] = _differential_probability( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + view(out_phase_space, :, i), + ) + end + return res +end + +# differential probability with energy momentum conservation check +# several in phase space points/ one or several out phase space point +function _differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Matrix{eltype(T)}(undef, size(in_phase_space, 2), size(out_phase_space, 2)) + for i in 1:size(in_phase_space, 2) + res[i, :] .= _differential_probability( + proc, + model, + in_phase_space_def, + view(in_phase_space, :, i), + out_phase_space_def, + out_phase_space, + ) + end + return res +end + +""" + differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, + ) where {T<:QEDbase.AbstractFourMomentum} + +Return the differential cross section if the given phase spaces are physical, and zero otherwise. +""" +function differential_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, + out_phase_space_def::AbstractPhasespaceDefinition, + out_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + size(in_phase_space, 1) == number_incoming_particles(proc) || throw( + DimensionMismatch( + "The number of incoming particles <$(number_incoming_particles(proc))> is inconsistent with input size <$(size(in_phase_space,1))>", + ), + ) + + size(out_phase_space, 1) == number_outgoing_particles(proc) || throw( + DimensionMismatch( + "The number of outgoing particles <$(number_outgoing_particles(proc))> is inconsistent with input size <$(size(out_phase_space,1))>", + ), + ) + + return _differential_probability( + proc, + model, + in_phase_space_def, + in_phase_space, + out_phase_space_def, + out_phase_space, + ) +end + +########### +# Total probability +########### + +# total probability on several phase space point +function _total_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, +) where {T<:QEDbase.AbstractFourMomentum} + res = Vector{eltype(T)}(undef, size(in_phase_space, 2)) + for i in 1:size(in_phase_space, 2) + res[i] = _total_probability( + proc, model, in_phase_space_def, view(in_phase_space, :, i) + ) + end + return res +end + +""" + total_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractMatrix{T}, + ) where {T<:QEDbase.AbstractFourMomentum} + +Return the total probability of a given model and process combination. +""" +function total_probability( + proc::AbstractProcessDefinition, + model::AbstractModelDefinition, + in_phase_space_def::AbstractPhasespaceDefinition, + in_phase_space::AbstractVecOrMat{T}, +) where {T<:QEDbase.AbstractFourMomentum} + size(in_phase_space, 1) == number_incoming_particles(proc) || throw( + DimensionMismatch( + "The number of incoming particles <$(number_incoming_particles(proc))> is inconsistent with input size <$(size(in_phase_space,1))>", + ), + ) + + return _total_probability(proc, model, in_phase_space_def, in_phase_space) +end diff --git a/test/Project.toml b/test/Project.toml index ce945cc..c71dc1e 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -2,4 +2,5 @@ QEDbase = "10e22c08-3ccb-4172-bfcf-7d7aa3d04d93" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" +Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/cross_sections.jl b/test/cross_sections.jl new file mode 100644 index 0000000..3da0744 --- /dev/null +++ b/test/cross_sections.jl @@ -0,0 +1,245 @@ +using Random +using Suppressor +using QEDbase +using QEDprocesses + +RNG = MersenneTwister(137137) +ATOL = 0.0 +RTOL = sqrt(eps()) + +include("test_implementation/TestImplementation.jl") +TESTMODEL = TestImplementation.TestModel() +TESTPSDEF = TestImplementation.TestPhasespaceDef() + +@testset "($N_INCOMING,$N_OUTGOING)" for (N_INCOMING, N_OUTGOING) in Iterators.product( + (1, rand(RNG, 2:8)), (1, rand(RNG, 2:8)) +) + INCOMING_PARTICLES = rand(RNG, TestImplementation.PARTICLE_SET, N_INCOMING) + OUTGOING_PARTICLES = rand(RNG, TestImplementation.PARTICLE_SET, N_OUTGOING) + + TESTPROC = TestImplementation.TestProcess(INCOMING_PARTICLES, OUTGOING_PARTICLES) + + # single ps points + p_in_phys = TestImplementation._rand_momenta(RNG, N_INCOMING) + p_in_phys_invalid = TestImplementation._rand_momenta(RNG, N_INCOMING + 1) + p_in_unphys = TestImplementation._rand_in_momenta_failing(RNG, N_INCOMING) + p_in_unphys_invalid = TestImplementation._rand_in_momenta_failing(RNG, N_INCOMING + 1) + + p_out_phys = TestImplementation._rand_momenta(RNG, N_OUTGOING) + p_out_phys_invalid = TestImplementation._rand_momenta(RNG, N_OUTGOING + 1) + p_out_unphys = TestImplementation._rand_out_momenta_failing(RNG, N_OUTGOING) + p_out_unphys_invalid = TestImplementation._rand_out_momenta_failing(RNG, N_OUTGOING + 1) + + # sets of ps points + p_in_set_phys = TestImplementation._rand_momenta(RNG, N_INCOMING, 2) + p_in_set_unphys_mix = TestImplementation._rand_in_momenta_failing_mix( + RNG, N_INCOMING, 2 + ) + p_in_set_unphys_all = TestImplementation._rand_in_momenta_failing_all( + RNG, N_INCOMING, 2 + ) + p_in_set_phys_invalid = TestImplementation._rand_momenta(RNG, N_INCOMING + 1, 2) + p_in_set_unphys_mix_invalid = TestImplementation._rand_in_momenta_failing_mix( + RNG, N_INCOMING + 1, 2 + ) + p_in_set_unphys_all_invalid = TestImplementation._rand_in_momenta_failing_all( + RNG, N_INCOMING + 1, 2 + ) + + p_out_set_phys = TestImplementation._rand_momenta(RNG, N_OUTGOING, 2) + p_out_set_unphys_mix = TestImplementation._rand_out_momenta_failing_mix( + RNG, N_OUTGOING, 2 + ) + p_out_set_unphys_all = TestImplementation._rand_out_momenta_failing_all( + RNG, N_OUTGOING, 2 + ) + p_out_set_phys_invalid = TestImplementation._rand_momenta(RNG, N_OUTGOING + 1, 2) + p_out_set_unphys_mix_invalid = TestImplementation._rand_out_momenta_failing_mix( + RNG, N_OUTGOING + 1, 2 + ) + p_out_set_unphys_all_invalid = TestImplementation._rand_out_momenta_failing_all( + RNG, N_OUTGOING + 1, 2 + ) + + p_in_all = ( + p_in_phys, + p_in_unphys, + p_in_phys_invalid, + p_in_unphys_invalid, + p_in_set_phys, + p_in_set_unphys_mix, + p_in_set_unphys_all, + p_in_set_phys_invalid, + p_in_set_unphys_mix_invalid, + p_in_set_unphys_all_invalid, + ) + + p_out_all = ( + p_out_phys, + p_out_phys_invalid, + p_out_unphys, + p_out_unphys_invalid, + p_out_set_phys, + p_out_set_unphys_mix, + p_out_set_unphys_all, + p_out_set_phys_invalid, + p_out_set_unphys_mix_invalid, + p_out_set_unphys_all_invalid, + ) + # all combinations + p_combs = Iterators.product(p_in_all, p_out_all) + + p_in_all_valid = ( + p_in_phys, p_in_unphys, p_in_set_phys, p_in_set_unphys_mix, p_in_set_unphys_all + ) + + p_out_all_valid = ( + p_out_phys, p_out_unphys, p_out_set_phys, p_out_set_unphys_mix, p_out_set_unphys_all + ) + + # all valid combinations + p_combs_valid = Iterators.product(p_in_all_valid, p_out_all_valid) + + p_in_all_phys = (p_in_phys, p_in_set_phys) + p_out_all_phys = (p_out_phys, p_out_set_phys) + + p_combs_phys = Iterators.product(p_in_all_phys, p_out_all_phys) + + @testset "cross section" begin + @testset "unsafe" begin + @testset "compute" begin + for (P_IN, P_OUT) in p_combs_phys + diffCS = unsafe_differential_cross_section( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + groundtruth = TestImplementation._groundtruth_unsafe_diffCS( + TESTPROC, P_IN, P_OUT + ) + @test isapprox(diffCS, groundtruth, atol=ATOL, rtol=RTOL) + end + end + + @testset "invalid input" begin + for (P_IN, P_OUT) in p_combs + + # filter out all valid combinations + if !((P_IN, P_OUT) in p_combs_valid) + @test_throws DimensionMismatch unsafe_differential_cross_section( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + end + end + end + end + @testset "safe" begin + @testset "compute" begin + for (P_IN, P_OUT) in p_combs_valid + diffCS = differential_cross_section( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + groundtruth = TestImplementation._groundtruth_safe_diffCS( + TESTPROC, P_IN, P_OUT + ) + @test isapprox(diffCS, groundtruth, atol=ATOL, rtol=RTOL) + end + end + + @testset "invalid input" begin + for (P_IN, P_OUT) in p_combs + + # filter out all valid combinations + if !((P_IN, P_OUT) in p_combs_valid) + @test_throws DimensionMismatch differential_cross_section( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + end + end + end + end + @testset "total cross section" begin + @testset "compute" begin + for P_IN in (p_in_phys, p_in_set_phys) + groundtruth = TestImplementation._groundtruth_total_cross_section(P_IN) + totCS_on_moms = total_cross_section( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN + ) + @test isapprox(totCS_on_moms, groundtruth, atol=ATOL, rtol=RTOL) + end + end + @testset "invalid input" begin + for P_IN in (p_in_phys_invalid, p_in_set_phys_invalid) + @test_throws DimensionMismatch total_cross_section( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN + ) + end + end + end + end + + @testset "differential probability" begin + @testset "unsafe compute" begin + for (P_IN, P_OUT) in p_combs_phys + prob = unsafe_differential_probability( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + groundtruth = TestImplementation._groundtruth_unsafe_probability( + TESTPROC, P_IN, P_OUT + ) + @test isapprox(prob, groundtruth, atol=ATOL, rtol=RTOL) + end + end + + @testset "unsafe invalid input" begin + for (P_IN, P_OUT) in p_combs + + # filter out all valid combinations + if !((P_IN, P_OUT) in p_combs_valid) + @test_throws DimensionMismatch unsafe_differential_probability( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + end + end + end + @testset "safe compute" begin + for (P_IN, P_OUT) in p_combs_valid + prob = differential_probability( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + groundtruth = TestImplementation._groundtruth_safe_probability( + TESTPROC, P_IN, P_OUT + ) + @test isapprox(prob, groundtruth, atol=ATOL, rtol=RTOL) + end + end + + @testset "safe invalid input" begin + for (P_IN, P_OUT) in p_combs + + # filter out all valid combinations + if !((P_IN, P_OUT) in p_combs_valid) + @test_throws DimensionMismatch differential_probability( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN, TESTPSDEF, P_OUT + ) + end + end + end + + @testset "total probability" begin + @testset "compute" begin + for P_IN in (p_in_phys, p_in_set_phys) + groundtruth = TestImplementation._groundtruth_total_probability(P_IN) + totCS_on_moms = total_probability(TESTPROC, TESTMODEL, TESTPSDEF, P_IN) + + @test isapprox(totCS_on_moms, groundtruth, atol=ATOL, rtol=RTOL) + end + end + @testset "invalid input" begin + for P_IN in (p_in_phys_invalid, p_in_set_phys_invalid) + @test_throws DimensionMismatch total_probability( + TESTPROC, TESTMODEL, TESTPSDEF, P_IN + ) + end + end + end + end +end diff --git a/test/interfaces/model_interface.jl b/test/interfaces/model_interface.jl index b094191..545609d 100644 --- a/test/interfaces/model_interface.jl +++ b/test/interfaces/model_interface.jl @@ -1,14 +1,13 @@ using QEDprocesses -struct TestModel <: AbstractModelDefinition end -fundamental_interaction_type(::TestModel) = :test_interaction - -struct TestModel_FAIL <: AbstractModelDefinition end +include("../test_implementation/TestImplementation.jl") @testset "hard interface" begin - @test fundamental_interaction_type(TestModel()) == :test_interaction + TESTMODEL = TestImplementation.TestModel() + @test fundamental_interaction_type(TESTMODEL) == :test_interaction end @testset "interface fail" begin - @test_throws MethodError fundamental_interaction_type(TestModel_FAIL()) + TESTMODEL_FAIL = TestImplementation.TestModel_FAIL() + @test_throws MethodError fundamental_interaction_type(TESTMODEL_FAIL) end diff --git a/test/interfaces/process_interface.jl b/test/interfaces/process_interface.jl index 16fa9e4..69135fd 100644 --- a/test/interfaces/process_interface.jl +++ b/test/interfaces/process_interface.jl @@ -1,4 +1,5 @@ using Random +using Suppressor using QEDbase using QEDprocesses @@ -6,219 +7,111 @@ RNG = MersenneTwister(137137) ATOL = 0.0 RTOL = sqrt(eps()) -function _rand_momenta(rng::AbstractRNG, N) - moms = Vector{SFourMomentum}(undef, N) - for i in 1:N - moms[i] = SFourMomentum(rand(rng, 4)) - end - return moms -end - -function _rand_momenta(rng::AbstractRNG, N1, N2) - moms = Matrix{SFourMomentum}(undef, N1, N2) - for i in 1:N1 - for j in 1:N2 - moms[i, j] = SFourMomentum(rand(rng, 4)) - end - end - return moms -end - -struct TestParticle1 <: AbstractParticle end -struct TestParticle2 <: AbstractParticle end -struct TestParticle3 <: AbstractParticle end -struct TestParticle4 <: AbstractParticle end - -PARTICLE_SET = [TestParticle1(), TestParticle2(), TestParticle3(), TestParticle4()] - -struct TestProcess <: AbstractProcessDefinition end -struct TestProcess_FAIL <: AbstractProcessDefinition end +include("../test_implementation/TestImplementation.jl") -struct TestModel <: AbstractModelDefinition end -struct TestModel_FAIL <: AbstractModelDefinition end - -_groundtruth_diffCS(initPS, finalPS) = sum(initPS) * sum(finalPS) -_groundtruth_totCS(initPS) = _groundtruth_diffCS(initPS, initPS) - -@testset "interface fail" begin - @test_throws MethodError incoming_particles(TestProcess_FAIL()) - @test_throws MethodError outgoing_particles(TestProcess_FAIL()) -end @testset "($N_INCOMING,$N_OUTGOING)" for (N_INCOMING, N_OUTGOING) in Iterators.product( (1, rand(RNG, 2:8)), (1, rand(RNG, 2:8)) ) - INCOMING_PARTICLES = rand(RNG, PARTICLE_SET, N_INCOMING) - OUTGOING_PARTICLES = rand(RNG, PARTICLE_SET, N_OUTGOING) - - QEDprocesses.incoming_particles(::TestProcess) = INCOMING_PARTICLES - QEDprocesses.outgoing_particles(::TestProcess) = OUTGOING_PARTICLES - - function QEDprocesses._differential_cross_section( - proc::TestProcess, - model::TestModel, - in_phasespace::AbstractVector{T}, - out_phasespace::AbstractVector{T}, - ) where {T<:QEDbase.AbstractFourMomentum} - return _groundtruth_diffCS(in_phasespace, out_phasespace) - end - - function QEDprocesses._total_cross_section( - proc::TestProcess, model::TestModel, in_phasespace::AbstractVector{T} - ) where {T<:QEDbase.AbstractFourMomentum} - return _groundtruth_totCS(in_phasespace) - end - - @testset "hard interface" begin - @test incoming_particles(TestProcess()) == INCOMING_PARTICLES - @test outgoing_particles(TestProcess()) == OUTGOING_PARTICLES - end - - @testset "delegated functions" begin - @test number_incoming_particles(TestProcess()) == N_INCOMING - @test number_outgoing_particles(TestProcess()) == N_OUTGOING - end - - @testset "cross section" begin - p_in = _rand_momenta(RNG, N_INCOMING) - p_out = _rand_momenta(RNG, N_OUTGOING) - p_in_set = _rand_momenta(RNG, N_INCOMING, 2) - p_out_set = _rand_momenta(RNG, N_OUTGOING, 2) - - @testset "interface fail" begin - @test_throws MethodError differential_cross_section( - TestProcess(), TestModel_FAIL(), p_in, p_out - ) - @test_throws MethodError total_cross_section( - TestProcess(), TestModel_FAIL(), p_in, p_out - ) + INCOMING_PARTICLES = rand(RNG, TestImplementation.PARTICLE_SET, N_INCOMING) + OUTGOING_PARTICLES = rand(RNG, TestImplementation.PARTICLE_SET, N_OUTGOING) + + TESTPROC = TestImplementation.TestProcess(INCOMING_PARTICLES, OUTGOING_PARTICLES) + TESTPROC_FAIL = TestImplementation.TestProcess_FAIL( + INCOMING_PARTICLES, OUTGOING_PARTICLES + ) + TESTMODEL = TestImplementation.TestModel() + TESTMODEL_FAIL = TestImplementation.TestModel_FAIL() + TESTPSDEF = TestImplementation.TestPhasespaceDef() + TESTPSDEF_FAIL = TestImplementation.TestPhasespaceDef_FAIL() + IN_PS = TestImplementation._rand_momenta(RNG, N_INCOMING) + OUT_PS = TestImplementation._rand_momenta(RNG, N_OUTGOING) + + @testset "failed interface" begin + @testset "failed process interface" begin + @test_throws MethodError incoming_particles(TESTPROC_FAIL) + @test_throws MethodError outgoing_particles(TESTPROC_FAIL) end - - @testset "differential cross section" begin - @testset "compute vector-vector" begin - diffCS = differential_cross_section(TestProcess(), TestModel(), p_in, p_out) - groundtruth = _groundtruth_diffCS(p_in, p_out) - @test isapprox(diffCS, groundtruth, atol=ATOL, rtol=RTOL) - end - - @testset "compute vector-matrix" begin - diffCS = differential_cross_section( - TestProcess(), TestModel(), p_in, p_out_set - ) - - groundtruth = Vector{QEDprocesses._base_component_type(p_in)}( - undef, size(p_out_set, 2) + @testset "$PROC $MODEL" for (PROC, MODEL) in Iterators.product( + (TESTPROC, TESTPROC_FAIL), (TESTMODEL, TESTMODEL_FAIL) + ) + in_ps = TestImplementation._rand_momenta(RNG, 2) + out_ps = TestImplementation._rand_momenta(RNG, 2) + if TestImplementation._any_fail(PROC, MODEL) + @test_throws MethodError QEDprocesses._incident_flux(PROC, MODEL, in_ps) + @test_throws MethodError QEDprocesses._averaging_norm(PROC, MODEL) + @test_throws MethodError QEDprocesses._matrix_element( + PROC, MODEL, in_ps, out_ps ) - for i in 1:size(p_out_set, 2) - groundtruth[i] = _groundtruth_diffCS(p_in, view(p_out_set, :, i)) - end - @test isapprox(diffCS, groundtruth, atol=ATOL, rtol=RTOL) end - @testset "compute matrix-vector" begin - diffCS = differential_cross_section( - TestProcess(), TestModel(), p_in_set, p_out - ) - groundtruth = Vector{QEDprocesses._base_component_type(p_in_set)}( - undef, size(p_in_set, 2) - ) - for i in 1:size(p_in_set, 2) - groundtruth[i] = _groundtruth_diffCS(view(p_in_set, :, i), p_out) + for (IN_PS_DEF, OUT_PS_DEF) in + Iterators.product((TESTPSDEF, TESTPSDEF_FAIL), (TESTPSDEF, TESTPSDEF_FAIL)) + if TestImplementation._any_fail(PROC, MODEL, IN_PS_DEF, OUT_PS_DEF) + @test_throws MethodError QEDprocesses._phase_space_factor( + PROC, MODEL, IN_PS_DEF, in_ps, OUT_PS_DEF, out_ps + ) end - @test isapprox(diffCS, groundtruth, atol=ATOL, rtol=RTOL) end + end + end + @testset "incoming/outgoing particles" begin + @test incoming_particles(TESTPROC) == INCOMING_PARTICLES + @test outgoing_particles(TESTPROC) == OUTGOING_PARTICLES + @test number_incoming_particles(TESTPROC) == N_INCOMING + @test number_outgoing_particles(TESTPROC) == N_OUTGOING + end - @testset "compute matrix-matrix" begin - diffCS = differential_cross_section( - TestProcess(), TestModel(), p_in_set, p_out_set - ) - groundtruth = Matrix{QEDprocesses._base_component_type(p_in_set)}( - undef, size(p_in_set, 2), size(p_out_set, 2) - ) - for i in 1:size(p_in_set, 2) - for j in 1:size(p_out_set, 2) - groundtruth[i, j] = _groundtruth_diffCS( - view(p_in_set, :, i), view(p_out_set, :, j) - ) - end - end - @test isapprox(diffCS, groundtruth, atol=ATOL, rtol=RTOL) - end + @testset "incident flux" begin + test_incident_flux = QEDprocesses._incident_flux(TESTPROC, TESTMODEL, IN_PS) + groundtruth = TestImplementation._groundtruth_incident_flux(IN_PS) + @test isapprox(test_incident_flux, groundtruth, atol=ATOL, rtol=RTOL) + end - @testset "fail vector-vector" begin - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), TestModel(), _rand_momenta(RNG, N_INCOMING + 1), p_out - ) - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), TestModel(), p_in, _rand_momenta(RNG, N_OUTGOING + 1) - ) - end + @testset "averaging norm" begin + test_avg_norm = QEDprocesses._averaging_norm(TESTPROC) + groundtruth = TestImplementation._groundtruth_averaging_norm(TESTPROC) + @test isapprox(test_avg_norm, groundtruth, atol=ATOL, rtol=RTOL) + end - @testset "fail vector-matrix" begin - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), - TestModel(), - _rand_momenta(RNG, N_INCOMING + 1), - p_out_set, - ) - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), TestModel(), p_in, _rand_momenta(RNG, N_OUTGOING + 1, 2) - ) - end + @testset "matrix element" begin + test_matrix_element = QEDprocesses._matrix_element( + TESTPROC, TESTMODEL, IN_PS, OUT_PS + ) + groundtruth = TestImplementation._groundtruth_matrix_element(IN_PS, OUT_PS) + @test length(test_matrix_element) == length(groundtruth) + for i in eachindex(test_matrix_element) + @test isapprox(test_matrix_element[i], groundtruth[i], atol=ATOL, rtol=RTOL) + end + end - @testset "fail matrix-vector" begin - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), TestModel(), _rand_momenta(RNG, N_INCOMING + 1, 2), p_out - ) - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), TestModel(), p_in_set, _rand_momenta(RNG, N_OUTGOING + 1) - ) - end + @testset "is in phasespace" begin + @test QEDprocesses._is_in_phasespace( + TESTPROC, TESTMODEL, TESTPSDEF, IN_PS, TESTPSDEF, OUT_PS + ) - @testset "fail matrix-matrix" begin - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), - TestModel(), - _rand_momenta(RNG, N_INCOMING + 1, 2), - p_out_set, - ) - @test_throws DimensionMismatch differential_cross_section( - TestProcess(), - TestModel(), - p_in_set, - _rand_momenta(RNG, N_OUTGOING + 1, 2), - ) - end - end - @testset "total cross section" begin - @testset "compute vector" begin - totCS = total_cross_section(TestProcess(), TestModel(), p_in) - groundtruth = _groundtruth_totCS(p_in) - @test isapprox(totCS, groundtruth, atol=ATOL, rtol=RTOL) - end + IN_PS_unphysical = deepcopy(IN_PS) + IN_PS_unphysical[1] = SFourMomentum(zeros(4)) - @testset "compute matrix" begin - totCS = total_cross_section(TestProcess(), TestModel(), p_in_set) + @test !QEDprocesses._is_in_phasespace( + TESTPROC, TESTMODEL, TESTPSDEF, IN_PS_unphysical, TESTPSDEF, OUT_PS + ) - groundtruth = Vector{QEDprocesses._base_component_type(p_in)}( - undef, size(p_in_set, 2) - ) - for i in 1:size(p_in_set, 2) - groundtruth[i] = _groundtruth_totCS(view(p_in_set, :, i)) - end - @test isapprox(totCS, groundtruth, atol=ATOL, rtol=RTOL) - end + OUT_PS_unphysical = deepcopy(OUT_PS) + OUT_PS_unphysical[end] = ones(SFourMomentum) - @testset "fail vector" begin - @test_throws DimensionMismatch total_cross_section( - TestProcess(), TestModel(), _rand_momenta(RNG, N_INCOMING + 1) - ) - end + @test !QEDprocesses._is_in_phasespace( + TESTPROC, TESTMODEL, TESTPSDEF, IN_PS, TESTPSDEF, OUT_PS_unphysical + ) + @test !QEDprocesses._is_in_phasespace( + TESTPROC, TESTMODEL, TESTPSDEF, IN_PS_unphysical, TESTPSDEF, OUT_PS_unphysical + ) + end - @testset "fail matrix" begin - @test_throws DimensionMismatch total_cross_section( - TestProcess(), TestModel(), _rand_momenta(RNG, N_INCOMING + 1, 2) - ) - end - end + @testset "phase space factor" begin + test_phase_space_factor = QEDprocesses._phase_space_factor( + TESTPROC, TESTMODEL, TESTPSDEF, IN_PS, TESTPSDEF, OUT_PS + ) + groundtruth = TestImplementation._groundtruth_phase_space_factor(IN_PS, OUT_PS) + @test isapprox(test_phase_space_factor, groundtruth, atol=ATOL, rtol=RTOL) end end diff --git a/test/interfaces/setup_interface.jl b/test/interfaces/setup_interface.jl index 27b013e..bdb8097 100644 --- a/test/interfaces/setup_interface.jl +++ b/test/interfaces/setup_interface.jl @@ -1,5 +1,6 @@ using Random +using Suppressor using QEDbase using QEDprocesses @@ -177,8 +178,8 @@ struct TestProcessSetupFAIL <: AbstractProcessSetup end INCOMING_PARTICLES = rand(RNG, PARTICLE_SET, N_INCOMING) OUTGOING_PARTICLES = rand(RNG, PARTICLE_SET, N_OUTGOING) - QEDprocesses.incoming_particles(::TestProcess) = INCOMING_PARTICLES - QEDprocesses.outgoing_particles(::TestProcess) = OUTGOING_PARTICLES + @suppress QEDprocesses.incoming_particles(::TestProcess) = INCOMING_PARTICLES + @suppress QEDprocesses.outgoing_particles(::TestProcess) = OUTGOING_PARTICLES @testset "delegated functions" begin stp = TestProcessSetup() diff --git a/test/runtests.jl b/test/runtests.jl index 70a68c0..470923a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,7 @@ using Test using SafeTestsets begin - # Interfaces + # # Interfaces @time @safetestset "model interface" begin include("interfaces/model_interface.jl") end @@ -18,4 +18,7 @@ begin @time @safetestset "propagators" begin include("propagators.jl") end + @time @safetestset "cross section & probability" begin + include("cross_sections.jl") + end end diff --git a/test/test_implementation/TestImplementation.jl b/test/test_implementation/TestImplementation.jl new file mode 100644 index 0000000..deba7c0 --- /dev/null +++ b/test/test_implementation/TestImplementation.jl @@ -0,0 +1,38 @@ +""" +This module provides a full implementation of the model and process interface. Its purpose is only for testing and it does not reflect any +real-world physics. + +The module exports: + +``` +TestParticle1 # set of test particles without properties +TestParticle2 +TestParticle3 +TestParticle4 +TestModel # dummy compute model +TestModel_FAIL # failing compute model +TestProcess # dummy scattering process +TestProcess_FAIL # failing scattering process +TestPhasespaceDef # dummy phase space definition +TestPhasespaceDef_FAIL # failing phase space definition +``` +The respective groundtruth implementations for the interface functions are stored in `groundtruths.jl` +""" +module TestImplementation + +export TestParticle1, TestParticle2, TestParticle3, TestParticle4, PARTICLE_SET +export TestModel, TestModel_FAIL +export TestProcess, TestProcess_FAIL +export TestPhasespaceDef, TestPhasespaceDef_FAIL + +using Random +using QEDbase +using QEDprocesses + +include("groundtruths.jl") +include("test_model.jl") +include("test_process.jl") +include("random_momenta.jl") +include("utils.jl") + +end diff --git a/test/test_implementation/groundtruths.jl b/test/test_implementation/groundtruths.jl new file mode 100644 index 0000000..22421cc --- /dev/null +++ b/test/test_implementation/groundtruths.jl @@ -0,0 +1,260 @@ + +""" + _groundtruth_incident_flux(in_ps) + +Test implementation of the incident flux. Return the Minkowski square of the sum of the incoming momenta: + +```math +\\begin{align} +I = \\left(\\sum p_i\\right)^2, +\\end{align} +``` +where \$p_i\\in\\mathrm{ps_in}\$. +""" +function _groundtruth_incident_flux(in_ps) + s = sum(in_ps) + return s * s +end + +""" + _groundtruth_matrix_element(in_ps, out_ps) + +Test implementation for a matrix elements. Returns a list of three complex numbers without any physical meaning. +""" +function _groundtruth_matrix_element(in_ps, out_ps) + s_in = sum(in_ps) + s_out = sum(out_ps) + res = s_in * s_in + 1im * (s_out * s_out) + return (res, 2 * res, 3 * res) +end + +""" + _groundtruth_averaging_norm(proc) + +Test implementation of the averaging norm. Returns the inverse of the sum of all external particles of the passed process. +""" +function _groundtruth_averaging_norm(proc) + return 1.0 / (number_incoming_particles(proc) + number_outgoing_particles(proc)) +end + +""" + _groundtruth_is_in_phasespace(in_ps, out_ps) + +Test implementation of the phase space check. Return `false` if either the momentum of the first incoming particle is exactly `zero(SFourMomentum)`, or if the momentum of the last outgoing momentum is exactly `ones(SFourMomentum)`. Otherwise, return true. +""" +function _groundtruth_is_in_phasespace(in_ps, out_ps) + if in_ps[1] == SFourMomentum(zeros(4)) + return false + end + if out_ps[end] == ones(SFourMomentum) + return false + end + return true +end + +""" + _groundtruth_phase_space_factor(in_ps, out_ps) + +Test implementation of the phase space factor. Return the inverse of the product of the energies of all external particles. +""" +function _groundtruth_phase_space_factor(in_ps, out_ps) + en_in = getE.(in_ps) + en_out = getE.(out_ps) + return 1 / (prod(en_in) * prod(en_out)) +end + +""" + _groundtruth_unsafe_probability(proc, in_ps, out_ps) + +Test implementation of the unsafe differential probability. Uses the test implementations of `_groundtruth_matrix_element`,`_groundtruth_averaging_norm` and `_groundtruth_phase_space_factor`. +""" +function _groundtruth_unsafe_probability(proc, in_ps, out_ps) + mat_el = _groundtruth_matrix_element(in_ps, out_ps) + mat_el_sq = abs2.(mat_el) + normalization = _groundtruth_averaging_norm(proc) + ps_fac = _groundtruth_phase_space_factor(in_ps, out_ps) + return sum(mat_el_sq) * ps_fac * normalization +end + +function _groundtruth_unsafe_probability( + proc, in_ps::AbstractVector, out_ps::AbstractMatrix +) + res = Vector{Float64}(undef, size(out_ps, 2)) + for i in 1:size(out_ps, 2) + res[i] = _groundtruth_unsafe_probability(proc, in_ps, view(out_ps, :, i)) + end + return res +end + +function _groundtruth_unsafe_probability( + proc, in_ps::AbstractMatrix, out_ps::AbstractVector +) + res = Vector{Float64}(undef, size(in_ps, 2)) + for i in 1:size(in_ps, 2) + res[i] = _groundtruth_unsafe_probability(proc, view(in_ps, :, i), out_ps) + end + return res +end + +function _groundtruth_unsafe_probability( + proc, in_ps::AbstractMatrix, out_ps::AbstractMatrix +) + res = Matrix{Float64}(undef, size(in_ps, 2), size(out_ps, 2)) + for i in 1:size(in_ps, 2) + for j in 1:size(out_ps, 2) + res[i, j] = _groundtruth_unsafe_probability( + proc, view(in_ps, :, i), view(out_ps, :, j) + ) + end + end + return res +end + +""" + _groundtruth_safe_probability(proc, in_ps, out_ps) + +Test implementation of the safe differential probability. Uses the test implementations of `_groundtruth_is_in_phasespace` and `_groundtruth_unsafe_probability`. +""" +function _groundtruth_safe_probability(proc, in_ps, out_ps) + if !_groundtruth_is_in_phasespace(in_ps, out_ps) + return zero(Float64) + end + return _groundtruth_unsafe_probability(proc, in_ps, out_ps) +end + +function _groundtruth_safe_probability(proc, in_ps::AbstractVector, out_ps::AbstractMatrix) + res = Vector{Float64}(undef, size(out_ps, 2)) + for i in 1:size(out_ps, 2) + res[i] = _groundtruth_safe_probability(proc, in_ps, view(out_ps, :, i)) + end + return res +end + +function _groundtruth_safe_probability(proc, in_ps::AbstractMatrix, out_ps::AbstractVector) + res = Vector{Float64}(undef, size(in_ps, 2)) + for i in 1:size(in_ps, 2) + res[i] = _groundtruth_safe_probability(proc, view(in_ps, :, i), out_ps) + end + return res +end + +function _groundtruth_safe_probability(proc, in_ps::AbstractMatrix, out_ps::AbstractMatrix) + res = Matrix{Float64}(undef, size(in_ps, 2), size(out_ps, 2)) + for i in 1:size(in_ps, 2) + for j in 1:size(out_ps, 2) + res[i, j] = _groundtruth_safe_probability( + proc, view(in_ps, :, i), view(out_ps, :, j) + ) + end + end + return res +end + +""" + _groundtruth_unsafe_diffCS(proc, in_ps, out_ps) + +Test implementation of the unsafe differential cross section. Uses the test implementations of `_groundtruth_incident_flux` and `_groundtruth_unsafe_probability`. +""" +function _groundtruth_unsafe_diffCS(proc, in_ps, out_ps) + init_flux = _groundtruth_incident_flux(in_ps) + return _groundtruth_unsafe_probability(proc, in_ps, out_ps) / (4 * init_flux) +end + +function _groundtruth_unsafe_diffCS(proc, in_ps::AbstractVector, out_ps::AbstractMatrix) + res = Vector{Float64}(undef, size(out_ps, 2)) + for i in 1:size(out_ps, 2) + res[i] = _groundtruth_unsafe_diffCS(proc, in_ps, view(out_ps, :, i)) + end + return res +end + +function _groundtruth_unsafe_diffCS(proc, in_ps::AbstractMatrix, out_ps::AbstractVector) + res = Vector{Float64}(undef, size(in_ps, 2)) + for i in 1:size(in_ps, 2) + res[i] = _groundtruth_unsafe_diffCS(proc, view(in_ps, :, i), out_ps) + end + return res +end + +function _groundtruth_unsafe_diffCS(proc, in_ps::AbstractMatrix, out_ps::AbstractMatrix) + res = Matrix{Float64}(undef, size(in_ps, 2), size(out_ps, 2)) + for i in 1:size(in_ps, 2) + for j in 1:size(out_ps, 2) + res[i, j] = _groundtruth_unsafe_diffCS( + proc, view(in_ps, :, i), view(out_ps, :, j) + ) + end + end + return res +end + +""" + _groundtruth_safe_diffCS(proc, in_ps, out_ps) + +Test implementation of the safe differential cross section. Uses the test implementations of `_groundtruth_is_in_phasespace` and `_groundtruth_unsafe_diffCS`. +""" +function _groundtruth_safe_diffCS(proc, in_ps, out_ps) + if !_groundtruth_is_in_phasespace(in_ps, out_ps) + return zero(Float64) + end + return _groundtruth_unsafe_diffCS(proc, in_ps, out_ps) +end + +function _groundtruth_safe_diffCS(proc, in_ps::AbstractVector, out_ps::AbstractMatrix) + res = Vector{Float64}(undef, size(out_ps, 2)) + for i in 1:size(out_ps, 2) + res[i] = _groundtruth_safe_diffCS(proc, in_ps, view(out_ps, :, i)) + end + return res +end + +function _groundtruth_safe_diffCS(proc, in_ps::AbstractMatrix, out_ps::AbstractVector) + res = Vector{Float64}(undef, size(in_ps, 2)) + for i in 1:size(in_ps, 2) + res[i] = _groundtruth_safe_diffCS(proc, view(in_ps, :, i), out_ps) + end + return res +end + +function _groundtruth_safe_diffCS(proc, in_ps::AbstractMatrix, out_ps::AbstractMatrix) + res = Matrix{Float64}(undef, size(in_ps, 2), size(out_ps, 2)) + for i in 1:size(in_ps, 2) + for j in 1:size(out_ps, 2) + res[i, j] = _groundtruth_safe_diffCS( + proc, view(in_ps, :, i), view(out_ps, :, j) + ) + end + end + return res +end + +""" + _groundtruth_total_probability(in_ps::AbstractVector) + +Test implementation of the total cross section. Return the Minkowski square of the sum the momenta of all incoming particles. +""" +function _groundtruth_total_probability(in_ps::AbstractVector) + Ptot = sum(in_ps) + return Ptot * Ptot +end + +function _groundtruth_total_probability(in_ps::AbstractMatrix) + res = Vector{Float64}(undef, size(in_ps, 2)) + for i in 1:size(in_ps, 2) + res[i] = _groundtruth_total_probability(view(in_ps, :, i)) + end + return res +end + +function _groundtruth_total_cross_section(in_ps) + init_flux = _groundtruth_incident_flux(in_ps) + return _groundtruth_total_probability(in_ps) / (4 * init_flux) +end + +function _groundtruth_total_cross_section(in_ps::AbstractMatrix) + res = Vector{Float64}(undef, size(in_ps, 2)) + for i in 1:size(in_ps, 2) + res[i] = _groundtruth_total_cross_section(view(in_ps, :, i)) + end + return res +end diff --git a/test/test_implementation/random_momenta.jl b/test/test_implementation/random_momenta.jl new file mode 100644 index 0000000..1972c16 --- /dev/null +++ b/test/test_implementation/random_momenta.jl @@ -0,0 +1,88 @@ + +""" +Return a vector of random four momenta, i.e. a random phase space point +""" +function _rand_momenta(rng::AbstractRNG, N) + moms = Vector{SFourMomentum}(undef, N) + for i in 1:N + moms[i] = SFourMomentum(rand(rng, 4)) + end + return moms +end + +""" +Return a matrix of random four momenta, i.e. a collection of phase space points +""" +function _rand_momenta(rng::AbstractRNG, N1, N2) + moms = Matrix{SFourMomentum}(undef, N1, N2) + for i in 1:N1 + for j in 1:N2 + moms[i, j] = SFourMomentum(rand(rng, 4)) + end + end + return moms +end + +""" +Return a random phase space point, which is failing the incoming phase space constraint, +i.e. the first entry of the vector is the null momentum. +""" +function _rand_in_momenta_failing(rng::AbstractRNG, N) + moms = _rand_momenta(rng, N) + moms[1] = zero(SFourMomentum) + return moms +end + +""" +Return a random phase space point, which is failing the outgoing phase space constraint, +i.e. the last entry of the vector is the unit momentum. +""" +function _rand_out_momenta_failing(rng::AbstractRNG, N) + moms = _rand_momenta(rng, N) + moms[end] = ones(SFourMomentum) + return moms +end + +""" +Return a collection of incoming phase space points, where the first point is failing the phase space constraint, +i.e. the first entry of the matrix is the null momentum, but the others pass. +""" +function _rand_in_momenta_failing_mix(rng::AbstractRNG, N1, N2) + moms = _rand_momenta(rng, N1, N2) + moms[1, 1] = zero(SFourMomentum) + return moms +end + +""" +Return a collection of incoming phase space points, where all points are failing the phase space constraint, +i.e. their first entries are null momenta. +""" +function _rand_in_momenta_failing_all(rng::AbstractRNG, N1, N2) + moms = _rand_momenta(rng, N1, N2) + for n in 1:N2 + moms[1, n] = zero(SFourMomentum) + end + return moms +end + +""" +Return a collection of outgoing phase space points, where the first point is failing the phase space constraint, +i.e. the last entry of the matrix is the unit momentum, but the others pass. +""" +function _rand_out_momenta_failing_mix(rng::AbstractRNG, N1, N2) + moms = _rand_momenta(rng, N1, N2) + moms[end, 1] = ones(SFourMomentum) + return moms +end + +""" +Return a collection of outgoing phase space points, where all points are failing the phase space constraint, +i.e. their last entries are unit momenta. +""" +function _rand_out_momenta_failing_all(rng::AbstractRNG, N1, N2) + moms = _rand_momenta(rng, N1, N2) + for n in 1:N2 + moms[end, n] = ones(SFourMomentum) + end + return moms +end diff --git a/test/test_implementation/test_model.jl b/test/test_implementation/test_model.jl new file mode 100644 index 0000000..4aa469b --- /dev/null +++ b/test/test_implementation/test_model.jl @@ -0,0 +1,5 @@ + +struct TestModel <: AbstractModelDefinition end +QEDprocesses.fundamental_interaction_type(::TestModel) = :test_interaction + +struct TestModel_FAIL <: AbstractModelDefinition end diff --git a/test/test_implementation/test_process.jl b/test/test_implementation/test_process.jl new file mode 100644 index 0000000..239e354 --- /dev/null +++ b/test/test_implementation/test_process.jl @@ -0,0 +1,91 @@ + +# dummy particles +struct TestParticle1 <: AbstractParticle end +struct TestParticle2 <: AbstractParticle end +struct TestParticle3 <: AbstractParticle end +struct TestParticle4 <: AbstractParticle end + +const PARTICLE_SET = [TestParticle1(), TestParticle2(), TestParticle3(), TestParticle4()] + +""" + + TestProcess(rng,incoming_particles,outgoing_particles) + +""" +struct TestProcess{IP<:AbstractVector,OP<:AbstractVector} <: AbstractProcessDefinition + incoming_particles::IP + outgoing_particles::OP +end + +function TestProcess(rng::AbstractRNG, N_in::Int, N_out::Int) + in_particles = rand(rng, PARTICLE_SET, N_in) + out_particles = rand(rng, PARTICLE_SET, N_out) + return TestProcess(in_particles, out_particles) +end + +QEDprocesses.incoming_particles(proc::TestProcess) = proc.incoming_particles +QEDprocesses.outgoing_particles(proc::TestProcess) = proc.outgoing_particles + +struct TestProcess_FAIL{IP<:AbstractVector,OP<:AbstractVector} <: AbstractProcessDefinition + incoming_particles::IP + outgoing_particles::OP +end + +function TestProcess_FAIL(rng::AbstractRNG, N_in::Int, N_out::Int) + in_particles = rand(rng, PARTICLE_SET, N_in) + out_particles = rand(rng, PARTICLE_SET, N_out) + return TestProcess_FAIL(in_particles, out_particles) +end + +# dummy phase space definition + failing phase space definition +struct TestPhasespaceDef <: AbstractPhasespaceDefinition end +struct TestPhasespaceDef_FAIL <: AbstractPhasespaceDefinition end + +# dummy implementation of the process interface + +function QEDprocesses._incident_flux( + ::TestProcess, ::TestModel, in_ps::AbstractVector{T} +) where {T<:QEDbase.AbstractFourMomentum} + return _groundtruth_incident_flux(in_ps) +end + +function QEDprocesses._averaging_norm(proc::TestProcess) + return _groundtruth_averaging_norm(proc) +end + +function QEDprocesses._matrix_element( + ::TestProcess, ::TestModel, in_ps::AbstractVector{T}, out_ps::AbstractVector{T} +) where {T<:QEDbase.AbstractFourMomentum} + return _groundtruth_matrix_element(in_ps, out_ps) +end + +function QEDprocesses._is_in_phasespace( + ::TestProcess, + ::TestModel, + in_ps_def::TestPhasespaceDef, + in_ps::AbstractVector{T}, + out_ps_def::TestPhasespaceDef, + out_ps::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + return _groundtruth_is_in_phasespace(in_ps, out_ps) +end + +function QEDprocesses._phase_space_factor( + ::TestProcess, + ::TestModel, + in_ps_def::TestPhasespaceDef, + in_ps::AbstractVector{T}, + out_ps_def::TestPhasespaceDef, + out_ps::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + return _groundtruth_phase_space_factor(in_ps, out_ps) +end + +function QEDprocesses._total_probability( + proc::TestProcess, + model::TestModel, + in_ps_def::TestPhasespaceDef, + in_ps::AbstractVector{T}, +) where {T<:QEDbase.AbstractFourMomentum} + return _groundtruth_total_probability(in_ps) +end diff --git a/test/test_implementation/utils.jl b/test/test_implementation/utils.jl new file mode 100644 index 0000000..b4d4f9d --- /dev/null +++ b/test/test_implementation/utils.jl @@ -0,0 +1,5 @@ + +# Check if any failed type is in the input +_any_fail(x...) = true +_any_fail(::TestProcess, ::TestModel) = false +_any_fail(::TestProcess, ::TestModel, ::TestPhasespaceDef, ::TestPhasespaceDef) = false