Refactor model into an interface and remove any ABC Model specific code from src/code_gen/. Also generate functions instead of direct code evaluation in execute()

This commit is contained in:
Anton Reinhard 2023-09-28 17:59:17 +02:00
parent a69dd6018e
commit 7dd9fedf2e
35 changed files with 489 additions and 827 deletions

View File

@ -1,5 +1,5 @@
indent = 4
margin = 80
margin = 120
always_for_in = true
for_in_replacement = "in"
whitespace_typedefs = true

View File

@ -1,5 +1,15 @@
# Models
## Interface
The interface that has to be implemented for a model to be usable is defined in `src/models/interface.jl`.
```@autodocs
Modules = [MetagraphOptimization]
Pages = ["models/interface.jl"]
Order = [:type, :constant, :function]
```
## ABC-Model
### Types

View File

@ -13,16 +13,15 @@ function bench_txt(filepath::String, bench::Bool = true)
return
end
model = ABCModel()
println(name, ":")
g = parse_abc(filepath)
g = parse_dag(filepath, model)
print(g)
println(
" Graph size in memory: ",
bytes_to_human_readable(MetagraphOptimization.mem(g)),
)
println(" Graph size in memory: ", bytes_to_human_readable(MetagraphOptimization.mem(g)))
if (bench)
@btime parse_abc($filepath)
@btime parse_dag($filepath, $model)
end
println(" Get Operations: ")

View File

@ -12,7 +12,7 @@ function gen_plot(filepath)
return
end
g = parse_abc(filepath)
g = parse_dag(filepath, ABCModel())
Random.seed!(1)
@ -48,23 +48,10 @@ function gen_plot(filepath)
println("\rDone.")
plot(
[x[1], x[2]],
[y[1], y[2]],
linestyle = :solid,
linewidth = 1,
color = :red,
legend = false,
)
plot([x[1], x[2]], [y[1], y[2]], linestyle = :solid, linewidth = 1, color = :red, legend = false)
# Create lines connecting the reference point to each data point
for i in 3:length(x)
plot!(
[x[i - 1], x[i]],
[y[i - 1], y[i]],
linestyle = :solid,
linewidth = 1,
color = :red,
)
plot!([x[i - 1], x[i]], [y[i - 1], y[i]], linestyle = :solid, linewidth = 1, color = :red)
end
return gui()

View File

@ -12,7 +12,7 @@ function gen_plot(filepath)
return
end
g = parse_abc(filepath)
g = parse_dag(filepath, ABCModel())
Random.seed!(1)
@ -60,14 +60,7 @@ function gen_plot(filepath)
push!(y, props.computeEffort)
pop_operation!(g)
push!(
names,
"NF: (" *
string(props.data) *
", " *
string(props.computeEffort) *
")",
)
push!(names, "NF: (" * string(props.data) * ", " * string(props.computeEffort) * ")")
end
for op in opt.nodeReductions
push_operation!(g, op)
@ -76,14 +69,7 @@ function gen_plot(filepath)
push!(y, props.computeEffort)
pop_operation!(g)
push!(
names,
"NR: (" *
string(props.data) *
", " *
string(props.computeEffort) *
")",
)
push!(names, "NR: (" * string(props.data) * ", " * string(props.computeEffort) * ")")
end
for op in opt.nodeSplits
push_operation!(g, op)
@ -92,33 +78,13 @@ function gen_plot(filepath)
push!(y, props.computeEffort)
pop_operation!(g)
push!(
names,
"NS: (" *
string(props.data) *
", " *
string(props.computeEffort) *
")",
)
push!(names, "NS: (" * string(props.data) * ", " * string(props.computeEffort) * ")")
end
plot(
[x0, x[1]],
[y0, y[1]],
linestyle = :solid,
linewidth = 1,
color = :red,
legend = false,
)
plot([x0, x[1]], [y0, y[1]], linestyle = :solid, linewidth = 1, color = :red, legend = false)
# Create lines connecting the reference point to each data point
for i in 2:length(x)
plot!(
[x0, x[i]],
[y0, y[i]],
linestyle = :solid,
linewidth = 1,
color = :red,
)
plot!([x0, x[i]], [y0, y[i]], linestyle = :solid, linewidth = 1, color = :red)
end
#scatter!(x, y, label=names)

View File

@ -6,20 +6,20 @@ julia --project=./examples -t 4 -e 'import Pkg; Pkg.instantiate()'
#for i in $(seq $minthreads $maxthreads)
# printf "(AB->AB, $i) "
# julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_abc("input/AB->AB.txt"))'
# julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_dag("input/AB->AB.txt"), ABCModel())'
#end
#for i in $(seq $minthreads $maxthreads)
# printf "(AB->ABBB, $i) "
# julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_abc("input/AB->ABBB.txt"))'
# julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_dag("input/AB->ABBB.txt"), ABCModel())'
#end
#for i in $(seq $minthreads $maxthreads)
# printf "(AB->ABBBBB, $i) "
# julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_abc("input/AB->ABBBBB.txt"))'
# julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_dag("input/AB->ABBBBB.txt"), ABCModel())'
#end
for i in $(seq $minthreads $maxthreads)
printf "(AB->ABBBBBBB, $i) "
julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_abc("input/AB->ABBBBBBB.txt"))'
julia --project=./examples -t $i -O3 -e 'using MetagraphOptimization; using BenchmarkTools; @btime get_operations(graph) setup=(graph = parse_dag("input/AB->ABBBBBBB.txt"), ABCModel())'
end

View File

@ -42,7 +42,6 @@ export can_pop
export reset_graph!
export get_operations
export parse_abc
export ComputeTaskP
export ComputeTaskS1
export ComputeTaskS2
@ -51,9 +50,12 @@ export ComputeTaskU
export ComputeTaskSum
export execute
export gen_particles
export parse_dag, parse_process
export gen_process_input
export get_compute_function
export ParticleValue
export Particle
export ParticleA, ParticleB, ParticleC
export ABCProcessDescription, ABCProcessInput, ABCModel
export ==, in, show, isempty, delete!, length
@ -114,6 +116,8 @@ include("task/compare.jl")
include("task/print.jl")
include("task/properties.jl")
include("models/interface.jl")
include("models/abc/types.jl")
include("models/abc/particle.jl")
include("models/abc/compute.jl")

View File

@ -3,7 +3,7 @@ using DataStructures
"""
gen_code(graph::DAG)
Generate the code for a given graph. The return value is a tuple of:
Generate the code for a given graph. The return value is a named tuple of:
- `code::Expr`: The julia expression containing the code for the whole graph.
- `inputSymbols::Dict{String, Vector{Symbol}}`: A dictionary of symbols mapping the names of the input nodes of the graph to the symbols their inputs should be provided on.
@ -47,85 +47,55 @@ function gen_code(graph::DAG)
# node is now the last node we looked at -> the output node
outSym = Symbol("data_$(to_var_name(node.id))")
return (
code = Expr(:block, code...),
inputSymbols = inputSyms,
outputSymbol = outSym,
)
return (code = Expr(:block, code...), inputSymbols = inputSyms, outputSymbol = outSym)
end
function gen_input_assignment_code(
inputSymbols::Dict{String, Vector{Symbol}},
inOutCount::Dict{ParticleType, Tuple{Int, Int}},
functionInputSymbol::Symbol = :input,
processDescription::AbstractProcessDescription,
processInputSymbol::Symbol = :input,
)
@assert !isempty(particles[1]) "Can't have 0 input particles!"
@assert !isempty(particles[2]) "Can't have 0 output particles!"
@assert length(inputSymbols) >= length(particles[1]) + length(particles[2])
# TODO none of this is very pretty
in_out_count = Dict{ParticleType, Tuple{Int, Int}}()
for type in types(particles[1][1])
in_out_count[type] = (0, 0)
end
# we assume that the particles with lower numbers are the inputs, and then the output particles follow
for p in particles[1]
(i, o) = in_out_count[p.type]
in_out_count[p.type] = (i + 1, o)
end
for p in particles[2]
(i, o) = in_out_count[p.type]
in_out_count[p.type] = (i, o + 1)
end
@assert length(inputSymbols) >=
sum(values(in_particles(processDescription))) + sum(values(out_particles(processDescription))) "Number of input Symbols is smaller than the number of particles in the process description"
assignInputs = Vector{Expr}()
for (name, symbols) in inputSymbols
type = nothing
if startswith(name, "A")
type = A
elseif startswith(name, "B")
type = B
else
type = C
end
type = type_from_name(name)
index = parse(Int, name[2:end])
p = nothing
if (index > in_out_count[type][1])
index -= in_out_count[type][1]
@assert index <= in_out_count[type][2] "Too few particles of type $type in input particles for this process"
if (index > in_particles(processDescription)[type])
index -= in_particles(processDescription)[type]
@assert index <= out_particles(processDescription)[type] "Too few particles of type $type in input particles for this process"
p = "$(functionInputSymbol)[2][$(index)]"
p = "filter(x -> typeof(x) <: $type, out_particles($(processInputSymbol)))[$(index)]"
else
p = "$(functionInputSymbol)[1][$(index)]"
p = "filter(x -> typeof(x) <: $type, in_particles($(processInputSymbol)))[$(index)]"
end
for symbol in symbols
push!(
assignInputs,
Meta.parse("$(symbol) = ParticleValue($p, 1.0)"),
)
push!(assignInputs, Meta.parse("$(symbol) = ParticleValue($p, 1.0)"))
end
end
return Expr(:block, assignInputs...)
end
"""
get_compute_function(graph::DAG, process::AbstractProcessDescription)
function get_compute_function(
graph::DAG,
input::Tuple{Vector{Particle}, Vector{Particle}},
)
Return a function of signature `compute_<id>(input::AbstractProcessInput)`, which will return the result of the DAG computation on the given input.
"""
function get_compute_function(graph::DAG, process::AbstractProcessDescription)
(code, inputSymbols, outputSymbol) = gen_code(graph)
assignInputs = gen_input_assignment_code(inputSymbols, input, :input)
assignInputs = gen_input_assignment_code(inputSymbols, process, :input)
function_id = to_var_name(UUIDs.uuid1(rng[1]))
func = eval(
Meta.parse(
"function compute_$(function_id)(input::Tuple{Vector{Particle}, Vector{Particle}}) $assignInputs; $code; return $outputSymbol; end",
"function compute_$(function_id)(input::AbstractProcessInput) $assignInputs; $code; return $outputSymbol; end",
),
)
@ -133,23 +103,20 @@ function get_compute_function(
end
"""
execute(graph::DAG, input::Dict{ParticleType, Vector{Particle}})
execute(graph::DAG, process::AbstractProcessDescription, input::AbstractProcessInput)
Execute the code of the given `graph` on the given input particles.
The input particles should be sorted correctly into the dictionary to their according [`ParticleType`](@ref)s.
This is essentially shorthand for
```julia
graph = parse_abc(input_file)
particles = gen_particles(...)
compute_graph = get_compute_function(graph, particles)
compute_graph = get_compute_function(graph, process)
result = compute_graph(particles)
```
See also: [`gen_particles`](@ref)
See also: [`parse_dag`](@ref), [`parse_process`](@ref), [`gen_process_input`](@ref)
"""
function execute(graph::DAG, input::Tuple{Vector{Particle}, Vector{Particle}})
func = get_compute_function(graph, input)
function execute(graph::DAG, process::AbstractProcessDescription, input::AbstractProcessInput)
func = get_compute_function(graph, process)
result = 0
try

View File

@ -26,10 +26,7 @@ function get_machine_info(verbose::Bool = Base.is_interactive())
noDevices = length(devices)
@assert noDevices > 0 "No devices were found, but at least one NUMA node should always be available!"
return Machine(
devices,
transferRates::Matrix{Float64}(-1, noDevices, noDevices),
)
return Machine(devices, transferRates::Matrix{Float64}(-1, noDevices, noDevices))
end
"""

View File

@ -3,10 +3,7 @@
Measure FLOPS, RAM, cache sizes and what other properties can be extracted for the devices in the given machine.
"""
function measure_devices!(
machine::Machine;
verbose::Bool = Base.is_interactive(),
)
function measure_devices!(machine::Machine; verbose::Bool = Base.is_interactive())
for device in machine.devices
measure_device!(device; verbose = verbose)
end
@ -19,10 +16,7 @@ end
Measure the transfer rates between devices in the machine.
"""
function measure_transfer_rates!(
machine::Machine;
verbose::Bool = Base.is_interactive(),
)
function measure_transfer_rates!(machine::Machine; verbose::Bool = Base.is_interactive())
return nothing
end

View File

@ -5,13 +5,7 @@ A named tuple representing a difference of added and removed nodes and edges on
"""
const Diff = NamedTuple{
(:addedNodes, :removedNodes, :addedEdges, :removedEdges, :updatedChildren),
Tuple{
Vector{Node},
Vector{Node},
Vector{Edge},
Vector{Edge},
Vector{Tuple{Node, String, String}},
},
Tuple{Vector{Node}, Vector{Node}, Vector{Edge}, Vector{Edge}, Vector{Tuple{Node, String, String}}},
}
function Diff()

View File

@ -38,8 +38,7 @@ end
Return `true` if [`pop_operation!`](@ref) is possible, `false` otherwise.
"""
can_pop(graph::DAG) =
!isempty(graph.operationsToApply) || !isempty(graph.appliedOperations)
can_pop(graph::DAG) = !isempty(graph.operationsToApply) || !isempty(graph.appliedOperations)
"""
reset_graph!(graph::DAG)

View File

@ -15,12 +15,7 @@ Insert the node into the graph.
See also: [`remove_node!`](@ref), [`insert_edge!`](@ref), [`remove_edge!`](@ref)
"""
function insert_node!(
graph::DAG,
node::Node;
track = true,
invalidate_cache = true,
)
function insert_node!(graph::DAG, node::Node; track = true, invalidate_cache = true)
# 1: mute
push!(graph.nodes, node)
@ -50,13 +45,7 @@ Insert the edge between node1 (child) and node2 (parent) into the graph.
See also: [`insert_node!`](@ref), [`remove_node!`](@ref), [`remove_edge!`](@ref)
"""
function insert_edge!(
graph::DAG,
node1::Node,
node2::Node;
track = true,
invalidate_cache = true,
)
function insert_edge!(graph::DAG, node1::Node, node2::Node; track = true, invalidate_cache = true)
@assert (node2 node1.parents) && (node1 node2.children) "Edge to insert already exists"
# 1: mute
@ -95,12 +84,7 @@ Remove the node from the graph.
See also: [`insert_node!`](@ref), [`insert_edge!`](@ref), [`remove_edge!`](@ref)
"""
function remove_node!(
graph::DAG,
node::Node;
track = true,
invalidate_cache = true,
)
function remove_node!(graph::DAG, node::Node; track = true, invalidate_cache = true)
@assert node in graph.nodes "Trying to remove a node that's not in the graph"
# 1: mute
@ -134,13 +118,7 @@ Remove the edge between node1 (child) and node2 (parent) into the graph.
See also: [`insert_node!`](@ref), [`remove_node!`](@ref), [`insert_edge!`](@ref)
"""
function remove_edge!(
graph::DAG,
node1::Node,
node2::Node;
track = true,
invalidate_cache = true,
)
function remove_edge!(graph::DAG, node1::Node, node2::Node; track = true, invalidate_cache = true)
# 1: mute
pre_length1 = length(node1.parents)
pre_length2 = length(node2.children)
@ -197,13 +175,7 @@ function replace_children!(task::AbstractTask, before, after)
return nothing
end
function update_child!(
graph::DAG,
n::Node,
child_before::String,
child_after::String;
track = true,
)
function update_child!(graph::DAG, n::Node, child_before::String, child_after::String; track = true)
# only need to update fused compute tasks
if !(typeof(n.task) <: FusedComputeTask)
return nothing
@ -212,16 +184,12 @@ function update_child!(
replace_children!(n.task, child_before, child_after)
if !((child_after in n.task.t1_inputs) || (child_after in n.task.t2_inputs))
println(
"------------------ Did not replace anything!! ------------------",
)
println("------------------ Did not replace anything!! ------------------")
child_ids = Vector{String}()
for child in n.children
push!(child_ids, "$(child.id)")
end
println(
"From $(child_before) to $(child_after) in $n with children $(child_ids)",
)
println("From $(child_before) to $(child_after) in $n with children $(child_ids)")
@assert false
end

View File

@ -62,9 +62,5 @@ function show(io::IO, graph::DAG)
properties = get_properties(graph)
println(io, " Total Compute Effort: ", properties.computeEffort)
println(io, " Total Data Transfer: ", properties.data)
return println(
io,
" Total Compute Intensity: ",
properties.computeIntensity,
)
return println(io, " Total Compute Intensity: ", properties.computeIntensity)
end

View File

@ -17,7 +17,7 @@ end
The representation of the graph as a set of [`Node`](@ref)s.
A DAG can be loaded using the appropriate parse function, e.g. [`parse_abc`](@ref).
A DAG can be loaded using the appropriate parse_dag function, e.g. [`parse_dag`](@ref).
[`Operation`](@ref)s can be applied on it using [`push_operation!`](@ref) and reverted using [`pop_operation!`](@ref) like a stack.
To get the set of possible operations, use [`get_operations`](@ref).
@ -52,11 +52,7 @@ end
Construct and return an empty [`PossibleOperations`](@ref) object.
"""
function PossibleOperations()
return PossibleOperations(
Set{NodeFusion}(),
Set{NodeReduction}(),
Set{NodeSplit}(),
)
return PossibleOperations(Set{NodeFusion}(), Set{NodeReduction}(), Set{NodeSplit}())
end
"""

View File

@ -45,14 +45,12 @@ For valid inputs, both input particles should have the same momenta at this poin
12 FLOP.
"""
function compute(::ComputeTaskS2, data1::ParticleValue, data2::ParticleValue)
@assert isapprox(
abs(data1.p.momentum.E),
abs(data2.p.momentum.E),
rtol = 0.001,
) "E: $(data1.p.momentum.E) vs. $(data2.p.momentum.E)"
@assert isapprox(data1.p.momentum.px, -data2.p.momentum.px, rtol = 0.001) "px: $(data1.p.momentum.px) vs. $(data2.p.momentum.px)"
@assert isapprox(data1.p.momentum.py, -data2.p.momentum.py, rtol = 0.001) "py: $(data1.p.momentum.py) vs. $(data2.p.momentum.py)"
@assert isapprox(data1.p.momentum.pz, -data2.p.momentum.pz, rtol = 0.001) "pz: $(data1.p.momentum.pz) vs. $(data2.p.momentum.pz)"
#=
@assert isapprox(abs(data1.p.momentum.E), abs(data2.p.momentum.E), rtol = 0.001, atol = sqrt(eps())) "E: $(data1.p.momentum.E) vs. $(data2.p.momentum.E)"
@assert isapprox(data1.p.momentum.px, -data2.p.momentum.px, rtol = 0.001, atol = sqrt(eps())) "px: $(data1.p.momentum.px) vs. $(data2.p.momentum.px)"
@assert isapprox(data1.p.momentum.py, -data2.p.momentum.py, rtol = 0.001, atol = sqrt(eps())) "py: $(data1.p.momentum.py) vs. $(data2.p.momentum.py)"
@assert isapprox(data1.p.momentum.pz, -data2.p.momentum.pz, rtol = 0.001, atol = sqrt(eps())) "pz: $(data1.p.momentum.pz) vs. $(data2.p.momentum.pz)"
=#
return data1.v * inner_edge(data1.p) * data2.v
end
@ -92,11 +90,7 @@ end
Generate and return code evaluating [`ComputeTaskP`](@ref) on `inExpr`, providing the output on `outExpr`.
"""
function get_expression(
::ComputeTaskP,
inExprs::Vector{String},
outExpr::String,
)
function get_expression(::ComputeTaskP, inExprs::Vector{String}, outExpr::String)
return Meta.parse("$outExpr = compute(ComputeTaskP(), $(inExprs[1]))")
end
@ -106,11 +100,7 @@ end
Generate code evaluating [`ComputeTaskU`](@ref) on `inExpr`, providing the output on `outExpr`.
`inExpr` should be of type [`ParticleValue`](@ref), `outExpr` will be of type [`ParticleValue`](@ref).
"""
function get_expression(
::ComputeTaskU,
inExprs::Vector{String},
outExpr::String,
)
function get_expression(::ComputeTaskU, inExprs::Vector{String}, outExpr::String)
return Meta.parse("$outExpr = compute(ComputeTaskU(), $(inExprs[1]))")
end
@ -120,14 +110,8 @@ end
Generate code evaluating [`ComputeTaskV`](@ref) on `inExpr1` and `inExpr2`, providing the output on `outExpr`.
`inExpr1` and `inExpr2` should be of type [`ParticleValue`](@ref), `outExpr` will be of type [`ParticleValue`](@ref).
"""
function get_expression(
::ComputeTaskV,
inExprs::Vector{String},
outExpr::String,
)
return Meta.parse(
"$outExpr = compute(ComputeTaskV(), $(inExprs[1]), $(inExprs[2]))",
)
function get_expression(::ComputeTaskV, inExprs::Vector{String}, outExpr::String)
return Meta.parse("$outExpr = compute(ComputeTaskV(), $(inExprs[1]), $(inExprs[2]))")
end
"""
@ -136,14 +120,8 @@ end
Generate code evaluating [`ComputeTaskS2`](@ref) on `inExpr1` and `inExpr2`, providing the output on `outExpr`.
`inExpr1` and `inExpr2` should be of type [`ParticleValue`](@ref), `outExpr` will be of type `Float64`.
"""
function get_expression(
::ComputeTaskS2,
inExprs::Vector{String},
outExpr::String,
)
return Meta.parse(
"$outExpr = compute(ComputeTaskS2(), $(inExprs[1]), $(inExprs[2]))",
)
function get_expression(::ComputeTaskS2, inExprs::Vector{String}, outExpr::String)
return Meta.parse("$outExpr = compute(ComputeTaskS2(), $(inExprs[1]), $(inExprs[2]))")
end
"""
@ -152,11 +130,7 @@ end
Generate code evaluating [`ComputeTaskS1`](@ref) on `inExpr`, providing the output on `outExpr`.
`inExpr` should be of type [`ParticleValue`](@ref), `outExpr` will be of type [`ParticleValue`](@ref).
"""
function get_expression(
::ComputeTaskS1,
inExprs::Vector{String},
outExpr::String,
)
function get_expression(::ComputeTaskS1, inExprs::Vector{String}, outExpr::String)
return Meta.parse("$outExpr = compute(ComputeTaskS1(), $(inExprs[1]))")
end
@ -166,14 +140,8 @@ end
Generate code evaluating [`ComputeTaskSum`](@ref) on `inExprs`, providing the output on `outExpr`.
`inExprs` should be of type [`Float64`], `outExpr` will be of type [`Float64`].
"""
function get_expression(
::ComputeTaskSum,
inExprs::Vector{String},
outExpr::String,
)
return Meta.parse(
"$outExpr = compute(ComputeTaskSum(), [$(unroll_string_vector(inExprs))])",
)
function get_expression(::ComputeTaskSum, inExprs::Vector{String}, outExpr::String)
return Meta.parse("$outExpr = compute(ComputeTaskSum(), [$(unroll_string_vector(inExprs))])")
end
"""
@ -182,19 +150,14 @@ end
Generate code evaluating a [`FusedComputeTask`](@ref) on `inExprs`, providing the output on `outExpr`.
`inExprs` should be of the correct types and may be heterogeneous. `outExpr` will be of the type of the output of `T2` of t.
"""
function get_expression(
t::FusedComputeTask,
inExprs::Vector{String},
outExpr::String,
)
function get_expression(t::FusedComputeTask, inExprs::Vector{String}, outExpr::String)
c1 = length(t.t1_inputs)
c2 = length(t.t2_inputs) + 1
expr1 = nothing
expr2 = nothing
expr1 = get_expression(t.first_task, t.t1_inputs, t.t1_output)
expr2 =
get_expression(t.second_task, [t.t2_inputs..., t.t1_output], outExpr)
expr2 = get_expression(t.second_task, [t.t2_inputs..., t.t1_output], outExpr)
full_expr = Expr(:block, expr1, expr2)
@ -208,7 +171,7 @@ Generate and return code for a given [`ComputeTaskNode`](@ref).
"""
function get_expression(node::ComputeTaskNode)
t = typeof(node.task)
@assert length(node.children) == children(node.task) "Node $(node) has inconsistent number of children"
@assert length(node.children) >= children(node.task) "Node $(node) has too few children for its task: node has $(length(node.children)) versus task has $(children(node.task))\nNode's children: $(getfield.(node.children, :children))"
if (t <: ComputeTaskU || t <: ComputeTaskP || t <: ComputeTaskS1) # single input
symbolIn = "data_$(to_var_name(node.children[1].id))"

View File

@ -6,51 +6,61 @@ using ForwardDiff
ComputeTaskSum() = ComputeTaskSum(0)
"""
gen_particles(in::Vector{ParticleType}, out::Vector{ParticleType})
gen_process_input(in::Vector{ParticleType}, out::Vector{ParticleType})
Return a Vector of randomly generated [`Particle`](@ref)s. `in` is the list of particles that enter the process, `out` the list of particles that exit it. Their added momenta will be equal.
Return a ProcessInput of randomly generated [`ABCParticle`](@ref)s from a [`ABCProcessDescription`](@ref). The process description can be created manually or parsed from a string using [`parse_process`](@ref).
Note: This does not take into account the preservation of momenta required for an actual valid process!
Note: This uses RAMBO to create a valid process with conservation of momentum and energy.
"""
function gen_particles(
in_particles::Vector{ParticleType},
out_particles::Vector{ParticleType},
)
particles = Dict{ParticleType, Vector{Particle}}()
rng = MersenneTwister(0)
function gen_process_input(processDescription::ABCProcessDescription)
inParticleTypes = keys(processDescription.inParticles)
outParticleTypes = keys(processDescription.outParticles)
mass_sum = 0
input_masses = Vector{Float64}()
for particle in in_particles
mass_sum += mass(particle)
push!(input_masses, mass(particle))
massSum = 0
inputMasses = Vector{Float64}()
for (particle, n) in processDescription.inParticles
for _ in 1:n
massSum += mass(particle)
push!(inputMasses, mass(particle))
end
end
output_masses = Vector{Float64}()
for particle in out_particles
mass_sum += mass(particle)
push!(output_masses, mass(particle))
outputMasses = Vector{Float64}()
for (particle, n) in processDescription.outParticles
for _ in 1:n
massSum += mass(particle)
push!(outputMasses, mass(particle))
end
end
# add some extra random mass to allow for some momentum
mass_sum += rand(rng) * (length(in_particles) + length(out_particles))
massSum += rand(rng[threadid()]) * (length(inputMasses) + length(outputMasses))
input_particles = Vector{Particle}()
initial_momenta = generate_initial_moms(mass_sum, input_masses)
for (mom, type) in zip(initial_momenta, in_particles)
push!(input_particles, Particle(mom, type))
inputParticles = Vector{ABCParticle}()
initialMomenta = generate_initial_moms(massSum, inputMasses)
index = 1
for (particle, n) in processDescription.inParticles
for _ in 1:n
mom = initialMomenta[index]
push!(inputParticles, particle(mom))
index += 1
end
end
output_particles = Vector{Particle}()
final_momenta = generate_physical_massive_moms(rng, mass_sum, output_masses)
for (mom, type) in zip(final_momenta, out_particles)
push!(
output_particles,
Particle(SFourMomentum(-mom.E, mom.px, mom.py, mom.pz), type),
)
outputParticles = Vector{ABCParticle}()
final_momenta = generate_physical_massive_moms(rng[threadid()], massSum, outputMasses)
index = 1
for (particle, n) in processDescription.outParticles
for _ in 1:n
mom = final_momenta[index]
push!(outputParticles, particle(SFourMomentum(-mom.E, mom.px, mom.py, mom.pz)))
index += 1
end
end
return (input_particles, output_particles)
processInput = ABCProcessInput(processDescription, inputParticles, outputParticles)
return return processInput
end
####################
@ -75,12 +85,8 @@ function generate_initial_moms(ss, masses)
end
Random.rand(rng::AbstractRNG, ::Random.SamplerType{SFourMomentum}) =
SFourMomentum(rand(rng, 4))
Random.rand(
rng::AbstractRNG,
::Random.SamplerType{NTuple{N, Float64}},
) where {N} = Tuple(rand(rng, N))
Random.rand(rng::AbstractRNG, ::Random.SamplerType{SFourMomentum}) = SFourMomentum(rand(rng, 4))
Random.rand(rng::AbstractRNG, ::Random.SamplerType{NTuple{N, Float64}}) where {N} = Tuple(rand(rng, N))
function _transform_uni_to_mom(u1, u2, u3, u4)
@ -109,8 +115,7 @@ function _transform_uni_to_mom!(uni_mom, dest)
end
_transform_uni_to_mom(u1234::Tuple) = _transform_uni_to_mom(u1234...)
_transform_uni_to_mom(u1234::SFourMomentum) =
_transform_uni_to_mom(Tuple(u1234))
_transform_uni_to_mom(u1234::SFourMomentum) = _transform_uni_to_mom(Tuple(u1234))
function generate_massless_moms(rng, n::Int)
a = Vector{SFourMomentum}(undef, n)

View File

@ -32,13 +32,13 @@ function parse_edges(input::AbstractString)
end
"""
parse_abc(filename::String; verbose::Bool = false)
parse_dag(filename::String, model::ABCModel; verbose::Bool = false)
Read an abc-model process from the given file. If `verbose` is set to true, print some progress information to stdout.
Returns a valid [`DAG`](@ref).
"""
function parse_abc(filename::String, verbose::Bool = false)
function parse_dag(filename::AbstractString, model::ABCModel, verbose::Bool = false)
file = open(filename, "r")
if (verbose)
@ -63,25 +63,9 @@ function parse_abc(filename::String, verbose::Bool = false)
end
sizehint!(graph.nodes, estimate_no_nodes)
sum_node = insert_node!(
graph,
make_node(ComputeTaskSum(0)),
track = false,
invalidate_cache = false,
)
global_data_out = insert_node!(
graph,
make_node(DataTask(FLOAT_SIZE)),
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
sum_node,
global_data_out,
track = false,
invalidate_cache = false,
)
sum_node = insert_node!(graph, make_node(ComputeTaskSum(0)), track = false, invalidate_cache = false)
global_data_out = insert_node!(graph, make_node(DataTask(FLOAT_SIZE)), track = false, invalidate_cache = false)
insert_edge!(graph, sum_node, global_data_out, track = false, invalidate_cache = false)
# remember the data out nodes for connection
dataOutNodes = Dict()
@ -96,10 +80,7 @@ function parse_abc(filename::String, verbose::Bool = false)
noNodes += 1
if (noNodes % 100 == 0)
if (verbose)
percent = string(
round(100.0 * noNodes / nodesToRead, digits = 2),
"%",
)
percent = string(round(100.0 * noNodes / nodesToRead, digits = 2), "%")
print("\rReading Nodes... $percent")
end
end
@ -111,59 +92,17 @@ function parse_abc(filename::String, verbose::Bool = false)
track = false,
invalidate_cache = false,
) # read particle data node
compute_P = insert_node!(
graph,
make_node(ComputeTaskP()),
track = false,
invalidate_cache = false,
) # compute P node
data_Pu = insert_node!(
graph,
make_node(DataTask(PARTICLE_VALUE_SIZE)),
track = false,
invalidate_cache = false,
) # transfer data from P to u (one ParticleValue object)
compute_u = insert_node!(
graph,
make_node(ComputeTaskU()),
track = false,
invalidate_cache = false,
) # compute U node
data_out = insert_node!(
graph,
make_node(DataTask(PARTICLE_VALUE_SIZE)),
track = false,
invalidate_cache = false,
) # transfer data out from u (one ParticleValue object)
compute_P = insert_node!(graph, make_node(ComputeTaskP()), track = false, invalidate_cache = false) # compute P node
data_Pu =
insert_node!(graph, make_node(DataTask(PARTICLE_VALUE_SIZE)), track = false, invalidate_cache = false) # transfer data from P to u (one ParticleValue object)
compute_u = insert_node!(graph, make_node(ComputeTaskU()), track = false, invalidate_cache = false) # compute U node
data_out =
insert_node!(graph, make_node(DataTask(PARTICLE_VALUE_SIZE)), track = false, invalidate_cache = false) # transfer data out from u (one ParticleValue object)
insert_edge!(
graph,
data_in,
compute_P,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
compute_P,
data_Pu,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
data_Pu,
compute_u,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
compute_u,
data_out,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, data_in, compute_P, track = false, invalidate_cache = false)
insert_edge!(graph, compute_P, data_Pu, track = false, invalidate_cache = false)
insert_edge!(graph, data_Pu, compute_u, track = false, invalidate_cache = false)
insert_edge!(graph, compute_u, data_out, track = false, invalidate_cache = false)
# remember the data_out node for future edges
dataOutNodes[node] = data_out
@ -173,27 +112,13 @@ function parse_abc(filename::String, verbose::Bool = false)
in1 = capt.captures[1]
in2 = capt.captures[2]
compute_v = insert_node!(
graph,
make_node(ComputeTaskV()),
track = false,
invalidate_cache = false,
)
data_out = insert_node!(
graph,
make_node(DataTask(PARTICLE_VALUE_SIZE)),
track = false,
invalidate_cache = false,
)
compute_v = insert_node!(graph, make_node(ComputeTaskV()), track = false, invalidate_cache = false)
data_out =
insert_node!(graph, make_node(DataTask(PARTICLE_VALUE_SIZE)), track = false, invalidate_cache = false)
if (occursin(regex_c, in1))
# put an S node after this input
compute_S = insert_node!(
graph,
make_node(ComputeTaskS1()),
track = false,
invalidate_cache = false,
)
compute_S = insert_node!(graph, make_node(ComputeTaskS1()), track = false, invalidate_cache = false)
data_S_v = insert_node!(
graph,
make_node(DataTask(PARTICLE_VALUE_SIZE)),
@ -201,47 +126,18 @@ function parse_abc(filename::String, verbose::Bool = false)
invalidate_cache = false,
)
insert_edge!(
graph,
dataOutNodes[in1],
compute_S,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
compute_S,
data_S_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, dataOutNodes[in1], compute_S, track = false, invalidate_cache = false)
insert_edge!(graph, compute_S, data_S_v, track = false, invalidate_cache = false)
insert_edge!(
graph,
data_S_v,
compute_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, data_S_v, compute_v, track = false, invalidate_cache = false)
else
insert_edge!(
graph,
dataOutNodes[in1],
compute_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, dataOutNodes[in1], compute_v, track = false, invalidate_cache = false)
end
if (occursin(regex_c, in2))
# i think the current generator only puts the combined particles in the first space, so this case might never be entered
# put an S node after this input
compute_S = insert_node!(
graph,
make_node(ComputeTaskS1()),
track = false,
invalidate_cache = false,
)
compute_S = insert_node!(graph, make_node(ComputeTaskS1()), track = false, invalidate_cache = false)
data_S_v = insert_node!(
graph,
make_node(DataTask(PARTICLE_VALUE_SIZE)),
@ -249,45 +145,15 @@ function parse_abc(filename::String, verbose::Bool = false)
invalidate_cache = false,
)
insert_edge!(
graph,
dataOutNodes[in2],
compute_S,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
compute_S,
data_S_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, dataOutNodes[in2], compute_S, track = false, invalidate_cache = false)
insert_edge!(graph, compute_S, data_S_v, track = false, invalidate_cache = false)
insert_edge!(
graph,
data_S_v,
compute_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, data_S_v, compute_v, track = false, invalidate_cache = false)
else
insert_edge!(
graph,
dataOutNodes[in2],
compute_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, dataOutNodes[in2], compute_v, track = false, invalidate_cache = false)
end
insert_edge!(
graph,
compute_v,
data_out,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, compute_v, data_out, track = false, invalidate_cache = false)
dataOutNodes[node] = data_out
elseif occursin(regex_m, node)
@ -298,84 +164,23 @@ function parse_abc(filename::String, verbose::Bool = false)
in3 = capt.captures[3]
# in2 + in3 with a v
compute_v = insert_node!(
graph,
make_node(ComputeTaskV()),
track = false,
invalidate_cache = false,
)
data_v = insert_node!(
graph,
make_node(DataTask(PARTICLE_VALUE_SIZE)),
track = false,
invalidate_cache = false,
)
compute_v = insert_node!(graph, make_node(ComputeTaskV()), track = false, invalidate_cache = false)
data_v =
insert_node!(graph, make_node(DataTask(PARTICLE_VALUE_SIZE)), track = false, invalidate_cache = false)
insert_edge!(
graph,
dataOutNodes[in2],
compute_v,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
dataOutNodes[in3],
compute_v,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
compute_v,
data_v,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, dataOutNodes[in2], compute_v, track = false, invalidate_cache = false)
insert_edge!(graph, dataOutNodes[in3], compute_v, track = false, invalidate_cache = false)
insert_edge!(graph, compute_v, data_v, track = false, invalidate_cache = false)
# combine with the v of the combined other input
compute_S2 = insert_node!(
graph,
make_node(ComputeTaskS2()),
track = false,
invalidate_cache = false,
)
data_out = insert_node!(
graph,
make_node(DataTask(FLOAT_SIZE)),
track = false,
invalidate_cache = false,
) # output of a S2 task is only a float
compute_S2 = insert_node!(graph, make_node(ComputeTaskS2()), track = false, invalidate_cache = false)
data_out = insert_node!(graph, make_node(DataTask(FLOAT_SIZE)), track = false, invalidate_cache = false) # output of a S2 task is only a float
insert_edge!(
graph,
data_v,
compute_S2,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
dataOutNodes[in1],
compute_S2,
track = false,
invalidate_cache = false,
)
insert_edge!(
graph,
compute_S2,
data_out,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, data_v, compute_S2, track = false, invalidate_cache = false)
insert_edge!(graph, dataOutNodes[in1], compute_S2, track = false, invalidate_cache = false)
insert_edge!(graph, compute_S2, data_out, track = false, invalidate_cache = false)
insert_edge!(
graph,
data_out,
sum_node,
track = false,
invalidate_cache = false,
)
insert_edge!(graph, data_out, sum_node, track = false, invalidate_cache = false)
add_child!(sum_node.task)
elseif occursin(regex_plus, node)
if (verbose)
@ -383,9 +188,7 @@ function parse_abc(filename::String, verbose::Bool = false)
println("Added ", length(graph.nodes), " nodes")
end
else
@assert false (
"Unknown node '$node' while reading from file $filename"
)
@assert false ("Unknown node '$node' while reading from file $filename")
end
end
@ -404,3 +207,36 @@ function parse_abc(filename::String, verbose::Bool = false)
# don't actually need to read the edges
return graph
end
"""
parse_process(string::AbstractString, model::ABCModel)
Parse a string representation of a process, such as "AB->ABBB" into the corresponding [`ABCProcessDescription`](@ref).
"""
function parse_process(str::AbstractString, model::ABCModel)
inParticles = Dict{Type, Int}()
outParticles = Dict{Type, Int}()
if !(contains(str, "->"))
throw("Did not find -> while parsing process \"$str\"")
end
(inStr, outStr) = split(str, "->")
if (isempty(inStr) || isempty(outStr))
throw("Process (\"$str\") input or output part is empty!")
end
for t in types(model)
inParticles[t] = count(x -> x == String(t)[1], inStr)
outParticles[t] = count(x -> x == String(t)[1], outStr)
end
if length(inStr) != sum(values(inParticles))
throw("Encountered unknown characters in the input part of process \"$str\"")
elseif length(outStr) != sum(values(outParticles))
throw("Encountered unknown characters in the output part of process \"$str\"")
end
return ABCProcessDescription(inParticles, outParticles)
end

View File

@ -1,108 +1,106 @@
using QEDbase
"""
ParticleType
abstract type ABCParticle <: AbstractParticle end
struct ABCModel <: AbstractPhysicsModel end
struct ABCProcessDescription <: AbstractProcessDescription
inParticles::Dict{Type, Int}
outParticles::Dict{Type, Int}
end
struct ABCProcessInput <: AbstractProcessInput
process::ABCProcessDescription
inParticles::Vector{ABCParticle}
outParticles::Vector{ABCParticle}
end
A Particle Type in the ABC Model as an enum, with types `A`, `B` and `C`.
"""
@enum ParticleType A = 1 B = 2 C = 3
ParticleA <: ABCParticle
An 'A' particle in the ABC Model.
"""
struct ParticleA <: ABCParticle
momentum::SFourMomentum
end
struct ParticleB <: ABCParticle
momentum::SFourMomentum
end
struct ParticleC <: ABCParticle
momentum::SFourMomentum
end
"""
PARTICLE_MASSES
A constant dictionary containing the masses of the different [`ParticleType`](@ref)s.
"""
const PARTICLE_MASSES =
Dict{ParticleType, Float64}(A => 1.0, B => 1.0, C => 0.0)
const PARTICLE_MASSES = Dict{Type, Float64}(ParticleA => 1.0, ParticleB => 1.0, ParticleC => 0.0)
"""
Particle
A struct describing a particle of the ABC-Model. It has the 4 momentum of the particle and a [`ParticleType`](@ref).
`sizeof(Particle())` = 40 Byte
"""
struct Particle
# SFourMomentum
momentum::SFourMomentum
type::ParticleType
end
"""
ParticleValue
A struct describing a particle during a calculation of a Feynman Diagram, together with the value that's being calculated.
`sizeof(ParticleValue())` = 48 Byte
"""
struct ParticleValue
p::Particle
v::Float64
end
"""
mass(t::ParticleType)
mass(t::Type{T}) where {T <: ABCParticle}
Return the mass (at rest) of the given particle type.
"""
mass(t::ParticleType) = PARTICLE_MASSES[t]
mass(t::Type{T}) where {T <: ABCParticle} = PARTICLE_MASSES[t]
"""
remaining_type(t1::ParticleType, t2::ParticleType)
interaction_result(t1::Type{T1}, t2::Type{T2}) where {T1 <: ABCParticle, T2 <: ABCParticle}
For 2 given (non-equal) particle types, return the third of ABC.
"""
function remaining_type(t1::ParticleType, t2::ParticleType)
function interaction_result(t1::Type{T1}, t2::Type{T2}) where {T1 <: ABCParticle, T2 <: ABCParticle}
@assert t1 != t2
if t1 != A && t2 != A
return A
elseif t1 != B && t2 != B
return B
if t1 != Type{ParticleA} && t2 != Type{ParticleA}
return ParticleA
elseif t1 != Type{ParticleB} && t2 != Type{ParticleB}
return ParticleB
else
return C
return ParticleC
end
end
"""
types(::Particle)
types(::ABCModel)
Return a Vector of the possible [`ParticleType`](@ref)s of this [`Particle`](@ref).
Return a Vector of the possible types of particle in the [`ABCModel`](@ref).
"""
function types(::Particle)
return [A, B, C]
function types(::ABCModel)
return [ParticleA, ParticleB, ParticleC]
end
"""
square(p::Particle)
square(p::ABCParticle)
Return the square of the particle's momentum as a `Float` value.
Takes 7 effective FLOP.
"""
function square(p::Particle)
function square(p::ABCParticle)
return getMass2(p.momentum)
end
"""
inner_edge(p::Particle)
inner_edge(p::ABCParticle)
Return the factor of the inner edge with the given (virtual) particle.
Takes 10 effective FLOP. (3 here + 7 in square(p))
"""
function inner_edge(p::Particle)
return 1.0 / (square(p) - mass(p.type) * mass(p.type))
function inner_edge(p::ABCParticle)
return 1.0 / (square(p) - mass(typeof(p)) * mass(typeof(p)))
end
"""
outer_edge(p::Particle)
outer_edge(p::ABCParticle)
Return the factor of the outer edge with the given (real) particle.
Takes 0 effective FLOP.
"""
function outer_edge(p::Particle)
function outer_edge(p::ABCParticle)
return 1.0
end
@ -120,14 +118,15 @@ function vertex()
end
"""
preserve_momentum(p1::Particle, p2::Particle)
preserve_momentum(p1::ABCParticle, p2::ABCParticle)
Calculate and return a new particle from two given interacting ones at a vertex.
Takes 4 effective FLOP.
"""
function preserve_momentum(p1::Particle, p2::Particle)
p3 = Particle(p1.momentum + p2.momentum, remaining_type(p1.type, p2.type))
function preserve_momentum(p1::ABCParticle, p2::ABCParticle)
t3 = interaction_result(typeof(p1), typeof(p2))
p3 = t3(p1.momentum + p2.momentum)
return p3
end
@ -135,16 +134,42 @@ end
"""
type_from_name(name::String)
For a name of a particle, return the [`ParticleType`].
For a name of a particle, return the particle's [`Type`].
"""
function type_from_name(name::String)
if startswith(name, "A")
return A
return ParticleA
elseif startswith(name, "B")
return B
return ParticleB
elseif startswith(name, "C")
return C
return ParticleC
else
throw("Invalid name for a particle in the ABC model")
end
end
function String(::Type{ParticleA})
return "A"
end
function String(::Type{ParticleB})
return "B"
end
function String(::Type{ParticleC})
return "C"
end
function in_particles(process::ABCProcessDescription)
return process.inParticles
end
function in_particles(input::ABCProcessInput)
return input.inParticles
end
function out_particles(process::ABCProcessDescription)
return process.outParticles
end
function out_particles(input::ABCProcessInput)
return input.outParticles
end

View File

@ -56,12 +56,4 @@ end
Constant vector of all tasks of the ABC-Model.
"""
ABC_TASKS = [
DataTask,
ComputeTaskS1,
ComputeTaskS2,
ComputeTaskP,
ComputeTaskV,
ComputeTaskU,
ComputeTaskSum,
]
ABC_TASKS = [DataTask, ComputeTaskS1, ComputeTaskS2, ComputeTaskP, ComputeTaskV, ComputeTaskU, ComputeTaskSum]

109
src/models/interface.jl Normal file
View File

@ -0,0 +1,109 @@
"""
AbstractPhysicsModel
Base type for a model, e.g. ABC-Model or QED. This is used to dispatch many functions.
"""
abstract type AbstractPhysicsModel end
"""
AbstractParticle
Base type for particles belonging to a certain [`AbstractPhysicsModel`](@ref).
"""
abstract type AbstractParticle end
"""
ParticleValue{ParticleType <: AbstractParticle}
A struct describing a particle during a calculation of a Feynman Diagram, together with the value that's being calculated.
`sizeof(ParticleValue())` = 48 Byte
"""
struct ParticleValue{ParticleType <: AbstractParticle}
p::ParticleType
v::Float64
end
"""
AbstractProcessDescription
Base type for process descriptions. An object of this type of a corresponding [`AbstractPhysicsModel`](@ref) should uniquely identify a process in that model.
See also: [`parse_process`](@ref)
"""
abstract type AbstractProcessDescription end
"""
AbstractProcessInput
Base type for process inputs. An object of this type contains the input values (e.g. momenta) of the particles in a process.
See also: [`gen_process_input`](@ref)
"""
abstract type AbstractProcessInput end
"""
mass(t::Type{T}) where {T <: AbstractParticle}
Interface function that must be implemented for every subtype of [`AbstractParticle`](@ref), returning the particles mass at rest.
"""
function mass end
"""
interaction_result(t1::Type{T1}, t2::Type{T2}) where {T1 <: AbstractParticle, T2 <: AbstractParticle}
Interface function that must be implemented for every subtype of [`AbstractParticle`](@ref), returning the result particle type when the two given particles interact.
"""
function interaction_result end
"""
types(::AbstractPhysicsModel)
Interface function that must be implemented for every subtype of [`AbstractPhysicsModel`](@ref), returning a `Vector` of the available particle types in the model.
"""
function types end
"""
in_particles(::AbstractProcessDescription)
Interface function that must be implemented for every subtype of [`AbstractProcessDescription`](@ref).
Returns a `<: Dict{Type{AbstractParticle}, Int}` object, representing the number of ingoing particles for the process per particle type.
in_particles(::AbstractProcessInput)
Interface function that must be implemented for every subtype of [`AbstractProcessInput`](@ref).
Returns a `<: Vector{AbstractParticle}` object with the values of all ingoing particles for the corresponding `ProcessDescription`.
"""
function in_particles end
"""
out_particles(::AbstractProcessDescription)
Interface function that must be implemented for every subtype of [`AbstractProcessDescription`](@ref).
Returns a `<: Dict{Type{AbstractParticle}, Int}` object, representing the number of outgoing particles for the process per particle type.
out_particles(::AbstractProcessInput)
Interface function that must be implemented for every subtype of [`AbstractProcessInput`](@ref).
Returns a `<: Vector{AbstractParticle}` object with the values of all outgoing particles for the corresponding `ProcessDescription`.
"""
function out_particles end
"""
parse_process(::AbstractString, ::AbstractPhysicsModel)
Interface function that must be implemented for every subtype of [`AbstractPhysicsModel`](@ref).
Returns a `ProcessDescription` object.
"""
function parse_process end
"""
gen_process_input(::AbstractProcessDescription)
Interface function that must be implemented for every specific [`AbstractProcessDescription`](@ref).
Returns a randomly generated and valid corresponding `ProcessInput`.
"""
function gen_process_input end

View File

@ -1,14 +1,6 @@
DataTaskNode(t::AbstractDataTask, name = "") = DataTaskNode(
t,
Vector{Node}(),
Vector{Node}(),
UUIDs.uuid1(rng[threadid()]),
missing,
missing,
missing,
name,
)
DataTaskNode(t::AbstractDataTask, name = "") =
DataTaskNode(t, Vector{Node}(), Vector{Node}(), UUIDs.uuid1(rng[threadid()]), missing, missing, missing, name)
ComputeTaskNode(t::AbstractComputeTask) = ComputeTaskNode(
t,
Vector{Node}(),

View File

@ -95,8 +95,5 @@ The child is the prerequisite node of the parent.
"""
struct Edge
# edge points from child to parent
edge::Union{
Tuple{DataTaskNode, ComputeTaskNode},
Tuple{ComputeTaskNode, DataTaskNode},
}
edge::Union{Tuple{DataTaskNode, ComputeTaskNode}, Tuple{ComputeTaskNode, DataTaskNode}}
end

View File

@ -34,12 +34,7 @@ Apply the given [`NodeFusion`](@ref) to the graph. Generic wrapper around [`node
Return an [`AppliedNodeFusion`](@ref) object generated from the graph's [`Diff`](@ref).
"""
function apply_operation!(graph::DAG, operation::NodeFusion)
diff = node_fusion!(
graph,
operation.input[1],
operation.input[2],
operation.input[3],
)
diff = node_fusion!(graph, operation.input[1], operation.input[2], operation.input[3])
graph.properties += GraphProperties(diff)
@ -156,12 +151,7 @@ Fuse nodes n1 -> n2 -> n3 together into one node, return the applied difference
For details see [`NodeFusion`](@ref).
"""
function node_fusion!(
graph::DAG,
n1::ComputeTaskNode,
n2::DataTaskNode,
n3::ComputeTaskNode,
)
function node_fusion!(graph::DAG, n1::ComputeTaskNode, n2::DataTaskNode, n3::ComputeTaskNode)
@assert is_valid_node_fusion_input(graph, n1, n2, n3)
# clear snapshot
@ -193,15 +183,7 @@ function node_fusion!(
end
# create new node with the fused compute task
new_node = ComputeTaskNode(
FusedComputeTask(
n1.task,
n3.task,
n1_inputs,
"data_$(to_var_name(n2.id))",
n3_inputs,
),
)
new_node = ComputeTaskNode(FusedComputeTask(n1.task, n3.task, n1_inputs, "data_$(to_var_name(n2.id))", n3_inputs))
insert_node!(graph, new_node)
for child in n1_children
@ -222,12 +204,7 @@ function node_fusion!(
# important! update the parent node's child names in case they are fused compute tasks
# needed for compute generation so the fused compute task can correctly match inputs to its component tasks
update_child!(
graph,
parent,
"data_$(to_var_name(n3.id))",
"data_$(to_var_name(new_node.id))",
)
update_child!(graph, parent, "data_$(to_var_name(n3.id))", "data_$(to_var_name(new_node.id))")
end
return get_snapshot_diff(graph)
@ -327,12 +304,7 @@ function node_split!(graph::DAG, n1::Node)
insert_edge!(graph, child, n_copy)
end
update_child!(
graph,
parent,
"data_$(to_var_name(n1.id))",
"data_$(to_var_name(n_copy.id))",
)
update_child!(graph, parent, "data_$(to_var_name(n1.id))", "data_$(to_var_name(n_copy.id))")
end
return get_snapshot_diff(graph)

View File

@ -7,10 +7,7 @@ using Base.Threads
Insert the given node fusion into its input nodes' operation caches. For the compute nodes, locking via the given `locks` is employed to have safe multi-threading. For a large set of nodes, contention on the locks should be very small.
"""
function insert_operation!(
nf::NodeFusion,
locks::Dict{ComputeTaskNode, SpinLock},
)
function insert_operation!(nf::NodeFusion, locks::Dict{ComputeTaskNode, SpinLock})
n1 = nf.input[1]
n2 = nf.input[2]
n3 = nf.input[3]
@ -52,10 +49,7 @@ end
Insert the node reductions into the graph and the nodes' caches. Employs multithreading for speedup.
"""
function nr_insertion!(
operations::PossibleOperations,
nodeReductions::Vector{Vector{NodeReduction}},
)
function nr_insertion!(operations::PossibleOperations, nodeReductions::Vector{Vector{NodeReduction}})
total_len = 0
for vec in nodeReductions
total_len += length(vec)
@ -83,11 +77,7 @@ end
Insert the node fusions into the graph and the nodes' caches. Employs multithreading for speedup.
"""
function nf_insertion!(
graph::DAG,
operations::PossibleOperations,
nodeFusions::Vector{Vector{NodeFusion}},
)
function nf_insertion!(graph::DAG, operations::PossibleOperations, nodeFusions::Vector{Vector{NodeFusion}})
total_len = 0
for vec in nodeFusions
total_len += length(vec)
@ -122,10 +112,7 @@ end
Insert the node splits into the graph and the nodes' caches. Employs multithreading for speedup.
"""
function ns_insertion!(
operations::PossibleOperations,
nodeSplits::Vector{Vector{NodeSplit}},
)
function ns_insertion!(operations::PossibleOperations, nodeSplits::Vector{Vector{NodeSplit}})
total_len = 0
for vec in nodeSplits
total_len += length(vec)
@ -231,16 +218,12 @@ function generate_operations(graph::DAG)
continue
end
push!(
generatedFusions[threadid()],
NodeFusion((child_node, node, parent_node)),
)
push!(generatedFusions[threadid()], NodeFusion((child_node, node, parent_node)))
end
end
# launch thread for node fusion insertion
nf_task =
@task nf_insertion!(graph, graph.possibleOperations, generatedFusions)
nf_task = @task nf_insertion!(graph, graph.possibleOperations, generatedFusions)
schedule(nf_task)
# find possible node splits

View File

@ -4,9 +4,7 @@
Return whether `operations` is empty, i.e. all of its fields are empty.
"""
function isempty(operations::PossibleOperations)
return isempty(operations.nodeFusions) &&
isempty(operations.nodeReductions) &&
isempty(operations.nodeSplits)
return isempty(operations.nodeFusions) && isempty(operations.nodeReductions) && isempty(operations.nodeSplits)
end
"""
@ -63,9 +61,7 @@ function can_fuse(n1::ComputeTaskNode, n2::DataTaskNode, n3::ComputeTaskNode)
return false
end
if length(n2.parents) != 1 ||
length(n2.children) != 1 ||
length(n1.parents) != 1
if length(n2.parents) != 1 || length(n2.children) != 1 || length(n1.parents) != 1
return false
end

View File

@ -9,24 +9,12 @@ Assert for a gven node fusion input whether the nodes can be fused. For the requ
Intended for use with `@assert` or `@test`.
"""
function is_valid_node_fusion_input(
graph::DAG,
n1::ComputeTaskNode,
n2::DataTaskNode,
n3::ComputeTaskNode,
)
function is_valid_node_fusion_input(graph::DAG, n1::ComputeTaskNode, n2::DataTaskNode, n3::ComputeTaskNode)
if !(n1 in graph) || !(n2 in graph) || !(n3 in graph)
throw(
AssertionError(
"[Node Fusion] The given nodes are not part of the given graph",
),
)
throw(AssertionError("[Node Fusion] The given nodes are not part of the given graph"))
end
if !is_child(n1, n2) ||
!is_child(n2, n3) ||
!is_parent(n3, n2) ||
!is_parent(n2, n1)
if !is_child(n1, n2) || !is_child(n2, n3) || !is_parent(n3, n2) || !is_parent(n2, n1)
throw(
AssertionError(
"[Node Fusion] The given nodes are not connected by edges which is required for node fusion",
@ -35,25 +23,13 @@ function is_valid_node_fusion_input(
end
if length(n2.parents) > 1
throw(
AssertionError(
"[Node Fusion] The given data node has more than one parent",
),
)
throw(AssertionError("[Node Fusion] The given data node has more than one parent"))
end
if length(n2.children) > 1
throw(
AssertionError(
"[Node Fusion] The given data node has more than one child",
),
)
throw(AssertionError("[Node Fusion] The given data node has more than one child"))
end
if length(n1.parents) > 1
throw(
AssertionError(
"[Node Fusion] The given n1 has more than one parent",
),
)
throw(AssertionError("[Node Fusion] The given n1 has more than one parent"))
end
return true
@ -69,31 +45,19 @@ Intended for use with `@assert` or `@test`.
function is_valid_node_reduction_input(graph::DAG, nodes::Vector{Node})
for n in nodes
if n graph
throw(
AssertionError(
"[Node Reduction] The given nodes are not part of the given graph",
),
)
throw(AssertionError("[Node Reduction] The given nodes are not part of the given graph"))
end
end
t = typeof(nodes[1].task)
for n in nodes
if typeof(n.task) != t
throw(
AssertionError(
"[Node Reduction] The given nodes are not of the same type",
),
)
throw(AssertionError("[Node Reduction] The given nodes are not of the same type"))
end
if (typeof(n) <: DataTaskNode)
if (n.name != nodes[1].name)
throw(
AssertionError(
"[Node Reduction] The given nodes do not have the same name",
),
)
throw(AssertionError("[Node Reduction] The given nodes do not have the same name"))
end
end
end
@ -121,11 +85,7 @@ Intended for use with `@assert` or `@test`.
"""
function is_valid_node_split_input(graph::DAG, n1::Node)
if n1 graph
throw(
AssertionError(
"[Node Split] The given node is not part of the given graph",
),
)
throw(AssertionError("[Node Split] The given node is not part of the given graph"))
end
if length(n1.parents) <= 1
@ -173,12 +133,7 @@ Assert for a given [`NodeFusion`](@ref) whether it is a valid operation in the g
Intended for use with `@assert` or `@test`.
"""
function is_valid(graph::DAG, nf::NodeFusion)
@assert is_valid_node_fusion_input(
graph,
nf.input[1],
nf.input[2],
nf.input[3],
)
@assert is_valid_node_fusion_input(graph, nf.input[1], nf.input[2], nf.input[3])
@assert nf in graph.possibleOperations.nodeFusions "NodeFusion is not part of the graph's possible operations!"
return true
end

View File

@ -11,8 +11,7 @@ function -(prop1::GraphProperties, prop2::GraphProperties)
computeIntensity = if (prop1.data - prop2.data == 0)
0.0
else
(prop1.computeEffort - prop2.computeEffort) /
(prop1.data - prop2.data)
(prop1.computeEffort - prop2.computeEffort) / (prop1.data - prop2.data)
end,
cost = prop1.cost - prop2.cost,
noNodes = prop1.noNodes - prop2.noNodes,
@ -33,8 +32,7 @@ function +(prop1::GraphProperties, prop2::GraphProperties)
computeIntensity = if (prop1.data + prop2.data == 0)
0.0
else
(prop1.computeEffort + prop2.computeEffort) /
(prop1.data + prop2.data)
(prop1.computeEffort + prop2.computeEffort) / (prop1.data + prop2.data)
end,
cost = prop1.cost + prop2.cost,
noNodes = prop1.noNodes + prop2.noNodes,

View File

@ -3,8 +3,7 @@
Fallback implementation of the copy of an abstract data task, throwing an error.
"""
copy(t::AbstractDataTask) =
error("Need to implement copying for your data tasks!")
copy(t::AbstractDataTask) = error("Need to implement copying for your data tasks!")
"""
copy(t::AbstractComputeTask)
@ -28,9 +27,5 @@ function copy(t::FusedComputeTask{T1, T2}) where {T1, T2}
)
end
FusedComputeTask{T1, T2}(
t1_inputs::Vector{String},
t1_output::String,
t2_inputs::Vector{String},
) where {T1, T2} =
FusedComputeTask{T1, T2}(t1_inputs::Vector{String}, t1_output::String, t2_inputs::Vector{String}) where {T1, T2} =
FusedComputeTask{T1, T2}(T1(), T2(), t1_inputs, t1_output, t2_inputs)

View File

@ -26,8 +26,7 @@ A fused compute task made up of the computation of first `T1` and then `T2`.
Also see: [`get_types`](@ref).
"""
struct FusedComputeTask{T1 <: AbstractComputeTask, T2 <: AbstractComputeTask} <:
AbstractComputeTask
struct FusedComputeTask{T1 <: AbstractComputeTask, T2 <: AbstractComputeTask} <: AbstractComputeTask
first_task::T1
second_task::T2
# the names of the inputs for T1

View File

@ -2,7 +2,7 @@ using Random
function test_known_graph(name::String, n, fusion_test = true)
@testset "Test $name Graph ($n)" begin
graph = parse_abc(joinpath(@__DIR__, "..", "input", "$name.txt"))
graph = parse_dag(joinpath(@__DIR__, "..", "input", "$name.txt"), ABCModel())
props = get_properties(graph)
if (fusion_test)

View File

@ -1,93 +1,73 @@
import MetagraphOptimization.A
import MetagraphOptimization.B
import MetagraphOptimization.ParticleType
import MetagraphOptimization.ABCParticle
using QEDbase
include("../examples/profiling_utilities.jl")
@testset "Unit Tests Execution" begin
particles_2_2 = Tuple{Vector{Particle}, Vector{Particle}}((
[
Particle(SFourMomentum(0.823648, 0.0, 0.0, 0.823648), A),
Particle(SFourMomentum(0.823648, 0.0, 0.0, -0.823648), B),
],
[
Particle(
SFourMomentum(0.823648, -0.835061, -0.474802, 0.277915),
A,
),
Particle(SFourMomentum(0.823648, 0.835061, 0.474802, -0.277915), B),
],
))
process_2_2 = ABCProcessDescription(
Dict{Type, Int64}(ParticleA => 1, ParticleB => 1),
Dict{Type, Int64}(ParticleA => 1, ParticleB => 1),
)
expected_result = 5.5320505145845555e-5
particles_2_2 = ABCProcessInput(
process_2_2,
ABCParticle[
ParticleA(SFourMomentum(0.823648, 0.0, 0.0, 0.823648)),
ParticleB(SFourMomentum(0.823648, 0.0, 0.0, -0.823648)),
],
ABCParticle[
ParticleA(SFourMomentum(0.823648, -0.835061, -0.474802, 0.277915)),
ParticleB(SFourMomentum(0.823648, 0.835061, 0.474802, -0.277915)),
],
)
expected_result = 0.00013916495566048735
@testset "AB->AB no optimization" begin
for _ in 1:10 # test in a loop because graph layout should not change the result
graph = parse_abc(joinpath(@__DIR__, "..", "input", "AB->AB.txt"))
@test isapprox(
execute(graph, particles_2_2),
expected_result;
rtol = 0.001,
)
graph = parse_dag(joinpath(@__DIR__, "..", "input", "AB->AB.txt"), ABCModel())
@test isapprox(execute(graph, process_2_2, particles_2_2), expected_result; rtol = 0.001)
#=code = MetagraphOptimization.gen_code(graph)
@test isapprox(
execute(code, particles_2_2),
expected_result;
rtol = 0.001,
)=#
func = get_compute_function(graph, process_2_2)
@test isapprox(func(particles_2_2), expected_result; rtol = 0.001)
end
end
@testset "AB->AB after random walk" begin
for i in 1:100
graph = parse_abc(joinpath(@__DIR__, "..", "input", "AB->AB.txt"))
graph = parse_dag(joinpath(@__DIR__, "..", "input", "AB->AB.txt"), ABCModel())
random_walk!(graph, 50)
@test is_valid(graph)
@test isapprox(
execute(graph, particles_2_2),
expected_result;
rtol = 0.001,
)
@test isapprox(execute(graph, process_2_2, particles_2_2), expected_result; rtol = 0.001)
end
end
particles_2_4 = gen_particles([A, B], [A, B, B, B])
graph = parse_abc(joinpath(@__DIR__, "..", "input", "AB->ABBB.txt"))
expected_result = execute(graph, particles_2_4)
process_2_4 = ABCProcessDescription(
Dict{Type, Int64}(ParticleA => 1, ParticleB => 1),
Dict{Type, Int64}(ParticleA => 1, ParticleB => 3),
)
particles_2_4 = gen_process_input(process_2_4)
graph = parse_dag(joinpath(@__DIR__, "..", "input", "AB->ABBB.txt"), ABCModel())
expected_result = execute(graph, process_2_4, particles_2_4)
@testset "AB->ABBB no optimization" begin
for _ in 1:5 # test in a loop because graph layout should not change the result
graph = parse_abc(joinpath(@__DIR__, "..", "input", "AB->ABBB.txt"))
@test isapprox(
execute(graph, particles_2_4),
expected_result;
rtol = 0.001,
)
graph = parse_dag(joinpath(@__DIR__, "..", "input", "AB->ABBB.txt"), ABCModel())
@test isapprox(execute(graph, process_2_4, particles_2_4), expected_result; rtol = 0.001)
#=code = MetagraphOptimization.gen_code(graph)
@test isapprox(
execute(code, particles_2_4),
expected_result;
rtol = 0.001,
)=#
func = get_compute_function(graph, process_2_4)
@test isapprox(func(particles_2_4), expected_result; rtol = 0.001)
end
end
@testset "AB->ABBB after random walk" begin
for i in 1:20
graph = parse_abc(joinpath(@__DIR__, "..", "input", "AB->ABBB.txt"))
graph = parse_dag(joinpath(@__DIR__, "..", "input", "AB->ABBB.txt"), ABCModel())
random_walk!(graph, 100)
@test is_valid(graph)
@test isapprox(
execute(graph, particles_2_4),
expected_result;
rtol = 0.001,
)
@test isapprox(execute(graph, process_2_4, particles_2_4), expected_result; rtol = 0.001)
end
end

View File

@ -11,10 +11,8 @@ import MetagraphOptimization.partners
@test length(graph.appliedOperations) == 0
@test length(graph.operationsToApply) == 0
@test length(graph.dirtyNodes) == 0
@test length(graph.diff) ==
(addedNodes = 0, removedNodes = 0, addedEdges = 0, removedEdges = 0)
@test length(get_operations(graph)) ==
(nodeFusions = 0, nodeReductions = 0, nodeSplits = 0)
@test length(graph.diff) == (addedNodes = 0, removedNodes = 0, addedEdges = 0, removedEdges = 0)
@test length(get_operations(graph)) == (nodeFusions = 0, nodeReductions = 0, nodeSplits = 0)
# s to output (exit node)
d_exit = insert_node!(graph, make_node(DataTask(10)), track = false)
@ -107,8 +105,7 @@ import MetagraphOptimization.partners
@test length(graph.appliedOperations) == 0
@test length(graph.operationsToApply) == 0
@test length(graph.dirtyNodes) == 26
@test length(graph.diff) ==
(addedNodes = 0, removedNodes = 0, addedEdges = 0, removedEdges = 0)
@test length(graph.diff) == (addedNodes = 0, removedNodes = 0, addedEdges = 0, removedEdges = 0)
@test is_valid(graph)
@ -135,8 +132,7 @@ import MetagraphOptimization.partners
@test length(siblings(s0)) == 1
operations = get_operations(graph)
@test length(operations) ==
(nodeFusions = 10, nodeReductions = 0, nodeSplits = 0)
@test length(operations) == (nodeFusions = 10, nodeReductions = 0, nodeSplits = 0)
@test length(graph.dirtyNodes) == 0
@test operations == get_operations(graph)
@ -157,8 +153,7 @@ import MetagraphOptimization.partners
@test length(graph.operationsToApply) == 1
@test first(graph.operationsToApply) == nf
@test length(graph.dirtyNodes) == 0
@test length(graph.diff) ==
(addedNodes = 0, removedNodes = 0, addedEdges = 0, removedEdges = 0)
@test length(graph.diff) == (addedNodes = 0, removedNodes = 0, addedEdges = 0, removedEdges = 0)
# this applies pending operations
properties = get_properties(graph)
@ -176,8 +171,7 @@ import MetagraphOptimization.partners
operations = get_operations(graph)
@test length(graph.dirtyNodes) == 0
@test length(operations) ==
(nodeFusions = 9, nodeReductions = 0, nodeSplits = 0)
@test length(operations) == (nodeFusions = 9, nodeReductions = 0, nodeSplits = 0)
@test !isempty(operations)
possibleNF = 9
@ -185,14 +179,12 @@ import MetagraphOptimization.partners
push_operation!(graph, first(operations.nodeFusions))
operations = get_operations(graph)
possibleNF = possibleNF - 1
@test length(operations) ==
(nodeFusions = possibleNF, nodeReductions = 0, nodeSplits = 0)
@test length(operations) == (nodeFusions = possibleNF, nodeReductions = 0, nodeSplits = 0)
end
@test isempty(operations)
@test length(operations) ==
(nodeFusions = 0, nodeReductions = 0, nodeSplits = 0)
@test length(operations) == (nodeFusions = 0, nodeReductions = 0, nodeSplits = 0)
@test length(graph.dirtyNodes) == 0
@test length(graph.nodes) == 6
@test length(graph.appliedOperations) == 10
@ -213,8 +205,7 @@ import MetagraphOptimization.partners
@test properties.computeIntensity 28 / 62
operations = get_operations(graph)
@test length(operations) ==
(nodeFusions = 10, nodeReductions = 0, nodeSplits = 0)
@test length(operations) == (nodeFusions = 10, nodeReductions = 0, nodeSplits = 0)
@test is_valid(graph)
end

View File

@ -3,8 +3,7 @@
nC1 = MetagraphOptimization.make_node(MetagraphOptimization.ComputeTaskU())
nC2 = MetagraphOptimization.make_node(MetagraphOptimization.ComputeTaskV())
nC3 = MetagraphOptimization.make_node(MetagraphOptimization.ComputeTaskP())
nC4 =
MetagraphOptimization.make_node(MetagraphOptimization.ComputeTaskSum())
nC4 = MetagraphOptimization.make_node(MetagraphOptimization.ComputeTaskSum())
nD1 = MetagraphOptimization.make_node(MetagraphOptimization.DataTask(10))
nD2 = MetagraphOptimization.make_node(MetagraphOptimization.DataTask(20))

View File

@ -5,9 +5,7 @@
@test MetagraphOptimization.bytes_to_human_readable(1025) == "1.001 KiB"
@test MetagraphOptimization.bytes_to_human_readable(684235) == "668.2 KiB"
@test MetagraphOptimization.bytes_to_human_readable(86214576) == "82.22 MiB"
@test MetagraphOptimization.bytes_to_human_readable(9241457698) ==
"8.607 GiB"
@test MetagraphOptimization.bytes_to_human_readable(3218598654367) ==
"2.927 TiB"
@test MetagraphOptimization.bytes_to_human_readable(9241457698) == "8.607 GiB"
@test MetagraphOptimization.bytes_to_human_readable(3218598654367) == "2.927 TiB"
end
println("Utility Unit Tests Complete!")