diff --git a/src/FeynmanDiagram.jl b/src/FeynmanDiagram.jl index 8d25dbc8..18683500 100644 --- a/src/FeynmanDiagram.jl +++ b/src/FeynmanDiagram.jl @@ -112,7 +112,7 @@ export Graph, FeynmanGraph, FeynmanProperties export isequiv, drop_topology, is_external, is_internal, diagram_type, orders, vertices, topology export external_legs, external_indices, external_operators, external_labels -export linear_combination, feynman_diagram, propagator, interaction, external_vertex +export multi_product, linear_combination, feynman_diagram, propagator, interaction, external_vertex # export DiagramType, Interaction, ExternalVertex, Propagator, SelfEnergy, VertexDiag, GreenDiag, GenericDiag # export standardize_order! diff --git a/src/backend/compiler.jl b/src/backend/compiler.jl index c63b33f3..6559b418 100644 --- a/src/backend/compiler.jl +++ b/src/backend/compiler.jl @@ -1,6 +1,6 @@ module Compilers using ..ComputationalGraphs -import ..ComputationalGraphs: FeynmanGraph +import ..ComputationalGraphs: id, name, set_name!, operator, subgraphs, subgraph_factors, factor using ..AbstractTrees using ..RuntimeGeneratedFunctions diff --git a/src/backend/static.jl b/src/backend/static.jl index 21ccdc18..d20b71c8 100644 --- a/src/backend/static.jl +++ b/src/backend/static.jl @@ -1,4 +1,16 @@ -function _to_static(::Type{ComputationalGraphs.Sum}, subgraphs::Vector{Graph{F,W}}, subgraph_factors::Vector{F}) where {F,W} +""" + function to_static(operator::Type, subgraphs::AbstractVector{<:AbstractGraph}, subgraph_factors::AbstractVector) + +Returns the static representation of a computational graph node `g` with operator `operator`, subgraphs `subgraphs`, and subgraph factors `subgraph_factors`. +""" +function to_static(operator::Type, subgraphs::AbstractVector{<:AbstractGraph}, subgraph_factors::AbstractVector) + error( + "Static representation for computational graph nodes with operator $(operator) not yet implemented! " * + "Please define a method `to_static(::Type{$(operator)}, subgraphs::$(typeof(subgraphs)), subgraph_factors::$(typeof(subgraph_factors)))`." + ) +end + +function to_static(::Type{ComputationalGraphs.Sum}, subgraphs::Vector{Graph{F,W}}, subgraph_factors::Vector{F}) where {F,W} if length(subgraphs) == 1 factor_str = subgraph_factors[1] == 1 ? "" : " * $(subgraph_factors[1])" return "(g$(subgraphs[1].id)$factor_str)" @@ -8,7 +20,7 @@ function _to_static(::Type{ComputationalGraphs.Sum}, subgraphs::Vector{Graph{F,W end end -function _to_static(::Type{ComputationalGraphs.Prod}, subgraphs::Vector{Graph{F,W}}, subgraph_factors::Vector{F}) where {F,W} +function to_static(::Type{ComputationalGraphs.Prod}, subgraphs::Vector{Graph{F,W}}, subgraph_factors::Vector{F}) where {F,W} if length(subgraphs) == 1 factor_str = subgraph_factors[1] == 1 ? "" : " * $(subgraph_factors[1])" return "(g$(subgraphs[1].id)$factor_str)" @@ -34,7 +46,7 @@ function _to_static(::Type{ComputationalGraphs.Sum}, subgraphs::Vector{FeynmanGr end end -function _to_static(::Type{ComputationalGraphs.Prod}, subgraphs::Vector{FeynmanGraph{F,W}}, subgraph_factors::Vector{F}) where {F,W} +function to_static(::Type{ComputationalGraphs.Prod}, subgraphs::Vector{FeynmanGraph{F,W}}, subgraph_factors::Vector{F}) where {F,W} if length(subgraphs) == 1 factor_str = subgraph_factors[1] == 1 ? "" : " * $(subgraph_factors[1])" return "(g$(subgraphs[1].id)$factor_str)" @@ -50,12 +62,12 @@ function _to_static(::Type{ComputationalGraphs.Power{N}}, subgraphs::Vector{Feyn end """ - function to_julia_str(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVector{Int}=[g.id for g in graphs], name::String="eval_graph!") + function to_julia_str(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVector{Int}=[id(g) for g in graphs], name::String="eval_graph!") Compile a list of graphs into a string for a julia static function. The function takes two arguments: `root` and `leaf`. `root` is a vector of the root node ids of the graphs, and `leaf` is a vector of the leaf nodes' weights of the graphs. """ -function to_julia_str(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVector{Int}=[g.id for g in graphs], name::String="eval_graph!") +function to_julia_str(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVector{Int}=[id(g) for g in graphs], name::String="eval_graph!") head = "function $name(root::AbstractVector, leaf::AbstractVector)\n " body = "" leafidx = 1 @@ -63,23 +75,24 @@ function to_julia_str(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVec inds_visitednode = Int[] for graph in graphs for g in PostOrderDFS(graph) #leaf first search - target = "g$(g.id)" + g_id = id(g) + target = "g$(g_id)" isroot = false - if g.id in root - target_root = "root[$(findfirst(x -> x == g.id, root))]" + if g_id in root + target_root = "root[$(findfirst(x -> x == g_id, root))]" isroot = true end - if isempty(g.subgraphs) #leaf - g.id in inds_visitedleaf && continue - factor_str = g.factor == 1 ? "" : " * $(g.factor)" + if isempty(subgraphs(g)) #leaf + g_id in inds_visitedleaf && continue + factor_str = factor(g) == 1 ? "" : " * $(factor(g))" body *= " $target = leaf[$leafidx]$factor_str\n " leafidx += 1 - push!(inds_visitedleaf, g.id) + push!(inds_visitedleaf, g_id) else - g.id in inds_visitednode && continue - factor_str = g.factor == 1 ? "" : " * $(g.factor)" - body *= " $target = $(_to_static(g.operator, g.subgraphs, g.subgraph_factors))$factor_str\n " - push!(inds_visitednode, g.id) + g_id in inds_visitednode && continue + factor_str = factor(g) == 1 ? "" : " * $(factor(g))" + body *= " $target = $(_to_static(operator(g), subgraphs(g), subgraph_factors(g)))$factor_str\n " + push!(inds_visitednode, g_id) end if isroot body *= " $target_root = $target\n " @@ -91,7 +104,7 @@ function to_julia_str(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVec end """ - function to_julia_str(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int,Int}; root::AbstractVector{Int}=[g.id for g in graphs], + function to_julia_str(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int,Int}; root::AbstractVector{Int}=[id(g) for g in graphs], name::String="eval_graph!") Compile a list of Feynman graphs into a string for a julia static function. The complied function takes two arguments: `root` and `leafVal`. @@ -100,10 +113,10 @@ Compile a list of Feynman graphs into a string for a julia static function. The # Arguments: - `graphs` (AbstractVector{G}): The vector object representing the Feynman graphs, - `leafMap (Dict{Int,Int})`: The mapping dictionary from the id of each leaf to the index of the leaf weight's table `leafVal`. -- `root` (AbstractVector{Int}, optional): The vector of the root node ids of the graphs (defaults to `[g.id for g in graphs]`). +- `root` (AbstractVector{Int}, optional): The vector of the root node ids of the graphs (defaults to `[id(g) for g in graphs]`). - `name` (String,optional): The name of the complied function (defaults to `"eval_graph!"`). """ -function to_julia_str(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int,Int}; root::AbstractVector{Int}=[g.id for g in graphs], +function to_julia_str(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int,Int}; root::AbstractVector{Int}=[id(g) for g in graphs], name::String="eval_graph!") head = "function $name(root::AbstractVector, leafVal::AbstractVector)\n " body = "" @@ -111,22 +124,23 @@ function to_julia_str(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int inds_visitednode = Int[] for graph in graphs for g in PostOrderDFS(graph) #leaf first search - target = "g$(g.id)" + g_id = id(g) + target = "g$(g_id)" isroot = false - if g.id in root - target_root = "root[$(findfirst(x -> x == g.id, root))]" + if g_id in root + target_root = "root[$(findfirst(x -> x == g_id, root))]" isroot = true end - if isempty(g.subgraphs) #leaf - g.id in inds_visitedleaf && continue - factor_str = g.factor == 1 ? "" : " * $(g.factor)" - body *= " $target = leafVal[$(leafMap[g.id])]$factor_str\n " - push!(inds_visitedleaf, g.id) + if isempty(subgraphs(g)) #leaf + g_id in inds_visitedleaf && continue + factor_str = factor(g) == 1 ? "" : " * $(factor(g))" + body *= " $target = leafVal[$(leafMap[g_id])]$factor_str\n " + push!(inds_visitedleaf, g_id) else - g.id in inds_visitednode && continue - factor_str = g.factor == 1 ? "" : " * $(g.factor)" - body *= " $target = $(_to_static(g.operator, g.subgraphs, g.subgraph_factors))$factor_str\n " - push!(inds_visitednode, g.id) + g_id in inds_visitednode && continue + factor_str = factor(g) == 1 ? "" : " * $(factor(g))" + body *= " $target = $(_to_static(operator(g), subgraphs(g), subgraph_factors(g)))$factor_str\n " + push!(inds_visitednode, g_id) end if isroot body *= " $target_root = $target\n " @@ -138,7 +152,7 @@ function to_julia_str(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int end """ - function compile(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVector{Int}=[g.id for g in graphs]) + function compile(graphs::AbstractVector{<:AbstractGraph}; root::AbstractVector{Int}=[id(g) for g in graphs]) Compile a list of graphs into a julia static function. The function takes two arguments: `root` and `leaf`. `root` is a vector of the root node ids of the graphs, and `leaf` is a vector of the leaf node ids of the graphs. @@ -160,7 +174,7 @@ leaf = [1.0, 2.0] ``` """ function compile(graphs::AbstractVector{<:AbstractGraph}; - root::AbstractVector{Int}=[g.id for g in graphs]) + root::AbstractVector{Int}=[id(g) for g in graphs]) # this function return a runtime generated function defined by compile() func_string = to_julia_str(graphs; root=root, name="func_name!") func_expr = Meta.parse(func_string) @@ -168,7 +182,7 @@ function compile(graphs::AbstractVector{<:AbstractGraph}; end function compile(graphs::AbstractVector{<:AbstractGraph}, leafMap::Dict{Int,Int}; - root::AbstractVector{Int}=[g.id for g in graphs]) + root::AbstractVector{Int}=[id(g) for g in graphs]) # this function return a runtime generated function defined by compile() func_string = to_julia_str(graphs, leafMap; root=root, name="func_name!") func_expr = Meta.parse(func_string) diff --git a/src/computational_graph/ComputationalGraph.jl b/src/computational_graph/ComputationalGraph.jl index fcfa7c1e..97c2bffb 100644 --- a/src/computational_graph/ComputationalGraph.jl +++ b/src/computational_graph/ComputationalGraph.jl @@ -22,13 +22,14 @@ export unary_istrivial, isassociative, isequiv include("graph.jl") include("feynmangraph.jl") +include("conversions.jl") export Graph, FeynmanGraph, FeynmanProperties # export DiagramType export isequiv, drop_topology, is_external, is_internal, diagram_type, orders, vertices, topology export external_legs, external_indices, external_operators, external_labels -export linear_combination, feynman_diagram, propagator, interaction, external_vertex +export multi_product, linear_combination, feynman_diagram, propagator, interaction, external_vertex # export Prod, Sum # export DiagramType, Interaction, ExternalVertex, Propagator, SelfEnergy, VertexDiag, GreenDiag, GenericDiag diff --git a/src/computational_graph/abstractgraph.jl b/src/computational_graph/abstractgraph.jl index 92b3f462..66651d27 100644 --- a/src/computational_graph/abstractgraph.jl +++ b/src/computational_graph/abstractgraph.jl @@ -12,9 +12,6 @@ struct Power{N} <: AbstractOperator end Base.eltype(::Type{<:Power{N}}) where {N} = N decrement_power(::Type{<:Power{N}}) where {N} = N == 2 ? Sum() : Power(N - 1) -# struct Power <: AbstractOperator -# exponent::Real -# end Base.isequal(a::AbstractOperator, b::AbstractOperator) = (typeof(a) == typeof(b)) Base.:(==)(a::AbstractOperator, b::AbstractOperator) = Base.isequal(a, b) apply(o::AbstractOperator, diags) = error("not implemented!") @@ -38,11 +35,235 @@ isassociative(::Type{Sum}) = true # requires Base.*(g1, g2) and Base./(g1, g2) # isassociative(::Type{Prod}) = true +""" + function unary_istrivial(g::AbstractGraph) + + Returns true if the unary form of the graph operation of node `g` is trivial. + Otherwise, returns false. +""" +unary_istrivial(g::AbstractGraph) = unary_istrivial(operator(g)) + +""" + function isassociative(g::AbstractGraph) + + Returns true if the graph operation of node `g` is associative. + Otherwise, returns false. +""" +isassociative(g::AbstractGraph) = isassociative(operator(g)) + + +### Getters/Setters ### + +""" + function id(g::AbstractGraph) + + Returns the unique hash id of computational graph `g`. +""" +function id(g::AbstractGraph) end + +""" + function name(g::AbstractGraph) + + Returns the name of computational graph `g`. +""" +function name(g::AbstractGraph) end + +""" + function orders(g::AbstractGraph) + + Returns the derivative orders (::Vector{Int}) of computational graph `g`. +""" +function orders(g::AbstractGraph) end + +""" +function operator(g::AbstractGraph) + + Returns the operation associated with computational graph node `g`. +""" +function operator(g::AbstractGraph) end + +""" +function factor(g::AbstractGraph) + + Returns the fixed scalar-multiplicative factor of the computational graph `g`. +""" +function factor(g::AbstractGraph) end + +""" + function weight(g::AbstractGraph) + + Returns the weight of the computational graph `g`. +""" +function weight(g::AbstractGraph) end + +""" +function subgraph(g::AbstractGraph, i=1) + + Returns a copy of the `i`th subgraph of computational graph `g`. + Defaults to the first subgraph if an index `i` is not supplied. +""" +function subgraph(g::AbstractGraph, i=1) end + +""" +function subgraphs(g::AbstractGraph) + + Returns the subgraphs of computational graph `g`. +""" +function subgraphs(g::AbstractGraph) end + +""" +function subgraphs(g::AbstractGraph, indices::AbstractVector{Int}) + + Returns the subgraphs of computational graph `g` at indices `indices`. + By default, calls `subgraph(g, i)` for each `i` in `indices`. +""" +function subgraphs(g::AbstractGraph, indices::AbstractVector{Int}) + return [subgraph(g, i) for i in indices] +end + +""" +function subgraph_factor(g::AbstractGraph, i=1) + + Returns a copy of the `i`th subgraph factor of computational graph `g`. + Defaults to the first subgraph factor if an index `i` is not supplied. +""" +function subgraph_factor(g::AbstractGraph, i=1) end + +""" +function subgraph_factors(g::AbstractGraph) + + Returns the subgraph factors of computational graph `g`. +""" +function subgraph_factors(g::AbstractGraph) end + +""" +function subgraph_factors(g::AbstractGraph, indices::AbstractVector{Int}) + + Returns the subgraph factors of computational graph `g` at indices `indices`. + By default, calls `subgraph_factor(g, i)` for each `i` in `indices`. +""" +function subgraph_factors(g::AbstractGraph, indices::AbstractVector{Int}) + return [subgraph_factor(g, i) for i in indices] +end + + +### Setters ### + +""" +function set_id!(g::AbstractGraph, id) + + Update the id of graph `g` to `id`. +""" +function set_id!(g::AbstractGraph, id) end + +""" +function set_name!(g::AbstractGraph, name::AbstractString) + + Update the name of graph `g` to `name`. +""" +function set_name!(g::AbstractGraph, name::AbstractString) end + +""" +function set_orders!(g::AbstractGraph, orders::AbstractVector) + + Update the orders of graph `g` to `orders`. +""" +function set_orders!(g::AbstractGraph, orders::AbstractVector) end + +""" +function set_operator!(g::AbstractGraph, operator::AbstractOperator) + + Update the operator of graph `g` to `typeof(operator)`. +""" +function set_operator!(g::AbstractGraph, operator::AbstractOperator) end + +""" +function set_operator!(g::AbstractGraph, operator::Type{<:AbstractOperator}) + + Update the operator of graph `g` to `operator`. +""" +function set_operator!(g::AbstractGraph, operator::Type{<:AbstractOperator}) end + +""" +function set_factor!(g::AbstractGraph, factor) + + Update the factor of graph `g` to `factor`. +""" +function set_factor!(g::AbstractGraph, factor) end + +""" +function set_weight!(g::AbstractGraph, weight) + + Update the weight of graph `g` to `weight`. +""" +function set_weight!(g::AbstractGraph, weight) end + +""" +function set_subgraph!(g::AbstractGraph, subgraph::AbstractGraph, i=1) + + Update the `i`th subgraph of graph `g` to `subgraph`. + Defaults to the first subgraph factor if an index `i` is not supplied. +""" +function set_subgraph!(g::AbstractGraph, subgraph::AbstractGraph, i=1) end + +""" +function set_subgraphs!(g::AbstractGraph, subgraphs::AbstractVector{<:AbstractGraph}) + + Update the full list of subgraphs of graph `g` to `subgraphs`. +""" +function set_subgraphs!(g::AbstractGraph, subgraphs::AbstractVector{<:AbstractGraph}) end + +""" +function set_subgraphs!(g::AbstractGraph, subgraphs::AbstractVector{<:AbstractGraph}, indices::AbstractVector{Int}) + + Update the specified subgraphs of graph `g` at indices `indices` to `subgraphs`. + By default, calls `set_subgraph!(g, subgraphs[i], i)` for each `i` in `indices`. +""" +function set_subgraphs!(g::AbstractGraph, subgraphs::AbstractVector{<:AbstractGraph}, indices::AbstractVector{Int}) + @assert length(subgraphs) == length(indices) + for (i, subg) in zip(indices, subgraphs) + set_subgraph!(g, subg, i) + end +end + +""" +function set_subgraph_factor!(g::AbstractGraph, subgraph_factor, i=1) + + Update the `i`th subgraph factor of graph `g` to `subgraph_factor`. + Defaults to the first subgraph factor if an index `i` is not supplied. +""" +function set_subgraph_factor!(g::AbstractGraph, subgraph_factor, i=1) end + +""" +function set_subgraph_factors!(g::AbstractGraph, subgraph_factors::AbstractVector) + + Update the subgraph factors of graph `g` to `subgraphs`. +""" +function set_subgraph_factors!(g::AbstractGraph, subgraph_factors::AbstractVector) end + +""" +function set_subgraph_factors!(g::AbstractGraph, subgraph_factors::AbstractVector, indices::AbstractVector{Int}) + + Update the specified subgraph factors of graph `g` at indices `indices` to `subgraph_factors`. + By default, calls `set_subgraph_factor!(g, subgraph_factors[i], i)` for each `i` in `indices`. +""" +function set_subgraph_factors!(g::AbstractGraph, subgraph_factors::AbstractVector, indices::AbstractVector{Int}) + @assert length(subgraph_factors) == length(indices) + for (i, subg_fac) in zip(indices, subgraph_factors) + set_subgraph_factor!(g, subg_fac, i) + end +end + + +### Methods ### + +# Tests for exact equality between two abstract graphs function Base.isequal(a::AbstractGraph, b::AbstractGraph) typeof(a) != typeof(b) && return false + (weight(a) ≈ weight(b)) == false && return false # check graph weights for approximate equality for field in fieldnames(typeof(a)) - if field == :weight - (getproperty(a, :weight) ≈ getproperty(b, :weight)) == false && return false + if field == :weight && getproperty(a, :weight) == weight(a) && getproperty(b, :weight) == weight(b) + continue # skip graph weights if already accounted for else getproperty(a, field) != getproperty(b, field) && return false end @@ -54,20 +275,40 @@ Base.:(==)(a::AbstractGraph, b::AbstractGraph) = Base.isequal(a, b) """ function isequiv(a::AbstractGraph, b::AbstractGraph, args...) - Determine whether `a` is equivalent to `b` without considering fields in `args`. + Determine whether graph `a` is equivalent to graph `b` without considering fields in `args`. """ function isequiv(a::AbstractGraph, b::AbstractGraph, args...) typeof(a) != typeof(b) && return false + # Check graph weights for approximate equality where applicable + if :weight ∉ args + (weight(a) ≈ weight(b)) == false && return false + end + # Check that all subgraphs are equivalent modulo `args` + length(subgraphs(a)) != length(subgraphs(b)) && return false + !all(isequiv.(subgraphs(a), subgraphs(b), args...)) && return false for field in fieldnames(typeof(a)) field in args && continue - if field == :weight - (getproperty(a, :weight) ≈ getproperty(b, :weight)) == false && return false - elseif field == :subgraphs - length(a.subgraphs) != length(b.subgraphs) && return false - !all(isequiv.(getproperty(a, field), getproperty(b, field), args...)) && return false + if field == :weight && getproperty(a, :weight) == weight(a) && getproperty(b, :weight) == weight(b) + continue # skip graph weights if already accounted for + elseif field == :subgraphs && getproperty(a, :subgraphs) == subgraphs(a) && getproperty(b, :subgraphs) == subgraphs(b) + continue # skip subgraphs if already accounted for else getproperty(a, field) != getproperty(b, field) && return false end end return true -end \ No newline at end of file +end + + +### Arithmetic operations ### + +errmsg(G::Type) = "Method not yet implemented for user-defined graph type $G." +linear_combination(g1::G, g2::G, c1, c2) where {G<:AbstractGraph} = error(errmsg(G)) +linear_combination(graphs::AbstractVector{G}, constants::AbstractVector) where {G<:AbstractGraph} = error(errmsg(G)) +multi_product(g1::G, g2::G, c1, c2) where {G<:AbstractGraph} = error(errmsg(G)) +multi_product(graphs::AbstractVector{G}, constants::AbstractVector) where {G<:AbstractGraph} = error(errmsg(G)) +Base.:*(c1, g2::G) where {G<:AbstractGraph} = error(errmsg(G)) +Base.:*(g1::G, c2) where {G<:AbstractGraph} = error(errmsg(G)) +Base.:*(g1::G, g2::G) where {G<:AbstractGraph} = error(errmsg(G)) +Base.:+(g1::G, g2::G) where {G<:AbstractGraph} = error(errmsg(G)) +Base.:-(g1::G, g2::G) where {G<:AbstractGraph} = error(errmsg(G)) diff --git a/src/computational_graph/conversions.jl b/src/computational_graph/conversions.jl new file mode 100644 index 00000000..d1133291 --- /dev/null +++ b/src/computational_graph/conversions.jl @@ -0,0 +1,37 @@ + +""" + function Base.convert(::Type{G}, g::FeynmanGraph{F,W}) where {F,W,G<:Graph} + + Converts a FeynmanGraph `g` into a Graph, discarding its Feynman properties. + After conversion, graph `g` is no longer guaranteed to be a valid (group of) Feynman diagram(s). + + # Arguments: + - `g` computational graph +""" +function Base.convert(::Type{G}, g::FeynmanGraph{F,W}) where {F,W,G<:Graph} + return Graph(g.subgraphs; subgraph_factors=g.subgraph_factors, name=g.name, operator=g.operator(), orders=g.orders, ftype=F, wtype=W, factor=g.factor, weight=g.weight) +end + +function Base.convert(::Type{FeynmanGraph}, g::Graph{F,W}) where {F,W} + error( + "A set of Feynman properties (operator vertices, topology, etc.) must be specified to convert an object of type Graph to FeynmanGraph. " * + "Please use constructor `FeynmanGraph(g::Graph, properties::FeynmanProperties)` instead." + ) +end + +# # Automatically promote FeynmanGraph to Graph for vectors v::Vector{Union{Graph,FeynmanGraph}} and arithmetic operations +# Base.promote_rule(::Type{Graph{F,W}}, ::Type{FeynmanGraph{F,W}}) where {F,W} = Graph{F,W} + +# # Arithmetic operations for mixed Graph/FeynmanGraph types +# linear_combination(g1::Graph{F,W}, g2::FeynmanGraph{F,W}, c1=F(1), c2=F(1)) where {F,W} = linear_combination(Base.promote(g1, g2)..., c1, c2) +# linear_combination(g1::FeynmanGraph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} = linear_combination(Base.promote(g1, g2)..., c1, c2) +# Base.:+(g1::Graph{F,W}, g2::FeynmanGraph{F,W}) where {F,W} = Base.:+(Base.promote(g1, g2)...) +# Base.:+(g1::FeynmanGraph{F,W}, g2::Graph{F,W}) where {F,W} = Base.:+(Base.promote(g1, g2)...) +# Base.:-(g1::Graph{F,W}, g2::FeynmanGraph{F,W}) where {F,W} = Base.:-(Base.promote(g1, g2)...) +# Base.:-(g1::FeynmanGraph{F,W}, g2::Graph{F,W}) where {F,W} = Base.:-(Base.promote(g1, g2)...) + +# # No auto-promotion for graph products +# multi_product(g1::Graph{F,W}, g2::FeynmanGraph{F,W}, c1=F(1), c2=F(1)) where {F,W} = error("Multiplication of Feynman graphs is not well defined!") +# multi_product(g1::FeynmanGraph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} = error("Multiplication of Feynman graphs is not well defined!") +# Base.:*(g1::Graph{F,W}, g2::FeynmanGraph{F,W}) where {F,W} = error("Multiplication of Feynman graphs is not well defined!") +# Base.:*(g1::FeynmanGraph{F,W}, g2::Graph{F,W}) where {F,W} = error("Multiplication of Feynman graphs is not well defined!") diff --git a/src/computational_graph/feynmangraph.jl b/src/computational_graph/feynmangraph.jl index c8d2203d..54393b5a 100644 --- a/src/computational_graph/feynmangraph.jl +++ b/src/computational_graph/feynmangraph.jl @@ -44,7 +44,7 @@ Base.:(==)(a::FeynmanProperties, b::FeynmanProperties) = Base.isequal(a, b) drop_topology(p::FeynmanProperties) = FeynmanProperties(p.diagtype, p.vertices, [], p.external_indices, p.external_legs) """ - mutable struct FeynmanGraph{F,W} + mutable struct FeynmanGraph{F<:Number,W} Computational graph representation of a (collection of) Feynman diagram(s). All Feynman diagrams should share the same set of external and internal vertices. @@ -55,8 +55,8 @@ drop_topology(p::FeynmanProperties) = FeynmanProperties(p.diagtype, p.vertices, - `properties::FeynmanProperties` diagrammatic properties, e.g., the operator vertices and topology - `subgraphs::Vector{FeynmanGraph{F,W}}` vector of sub-diagrams - `subgraph_factors::Vector{F}` scalar multiplicative factors associated with each subdiagram -- `operator::DataType` node operation, support Sum and Prod -- `factor::F` total scalar multiplicative factor for the diagram +- `operator::DataType` node operation (Sum, Prod, etc.) +- `factor::F` a number representing the total scalar multiplicative factor for the diagram. - `weight::W` weight of the diagram # Example: @@ -71,7 +71,7 @@ julia> g = FeynmanGraph([g1,g2]; vertices=[𝑓⁺(1),𝑓⁻(2),𝑓⁺(3),𝑓 3:f⁺(1)|f⁻(2)|f⁺(3)|f⁻(4)=0.0=Ⓧ (1,2) ``` """ -mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph +mutable struct FeynmanGraph{F<:Number,W} <: AbstractGraph # FeynmanGraph id::Int name::String # "" by default orders::Vector{Int} @@ -87,7 +87,7 @@ mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph """ function FeynmanGraph(subgraphs::AbstractVector; topology=[], vertices::Union{Vector{OperatorProduct},Nothing}=nothing, external_indices=[], external_legs=[], subgraph_factors=one.(eachindex(subgraphs)), name="", diagtype::DiagramType=GenericDiag(), operator::AbstractOperator=Sum(), - orders=zeros(Int, 16), ftype=_dtype.factor, wtype=_dtype.weight, factor=one(ftype), weight=zero(wtype) + orders=zeros(Int, 16), ftype=_dtype.factor, wtype=_dtype.weight, factor=one(ftype), weight=zero(wtype)) Create a FeynmanGraph struct from a set of subgraphs, vertices and external indices. @@ -100,7 +100,7 @@ mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph - `subgraph_factors` scalar multiplicative factors associated with each subdiagram - `name` name of the diagram - `diagtype::DiagramType` type of the diagram - - `operator::AbstractOperator` node operation, Sum, Prod, etc. + - `operator::AbstractOperator` node operation (Sum, Prod, etc.) - `orders` orders associated with the Feynman graph, e.g., loop/derivative orders - `ftype` typeof(factor) - `wtype` typeof(weight) @@ -126,16 +126,16 @@ mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph """ function FeynmanGraph(subgraphs::AbstractVector, properties::FeynmanProperties; subgraph_factors=one.(eachindex(subgraphs)), name="", operator::AbstractOperator=Sum(), - ftype=_dtype.factor, wtype=_dtype.weight, factor=one(ftype), weight=zero(wtype) + ftype=_dtype.factor, wtype=_dtype.weight, factor=one(ftype), weight=zero(wtype)) - Create a FeynmanGraph struct from a given set of subgraphs and FeynmanProperties. + Create a FeynmanGraph struct from a given set of subgraphs and Feynman properties. # Arguments: - `subgraphs` vector of sub-diagram - - `properties::FeynmanProperties` diagrammatic properties, e.g., the operator vertices and topologys + - `properties::FeynmanProperties` diagrammatic properties, e.g., the operator vertices and topology - `subgraph_factors` scalar multiplicative factors associated with each subdiagram - `name` name of the diagram - - `operator::AbstractOperator` node operation, Sum, Prod, etc. + - `operator::AbstractOperator` node operation (Sum, Prod, etc.) - `ftype` typeof(factor) - `wtype` typeof(weight) - `factor` overall scalar multiplicative factor for this diagram (e.g., permutation sign) @@ -152,21 +152,69 @@ mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph # @assert allunique(subgraphs) "all subgraphs must be distinct." return new{ftype,wtype}(uid(), name, orders, properties, subgraphs, subgraph_factors, typeof(operator), factor, weight) end + + """ + function FeynmanGraph(g::Graph, properties::FeynmanProperties) + + Create a Feynman graph given a graph `g` and the Feynman properties (external vertices, topology, etc.) to endow it with. + + # Arguments: + - `g` computational graph + - `properties::FeynmanProperties` diagrammatic properties, e.g., the operator vertices and topology + """ + function FeynmanGraph(g::Graph{F,W}, properties::FeynmanProperties) where {F,W} + @assert length(properties.external_indices) == length(properties.external_legs) + # @assert allunique(subgraphs) "all subgraphs must be distinct." + return new{F,W}(uid(), g.name, g.orders, properties, g.subgraphs, g.subgraph_factors, g.operator, g.factor, g.weight) + end end +### AbstractGraph interface for FeynmanGraph ### + +# Getters +id(g::FeynmanGraph) = g.id +name(g::FeynmanGraph) = g.name +orders(g::FeynmanGraph) = g.orders +operator(g::FeynmanGraph) = g.operator +factor(g::FeynmanGraph) = g.factor +weight(g::FeynmanGraph) = g.weight +subgraph(g::FeynmanGraph, i=1) = g.subgraphs[i] +subgraphs(g::FeynmanGraph) = g.subgraphs +subgraphs(g::FeynmanGraph, indices::AbstractVector{Int}) = g.subgraphs[indices] +subgraph_factor(g::FeynmanGraph, i=1) = g.subgraph_factors[i] +subgraph_factors(g::FeynmanGraph) = g.subgraph_factors +subgraph_factors(g::FeynmanGraph, indices::AbstractVector{Int}) = g.subgraph_factors[indices] + +# Setters +set_id!(g::FeynmanGraph, id::Int) = (g.id = id) +set_name!(g::FeynmanGraph, name::String) = (g.name = name) +set_orders!(g::FeynmanGraph, orders::Vector{Int}) = (g.orders = orders) +set_operator!(g::FeynmanGraph, operator::Type{<:AbstractOperator}) = (g.operator = operator) +set_operator!(g::FeynmanGraph, operator::AbstractOperator) = (g.operator = typeof(operator)) +set_factor!(g::FeynmanGraph{F,W}, factor) where {F,W} = (g.factor = F(factor)) +set_weight!(g::FeynmanGraph{F,W}, weight) where {F,W} = (g.weight = W(weight)) +set_subgraph!(g::FeynmanGraph{F,W}, subgraph::FeynmanGraph{F,W}, i=1) where {F,W} = (g.subgraphs[i] = subgraph) +set_subgraphs!(g::FeynmanGraph{F,W}, subgraphs::Vector{FeynmanGraph{F,W}}) where {F,W} = (g.subgraphs = subgraphs) +set_subgraphs!(g::FeynmanGraph{F,W}, subgraphs::Vector{FeynmanGraph{F,W}}, indices::AbstractVector{Int}) where {F,W} = (g.subgraphs[indices] = subgraphs) +set_subgraph_factor!(g::FeynmanGraph{F,W}, subgraph_factor, i=1) where {F,W} = (g.subgraph_factors[i] = F(subgraph_factor)) +set_subgraph_factors!(g::FeynmanGraph{F,W}, subgraph_factors::AbstractVector) where {F,W} = (g.subgraph_factors = Vector{F}(subgraph_factors)) +set_subgraph_factors!(g::FeynmanGraph{F,W}, subgraph_factors::AbstractVector, indices::AbstractVector{Int}) where {F,W} = (g.subgraph_factors[indices] = Vector{F}(subgraph_factors)) + +############################### + """ - function is_external_operators(g::FeynmanGraph, i::Int) +function is_external_operators(g::FeynmanGraph, i) - Check if `i::Int` in the external indices of FeynmanGraph `g`. + Check if `i` in the external indices of FeynmanGraph `g`. """ -is_external(g::FeynmanGraph, i::Int) = i in g.properties.external_indices +is_external(g::FeynmanGraph, i) = i in g.properties.external_indices """ - function is_internal(g::FeynmanGraph, i::Int) + function is_internal(g::FeynmanGraph, i) - Check if `i::Int` in the internal indices of FeynmanGraph `g`. + Check if `i` in the internal indices of FeynmanGraph `g`. """ -is_internal(g::FeynmanGraph, i::Int) = (i in g.properties.external_indices) == false +is_internal(g::FeynmanGraph, i) = (i in g.properties.external_indices) == false """ function diagram_type(g::FeynmanGraph) @@ -176,18 +224,19 @@ is_internal(g::FeynmanGraph, i::Int) = (i in g.properties.external_indices) == f diagram_type(g::FeynmanGraph) = g.properties.diagtype """ - function orders(g::FeynmanGraph) + function vertices(g::FeynmanGraph) - Returns the loop/derivative orders (::Vector{Int}) of FeynmanGraph `g`. + Returns all vertices (::Vector{OperatorProduct}) of FeynmanGraph `g`. """ -orders(g::FeynmanGraph) = g.orders +vertices(g::FeynmanGraph) = g.properties.vertices """ - function vertices(g::FeynmanGraph) + function vertex(g::FeynmanGraph, i=1) - Returns all vertices (::Vector{OperatorProduct}) of FeynmanGraph `g`. + Returns the `i`th vertex (::OperatorProduct) of FeynmanGraph `g`. + Defaults to the first vertex if an index `i` is not supplied. """ -vertices(g::FeynmanGraph) = g.properties.vertices +vertex(g::FeynmanGraph, i=1) = g.properties.vertices[i] """ function topology(g::FeynmanGraph) @@ -237,7 +286,7 @@ function connectivity(g::FeynmanGraph) end """ - function Base.:*(g1::Graph{F,W}, c2::C) where {F,W,C} + function Base.:*(g1::Graph{F,W}, c2) where {F,W} Returns a graph representing the scalar multiplication `g1*c2`. @@ -245,18 +294,19 @@ end - `g1` Feynman graph - `c2` scalar multiple """ -function Base.:*(g1::FeynmanGraph{F,W}, c2::C) where {F,W,C} +function Base.:*(g1::FeynmanGraph{F,W}, c2) where {F,W} g = FeynmanGraph([g1,], g1.properties; subgraph_factors=[F(c2),], operator=Prod(), orders=orders(g1), ftype=F, wtype=W) - # Merge multiplicative link - if g1.operator == Prod && onechild(g1) + # Convert trivial unary link to in-place form + if unary_istrivial(g1) && onechild(g1) g.subgraph_factors[1] *= g1.subgraph_factors[1] + # g.subgraph_factors[1] *= g1.subgraph_factors[1] * g1.factor g.subgraphs = g1.subgraphs end return g end """ - function Base.:*(c1::C, g2::Graph{F,W}) where {F,W,C} + function Base.:*(c1, g2::Graph{F,W}) where {F,W} Returns a graph representing the scalar multiplication `c1*g2`. @@ -264,18 +314,19 @@ end - `c1` scalar multiple - `g2` Feynman graph """ -function Base.:*(c1::C, g2::FeynmanGraph{F,W}) where {F,W,C} +function Base.:*(c1, g2::FeynmanGraph{F,W}) where {F,W} g = FeynmanGraph([g2,], g2.properties; subgraph_factors=[F(c1),], operator=Prod(), orders=orders(g2), ftype=F, wtype=W) - # Merge multiplicative link - if g2.operator == Prod && onechild(g2) + # Convert trivial unary link to in-place form + if unary_istrivial(g2) && onechild(g2) g.subgraph_factors[1] *= g2.subgraph_factors[1] + # g.subgraph_factors[1] *= g2.subgraph_factors[1] * g2.factor g.subgraphs = g2.subgraphs end return g end """ - function linear_combination(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}, c1::C, c2::C) where {F,W,C} + function linear_combination(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}, c1, c2) where {F,W} Returns a graph representing the linear combination `c1*g1 + c2*g2`. If `g1 == g2`, it will return a graph representing `(c1+c2)*g1` Feynman Graphs `g1` and `g2` must have the same diagram type, orders, and external vertices. @@ -286,29 +337,32 @@ end - `c1`: first scalar multiple (defaults to 1). - `c2`: second scalar multiple (defaults to 1). """ -function linear_combination(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}, c1::C=1, c2::C=1) where {F,W,C} +function linear_combination(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}, c1=F(1), c2=F(1)) where {F,W} @assert diagram_type(g1) == diagram_type(g2) "g1 and g2 are not of the same graph type." @assert orders(g1) == orders(g2) "g1 and g2 have different orders." @assert Set(external_operators(g1)) == Set(external_operators(g2)) "g1 and g2 have different external vertices." empty_topology = [] # No topology for Sum nodes total_vertices = union(vertices(g1), vertices(g2)) properties = FeynmanProperties(diagram_type(g1), total_vertices, empty_topology, external_indices(g1), external_legs(g1)) - + + f1 = typeof(c1) == F ? c1 : F(c1) + f2 = typeof(c2) == F ? c2 : F(c2) subgraphs = [g1, g2] - subgraph_factors = [F(c1), F(c2)] - # Convert multiplicative links to in-place form - if unary_istrivial(g1.operator) && onechild(g1) + subgraph_factors = [f1, f2] + # Convert trivial unary links to in-place form + if unary_istrivial(g1) && onechild(g1) subgraph_factors[1] *= g1.subgraph_factors[1] + # subgraph_factors[1] *= g1.subgraph_factors[1] * g1.factor subgraphs[1] = g1.subgraphs[1] end - if unary_istrivial(g2.operator) && onechild(g2) + if unary_istrivial(g2) && onechild(g2) subgraph_factors[2] *= g2.subgraph_factors[1] + # subgraph_factors[2] *= g2.subgraph_factors[1] * g2.factor subgraphs[2] = g2.subgraphs[1] end - # g = FeynmanGraph([g1, g2], properties; subgraph_factors=[F(c1), F(c2)], operator=Sum(), ftype=F, wtype=W) if subgraphs[1] == subgraphs[2] - g = FeynmanGraph([subgraphs[1]], properties; subgraph_factors=[sum(subgraph_factors)], operator=Sum(), ftype=F, wtype=W) + g = FeynmanGraph([subgraphs[1]], properties; subgraph_factors=[sum(subgraph_factors)], operator=Sum(), orders=orders(g1), ftype=F, wtype=W) else g = FeynmanGraph(subgraphs, properties; subgraph_factors=subgraph_factors, operator=Sum(), orders=orders(g1), ftype=F, wtype=W) end @@ -316,7 +370,7 @@ function linear_combination(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}, c1::C= end """ - function linear_combination(graphs::Vector{FeynmanGraph{F,W}}, constants::Vector{C}) where {F,W,C} + function linear_combination(graphs::Vector{FeynmanGraph{F,W}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} Given a vector 𝐠 of graphs each with the same type and external/internal vertices and an equally-sized vector 𝐜 of constants, returns a new graph representing the linear combination (𝐜 ⋅ 𝐠). @@ -325,7 +379,7 @@ end # Arguments: - `graphs` vector of input FeymanGraphs -- `constants` vector of scalar multiples (defaults to ones(C, length(graphs))). +- `constants` vector of scalar multiples (defaults to ones(F, length(graphs))). # Returns: - A new `FeynmanGraph{F,W}` object representing the linear combination of the unique input `graphs` weighted by the constants, @@ -334,7 +388,7 @@ where duplicate graphs in the input `graphs` are combined by summing their assoc # Example: Given graphs `g1`, `g2`, `g1` and constants `c1`, `c2`, `c3`, the function computes `(c1+c3)*g1 + c2*g2`. """ -function linear_combination(graphs::Vector{FeynmanGraph{F,W}}, constants::Vector{C}=ones(C, length(graphs))) where {F,W,C} +function linear_combination(graphs::Vector{FeynmanGraph{F,W}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} @assert alleq(diagram_type.(graphs)) "Graphs are not all of the same graph type." @assert alleq(orders.(graphs)) "Graphs do not all have the same order." @assert alleq(Set.(external_operators.(graphs))) "Graphs do not share the same set of external vertices." @@ -342,12 +396,14 @@ function linear_combination(graphs::Vector{FeynmanGraph{F,W}}, constants::Vector empty_topology = [] # No topology for Sum nodes total_vertices = union(Iterators.flatten(vertices.(graphs))) properties = FeynmanProperties(diagram_type(g1), total_vertices, empty_topology, external_indices(g1), external_legs(g1)) - - subgraphs, subgraph_factors = graphs, constants - # Convert multiplicative links to in-place form + + subgraphs = graphs + subgraph_factors = eltype(constants) == F ? constants : Vector{F}(constants) + # Convert trivial unary links to in-place form for (i, sub_g) in enumerate(graphs) - if unary_istrivial(sub_g.operator) && onechild(sub_g) + if unary_istrivial(sub_g) && onechild(sub_g) subgraph_factors[i] *= sub_g.subgraph_factors[1] + # subgraph_factors[i] *= sub_g.subgraph_factors[1] * sub_g.factor subgraphs[i] = sub_g.subgraphs[1] end end @@ -370,8 +426,8 @@ end """ function Base.:+(g1::Graph{F,W}, g2::Graph{F,W}) where {F,W} - Returns a graph `g1 + g2` representing the addition of `g2` with `g1`. - Feynman Graphs `g1` and `g2` must have the same diagram type, orders, and external vertices. + Returns a graph `g1 + g2` representing the addition of two Feynman diagrams `g2` with `g1`. + Diagrams `g1` and `g2` must have the same diagram type, orders, and external vertices. # Arguments: - `g1` first Feynman graph @@ -385,7 +441,7 @@ end function Base.:-(g1::Graph{F,W}, g2::Graph{F,W}) where {F,W} Returns a graph `g1 - g2` representing the subtraction of `g2` from `g1`. - Feynman Graphs `g1` and `g2` must have the same diagram type, orders, and external vertices. + Diagrams `g1` and `g2` must have the same diagram type, orders, and external vertices. # Arguments: - `g1` first Feynman graph @@ -396,7 +452,7 @@ function Base.:-(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}) where {F,W} end function Base.:*(g1::FeynmanGraph, g2::FeynmanGraph) - error("Not implemented!") + error("Multiplication of Feynman graphs is not well defined!") end """ diff --git a/src/computational_graph/graph.jl b/src/computational_graph/graph.jl index 7a68344f..92ccf7f7 100644 --- a/src/computational_graph/graph.jl +++ b/src/computational_graph/graph.jl @@ -1,5 +1,5 @@ """ - mutable struct Graph{F,W} + mutable struct Graph{F<:Number,W} A representation of a computational graph, e.g., an expression tree, with type stable node data. @@ -10,7 +10,7 @@ - `subgraphs::Vector{Graph{F,W}}` vector of sub-diagrams - `subgraph_factors::Vector{F}` scalar multiplicative factors associated with each subgraph. Note that the subgraph factors may be manipulated algebraically. To associate a fixed multiplicative factor with this graph which carries some semantic meaning, use the `factor` argument instead. - `operator::DataType` node operation. Addition and multiplication are natively supported via operators Sum and Prod, respectively. Should be a concrete subtype of `AbstractOperator`. -- `factor::F` total scalar multiplicative factor for the diagram +- `factor::F` a number representing the total scalar multiplicative factor for the diagram. - `weight::W` the weight of this node # Example: @@ -25,7 +25,7 @@ julia> g = Graph([g1, g2]; operator=ComputationalGraphs.Sum()) 3=0.0=⨁ (1,2) ``` """ -mutable struct Graph{F,W} <: AbstractGraph # Graph +mutable struct Graph{F<:Number,W} <: AbstractGraph # Graph id::Int name::String # "" by default orders::Vector{Int} @@ -65,6 +65,39 @@ mutable struct Graph{F,W} <: AbstractGraph # Graph end end +### AbstractGraph interface for Graph ### + +# Getters +id(g::Graph) = g.id +name(g::Graph) = g.name +orders(g::Graph) = g.orders +operator(g::Graph) = g.operator +factor(g::Graph) = g.factor +weight(g::Graph) = g.weight +subgraph(g::Graph, i=1) = g.subgraphs[i] +subgraphs(g::Graph) = g.subgraphs +subgraphs(g::Graph, indices::AbstractVector{Int}) = g.subgraphs[indices] +subgraph_factor(g::Graph, i=1) = g.subgraph_factors[i] +subgraph_factors(g::Graph) = g.subgraph_factors +subgraph_factors(g::Graph, indices::AbstractVector{Int}) = g.subgraph_factors[indices] + +# Setters +set_id!(g::Graph, id::Int) = (g.id = id) +set_name!(g::Graph, name::String) = (g.name = name) +set_orders!(g::Graph, orders::Vector{Int}) = (g.orders = orders) +set_operator!(g::Graph, operator::Type{<:AbstractOperator}) = (g.operator = operator) +set_operator!(g::Graph, operator::AbstractOperator) = (g.operator = typeof(operator)) +set_factor!(g::Graph{F,W}, factor) where {F,W} = (g.factor = F(factor)) +set_weight!(g::Graph{F,W}, weight) where {F,W} = (g.weight = W(weight)) +set_subgraph!(g::Graph{F,W}, subgraph::Graph{F,W}, i=1) where {F,W} = (g.subgraphs[i] = subgraph) +set_subgraphs!(g::Graph{F,W}, subgraphs::Vector{Graph{F,W}}) where {F,W} = (g.subgraphs = subgraphs) +set_subgraphs!(g::Graph{F,W}, subgraphs::Vector{Graph{F,W}}, indices::AbstractVector{Int}) where {F,W} = (g.subgraphs[indices] = subgraphs) +set_subgraph_factor!(g::Graph{F,W}, subgraph_factor, i=1) where {F,W} = (g.subgraph_factors[i] = F(subgraph_factor)) +set_subgraph_factors!(g::Graph{F,W}, subgraph_factors::AbstractVector) where {F,W} = (g.subgraph_factors = Vector{F}(subgraph_factors)) +set_subgraph_factors!(g::Graph{F,W}, subgraph_factors::AbstractVector, indices::AbstractVector{Int}) where {F,W} = (g.subgraph_factors[indices] = Vector{F}(subgraph_factors)) + +############################### + """ function constant_graph(factor=one(_dtype.factor)) @@ -78,14 +111,7 @@ function constant_graph(factor=one(_dtype.factor)) end """ - function orders(g::Graph) - - Returns the derivative orders (::Vector{Int}) of Graph `g`. -""" -orders(g::Graph) = g.orders - -""" - function Base.:*(g1::Graph{F,W}, c2::C) where {F,W,C} + function Base.:*(g1::Graph{F,W}, c2) where {F,W} Returns a graph representing the scalar multiplication `g1*c2`. @@ -93,18 +119,19 @@ orders(g::Graph) = g.orders - `g1` computational graph - `c2` scalar multiple """ -function Base.:*(g1::Graph{F,W}, c2::C) where {F,W,C} +function Base.:*(g1::Graph{F,W}, c2) where {F,W} g = Graph([g1,]; subgraph_factors=[F(c2),], operator=Prod(), orders=orders(g1), ftype=F, wtype=W) - # Merge multiplicative link - if unary_istrivial(g1.operator) && onechild(g1) + # Convert trivial unary link to in-place form + if unary_istrivial(g1) && onechild(g1) g.subgraph_factors[1] *= g1.subgraph_factors[1] + # g.subgraph_factors[1] *= g1.subgraph_factors[1] * g1.factor g.subgraphs = g1.subgraphs end return g end """ - function Base.:*(c1::C, g2::Graph{F,W}) where {F,W,C} + function Base.:*(c1, g2::Graph{F,W}) where {F,W} Returns a graph representing the scalar multiplication `c1*g2`. @@ -112,18 +139,19 @@ end - `c1` scalar multiple - `g2` computational graph """ -function Base.:*(c1::C, g2::Graph{F,W}) where {F,W,C} +function Base.:*(c1, g2::Graph{F,W}) where {F,W} g = Graph([g2,]; subgraph_factors=[F(c1),], operator=Prod(), orders=orders(g2), ftype=F, wtype=W) - # Merge multiplicative link - if unary_istrivial(g2.operator) && onechild(g2) + # Convert trivial unary link to in-place form + if unary_istrivial(g2) && onechild(g2) g.subgraph_factors[1] *= g2.subgraph_factors[1] + # g.subgraph_factors[1] *= g2.subgraph_factors[1] * g2.factor g.subgraphs = g2.subgraphs end return g end """ - function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1::C, c2::C) where {F,W,C} + function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1, c2) where {F,W} Returns a graph representing the linear combination `c1*g1 + c2*g2`. If `g1 == g2`, it will return a graph representing `(c1+c2)*g1`. @@ -135,17 +163,21 @@ end - `c1` first scalar multiple - `c2` second scalar multiple """ -function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1::C=1, c2::C=1) where {F,W,C} +function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} @assert orders(g1) == orders(g2) "g1 and g2 have different orders." + f1 = typeof(c1) == F ? c1 : F(c1) + f2 = typeof(c2) == F ? c2 : F(c2) subgraphs = [g1, g2] - subgraph_factors = [F(c1), F(c2)] - # Convert multiplicative links to in-place form - if unary_istrivial(g1.operator) && onechild(g1) + subgraph_factors = [f1, f2] + # Convert trivial unary links to in-place form + if unary_istrivial(g1) && onechild(g1) subgraph_factors[1] *= g1.subgraph_factors[1] + # subgraph_factors[1] *= g1.subgraph_factors[1] * g1.factor subgraphs[1] = g1.subgraphs[1] end - if unary_istrivial(g2.operator) && onechild(g2) + if unary_istrivial(g2) && onechild(g2) subgraph_factors[2] *= g2.subgraph_factors[1] + # subgraph_factors[2] *= g2.subgraph_factors[1] * g2.factor subgraphs[2] = g2.subgraphs[1] end @@ -159,7 +191,7 @@ function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1::C=1, c2::C=1) wh end """ - function linear_combination(graphs::Vector{Graph{F,W}}, constants::Vector{C}) where {F,W,C} + function linear_combination(graphs::Vector{Graph{F,W}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} Given a vector 𝐠 of graphs and an equally-sized vector 𝐜 of constants, returns a new graph representing the linear combination (𝐜 ⋅ 𝐠). @@ -168,7 +200,7 @@ end # Arguments: - `graphs` vector of computational graphs -- `constants` vector of scalar multiples (defaults to ones(C, length(graphs))). +- `constants` vector of scalar multiples (defaults to ones(F, length(graphs))). # Returns: - A new `Graph{F,W}` object representing the linear combination of the unique input `graphs` weighted by the constants, @@ -177,14 +209,15 @@ where duplicate graphs in the input `graphs` are combined by summing their assoc # Example: Given graphs `g1`, `g2`, `g1` and constants `c1`, `c2`, `c3`, the function computes `(c1+c3)*g1 + c2*g2`. """ -function linear_combination(graphs::Vector{Graph{F,W}}, constants::Vector{C}=ones(F, length(graphs))) where {F,W,C<:Number} +function linear_combination(graphs::Vector{Graph{F,W}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} @assert alleq(orders.(graphs)) "Graphs do not all have the same order." - subgraphs, subgraph_factors = graphs, constants - # parameters = union(getproperty.(graphs, :parameters)) - # Convert multiplicative links to in-place form + subgraphs = graphs + subgraph_factors = eltype(constants) == F ? constants : Vector{F}(constants) + # Convert trivial unary links to in-place form for (i, sub_g) in enumerate(graphs) - if unary_istrivial(sub_g.operator) && onechild(sub_g) + if unary_istrivial(sub_g) && onechild(sub_g) subgraph_factors[i] *= sub_g.subgraph_factors[1] + # subgraph_factors[i] *= sub_g.subgraph_factors[1] * sub_g.factor subgraphs[i] = sub_g.subgraphs[1] end end @@ -208,21 +241,6 @@ function linear_combination(graphs::Vector{Graph{F,W}}, constants::Vector{C}=one return g end -# function Base.:+(c::C, g1::Graph{F,W}) where {F,W,C} -# return linear_combination(g1, Unity, F(1), F(c)) -# end -# function Base.:+(g1::Graph{F,W},c::C) where {F,W,C} -# return linear_combination(g1, Unity, F(1), F(c)) -# end - -# function Base.:-(c::C, g1::Graph{F,W}) where {F,W,C} -# return linear_combination(Unity, g1, F(c), F(-1)) -# end -# function Base.:-(g1::Graph{F,W},c::C) where {F,W,C} -# return linear_combination(g1, Unity, F(1), F(-c)) -# end - - """ function Base.:+(g1::Graph{F,W}, g2::Graph{F,W}) where {F,W} @@ -251,9 +269,8 @@ function Base.:-(g1::Graph{F,W}, g2::Graph{F,W}) where {F,W} return linear_combination(g1, g2, F(1), F(-1)) end - """ - function multi_product(g1::Graph{F,W}, g2::Graph{F,W}, c1::C=1, c2::C=1) where {F,W,C} + function multi_product(g1::Graph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W,C} Returns a graph representing the multi product `c1*g1 * c2*g2`. If `g1 == g2`, it will return a graph representing `c1*c2 * (g1)^2` with `Power(2)` operator. @@ -264,30 +281,34 @@ end - `c1`: first scalar multiple (defaults to 1). - `c2`: second scalar multiple (defaults to 1). """ -function multi_product(g1::Graph{F,W}, g2::Graph{F,W}, c1::C=1, c2::C=1) where {F,W,C} - # g = Graph([g1, g2]; subgraph_factors=[F(c1), F(c2)], operator=Prod(), ftype=F, wtype=W) +function multi_product(g1::Graph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} + @assert orders(g1) == orders(g2) "g1 and g2 have different orders." + f1 = typeof(c1) == F ? c1 : F(c1) + f2 = typeof(c2) == F ? c2 : F(c2) subgraphs = [g1, g2] - subgraph_factors = [F(c1), F(c2)] - # Convert multiplicative links to in-place form - if unary_istrivial(g1.operator) && onechild(g1) + subgraph_factors = [f1, f2] + # Convert trivial unary links to in-place form + if unary_istrivial(g1) && onechild(g1) subgraph_factors[1] *= g1.subgraph_factors[1] + # subgraph_factors[1] *= g1.subgraph_factors[1] * g1.factor subgraphs[1] = g1.subgraphs[1] end - if unary_istrivial(g2.operator) && onechild(g2) + if unary_istrivial(g2) && onechild(g2) subgraph_factors[2] *= g2.subgraph_factors[1] + # subgraph_factors[2] *= g2.subgraph_factors[1] * g2.factor subgraphs[2] = g2.subgraphs[1] end if subgraphs[1] == subgraphs[2] g = Graph([subgraphs[1]]; subgraph_factors=[prod(subgraph_factors)], operator=Power(2), ftype=F, wtype=W) else - g = Graph(subgraphs; subgraph_factors=subgraph_factors, operator=Prod(), ftype=F, wtype=W) + g = Graph(subgraphs; subgraph_factors=subgraph_factors, operator=Prod(), orders=orders(g1), ftype=F, wtype=W) end return g end """ - multi_product(graphs::Vector{Graph{F,W}}, constants::Vector{C}=ones(C, length(graphs.subgraphs))) where {F,W,C} + multi_product(graphs::Vector{Graph{F,W}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W,C} Construct a product graph from multiple input graphs, where each graph can be weighted by a constant. For graphs that are repeated more than once, it adds a power operator to the subgraph to represent the repetition. @@ -295,7 +316,7 @@ end # Arguments: - `graphs::Vector{Graph{F,W}}`: A vector of input graphs to be multiplied. -- `constants::Vector{C}`: A vector of scalar multiples. If not provided, it defaults to a vector of ones of the same length as `graphs`. +- `constants::AbstractVector`: A vector of scalar multiples. If not provided, it defaults to a vector of ones of the same length as `graphs`. Returns: - A new product graph with the unique subgraphs (or powered versions thereof) and the associated constants as subgraph factors. @@ -303,12 +324,16 @@ Returns: # Example: Given graphs `g1`, `g2`, `g1` and constants `c1`, `c2`, `c3`, the function computes `(c1*c3)*(g1)^2 * c2*g2`. """ -function multi_product(graphs::Vector{Graph{F,W}}, constants::Vector{C}=ones(C, length(graphs))) where {F,W,C} - subgraphs, subgraph_factors = graphs, constants - # Convert multiplicative links to in-place form +function multi_product(graphs::Vector{Graph{F,W}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} + @assert alleq(orders.(graphs)) "Graphs do not all have the same order." + g1 = graphs[1] + subgraphs = graphs + subgraph_factors = eltype(constants) == F ? constants : Vector{F}(constants) + # Convert trivial unary links to in-place form for (i, sub_g) in enumerate(graphs) - if unary_istrivial(sub_g.operator) && onechild(sub_g) + if unary_istrivial(sub_g) && onechild(sub_g) subgraph_factors[i] *= sub_g.subgraph_factors[1] + # subgraph_factors[i] *= sub_g.subgraph_factors[1] * sub_g.factor subgraphs[i] = sub_g.subgraphs[1] end end @@ -333,22 +358,21 @@ function multi_product(graphs::Vector{Graph{F,W}}, constants::Vector{C}=ones(C, end if length(unique_factors) == 1 - g = Graph(unique_graphs; subgraph_factors=unique_factors, operator=Power(repeated_counts[1]), ftype=F, wtype=W) + g = Graph(unique_graphs; subgraph_factors=unique_factors, operator=Power(repeated_counts[1]), orders=orders(g1), ftype=F, wtype=W) else subgraphs = Vector{Graph{F,W}}() for (idx, g) in enumerate(unique_graphs) if repeated_counts[idx] == 1 push!(subgraphs, g) else - push!(subgraphs, Graph([g], operator=Power(repeated_counts[idx]), ftype=F, wtype=W)) + push!(subgraphs, Graph([g], operator=Power(repeated_counts[idx]), orders=orders(g1), ftype=F, wtype=W)) end end - g = Graph(subgraphs; subgraph_factors=unique_factors, operator=Prod(), ftype=F, wtype=W) + g = Graph(subgraphs; subgraph_factors=unique_factors, operator=Prod(), orders=orders(g1), ftype=F, wtype=W) end return g end - """ function Base.:*(g1::Graph{F,W}, g2::Graph{F,W}) where {F,W} diff --git a/src/computational_graph/io.jl b/src/computational_graph/io.jl index 32e20e88..7d321d02 100644 --- a/src/computational_graph/io.jl +++ b/src/computational_graph/io.jl @@ -26,21 +26,29 @@ function short_orders(orders) return orders_no_trailing_zeros end +function _namestr(graph::AbstractGraph) + return isempty(name(graph)) ? "" : "-$(name(graph))" +end + +function _idstring(graph::AbstractGraph) + return string(id(graph), _namestr(graph)) +end + +function _idstring(graph::FeynmanGraph) + return string(id(graph), _namestr(graph), ":", _ops_to_str(vertices(graph))) +end + function _stringrep(graph::AbstractGraph, color=true) - namestr = isempty(graph.name) ? "" : "-$(graph.name)" - idstr = "$(graph.id)$namestr" - if graph isa FeynmanGraph - idstr *= ":$(_ops_to_str(vertices(graph)))" - end - fstr = short(graph.factor, one(graph.factor)) - wstr = short(graph.weight) + idstr = _idstring(graph) + fstr = short(factor(graph), one(factor(graph))) + wstr = short(weight(graph)) ostr = short_orders(orders(graph)) # =$(node.weight*(2π)^(3*node.id.para.innerLoopNum)) - if length(graph.subgraphs) == 0 + if length(subgraphs(graph)) == 0 return isempty(fstr) ? "$(idstr)$(ostr)=$wstr" : "$(idstr)⋅$(fstr)=$wstr" else - return "$(idstr)$(ostr)=$wstr=$(fstr)$(graph.operator) " + return "$(idstr)$(ostr)=$wstr=$(fstr)$(operator(graph)) " end end @@ -50,10 +58,10 @@ end Write a text representation of an AbstractGraph `graph` to the output stream `io`. """ function Base.show(io::IO, graph::AbstractGraph; kwargs...) - if length(graph.subgraphs) == 0 + if length(subgraphs(graph)) == 0 typestr = "" else - typestr = join(["$(g.id)" for g in graph.subgraphs], ",") + typestr = join(["$(id(g))" for g in subgraphs(graph)], ",") typestr = "($typestr)" end print(io, "$(_stringrep(graph, true))$typestr") @@ -82,10 +90,10 @@ function plot_tree(graph::AbstractGraph; verbose=0, maxdepth=6) name = "$(_stringrep(node, false))" nt = t.add_child(name=name) - if length(node.subgraphs) > 0 + if length(subgraphs(node)) > 0 name_face = ete.TextFace(nt.name, fgcolor="black", fsize=10) nt.add_face(name_face, column=0, position="branch-top") - for child in node.subgraphs + for child in subgraphs(node) treeview(child, level + 1, nt) end end diff --git a/src/computational_graph/optimize.jl b/src/computational_graph/optimize.jl index 2ceb47b3..b6e57e21 100644 --- a/src/computational_graph/optimize.jl +++ b/src/computational_graph/optimize.jl @@ -103,7 +103,7 @@ end function merge_all_linear_combinations!(g::AbstractGraph; verbose=0) verbose > 0 && println("merge nodes representing a linear combination of a non-unique list of graphs.") # Post-order DFS - for sub_g in g.subgraphs + for sub_g in subgraphs(g) merge_all_linear_combinations!(sub_g) merge_linear_combination!(sub_g) end @@ -128,7 +128,7 @@ function merge_all_linear_combinations!(graphs::Union{Tuple,AbstractVector{<:Abs verbose > 0 && println("merge nodes representing a linear combination of a non-unique list of graphs.") # Post-order DFS for g in graphs - merge_all_linear_combinations!(g.subgraphs) + merge_all_linear_combinations!(subgraphs(g)) merge_linear_combination!(g) end return graphs @@ -182,39 +182,39 @@ function merge_all_multi_products!(graphs::Union{Tuple,AbstractVector{<:Graph}}; end """ - function unique_leaves(_graphs::AbstractVector{<:AbstractGraph}) + function unique_leaves(graphs::AbstractVector{<:AbstractGraph}) Identifies and retrieves unique leaf nodes from a set of graphs. # Arguments: -- `_graphs`: A collection of graphs to be processed. +- `graphs`: A collection of graphs to be processed. # Returns: - The vector of unique leaf nodes. - A mapping dictionary from the id of each unique leaf node to its index in collect(1:length(leafs)). """ -function unique_leaves(_graphs::AbstractVector{<:AbstractGraph}) +function unique_leaves(graphs::AbstractVector{<:AbstractGraph}) ############### find the unique Leaves ##################### - uniqueGraph = [] + unique_graphs = [] mapping = Dict{Int,Int}() idx = 1 - for g in _graphs + for g in graphs flag = true - for (ie, e) in enumerate(uniqueGraph) + for (ie, e) in enumerate(unique_graphs) if isequiv(e, g, :id) - mapping[g.id] = ie + mapping[id(g)] = ie flag = false break end end if flag - push!(uniqueGraph, g) - mapping[g.id] = idx + push!(unique_graphs, g) + mapping[id(g)] = idx idx += 1 end end - return uniqueGraph, mapping + return unique_graphs, mapping end """ @@ -239,35 +239,35 @@ function remove_duplicated_leaves!(graphs::Union{Tuple,AbstractVector{<:Abstract if isnothing(normalize) == false @assert normalize isa Function "a function call is expected for normalize" for leaf in leaves - normalize(leaf.id) + normalize(id(leaf)) end end - sort!(leaves, by=x -> x.id) #sort the id of the leaves in an asscend order - unique!(x -> x.id, leaves) #filter out the leaves with the same id number + sort!(leaves, by=x -> id(x)) #sort the id of the leaves in an asscend order + unique!(x -> id(x), leaves) #filter out the leaves with the same id number - uniqueLeaf, leafMap = unique_leaves(leaves) - verbose > 0 && length(leaves) > 0 && println("Number of independent Leaves $(length(leaves)) → $(length(uniqueLeaf))") + _unique_leaves, leafmap = unique_leaves(leaves) + verbose > 0 && length(leaves) > 0 && println("Number of independent Leaves $(length(leaves)) → $(length(_unique_leaves))") for g in graphs for n in PreOrderDFS(g) - for (si, sub_g) in enumerate(n.subgraphs) + for (si, sub_g) in enumerate(subgraphs(n)) if isleaf(sub_g) - n.subgraphs[si] = uniqueLeaf[leafMap[sub_g.id]] + set_subgraph!(n, _unique_leaves[leafmap[id(sub_g)]], si) end end end end - return leafMap + return leafmap end """ - function burn_from_targetleaves!(graphs::AbstractVector{G}, targetleaves_id::AbstractVector{Int}; verbose=0) where {G<:AbstractGraph} + function burn_from_targetleaves!(graphs::AbstractVector{G}, targetleaves_id::AbstractVector{Int}; verbose=0) where {G <: AbstractGraph} Removes all nodes connected to the target leaves in-place via "Prod" operators. # Arguments: -- `graphs`: An AbstractVector of graphs. +- `graphs`: A vector of graphs. - `targetleaves_id::AbstractVector{Int}`: Vector of target leafs' id. - `verbose`: Level of verbosity (default: 0). @@ -278,33 +278,33 @@ function burn_from_targetleaves!(graphs::AbstractVector{G}, targetleaves_id::Abs verbose > 0 && println("remove all nodes connected to the target leaves via Prod operators.") graphs_sum = linear_combination(graphs, one.(eachindex(graphs))) - ftype = typeof(graphs[1].factor) + ftype = typeof(factor(graphs[1])) for leaf in Leaves(graphs_sum) - if !isdisjoint(leaf.id, targetleaves_id) - leaf.name = "BURNING" + if !isdisjoint(id(leaf), targetleaves_id) + set_name!(leaf, "BURNING") end end for node in PostOrderDFS(graphs_sum) - if any(x -> x.name == "BURNING", node.subgraphs) - if node.operator == Prod || node.operator <: Power - node.subgraphs = G[] - node.subgraph_factors = ftype[] - node.name = "BURNING" + if any(x -> name(x) == "BURNING", subgraphs(node)) + if operator(node) == Prod || operator(node) <: Power + set_subgraphs!(node, G[]) + set_subgraph_factors!(node, ftype[]) + set_name!(node, "BURNING") else - subgraphs = G[] - subgraph_factors = ftype[] - for (i, subg) in enumerate(node.subgraphs) - if subg.name != "BURNING" - push!(subgraphs, subg) - push!(subgraph_factors, node.subgraph_factors[i]) + _subgraphs = G[] + _subgraph_factors = ftype[] + for (i, subg) in enumerate(subgraphs(node)) + if name(subg) != "BURNING" + push!(_subgraphs, subg) + push!(_subgraph_factors, subgraph_factor(node, i)) end end - node.subgraphs = subgraphs - node.subgraph_factors = subgraph_factors - if isempty(subgraph_factors) - node.name = "BURNING" + set_subgraphs!(node, _subgraphs) + set_subgraph_factors!(node, _subgraph_factors) + if isempty(_subgraph_factors) + set_name!(node, "BURNING") end end end @@ -313,13 +313,13 @@ function burn_from_targetleaves!(graphs::AbstractVector{G}, targetleaves_id::Abs g_c0 = constant_graph(ftype(0)) has_c0 = false for g in graphs - if g.name == "BURNING" + if name(g) == "BURNING" has_c0 = true - g.id = g_c0.id - g.operator = Constant - g.factor = ftype(0) + set_id!(g, id(g_c0)) + set_operator!(g, Constant) + set_factor!(g, ftype(0)) end end - has_c0 ? (return g_c0.id) : (return nothing) + has_c0 ? (return id(g_c0)) : (return nothing) end \ No newline at end of file diff --git a/src/computational_graph/transform.jl b/src/computational_graph/transform.jl index a0683380..3c5c6380 100644 --- a/src/computational_graph/transform.jl +++ b/src/computational_graph/transform.jl @@ -3,7 +3,7 @@ """ function relabel!(g::FeynmanGraph, map::Dict{Int,Int}) - Relabels the quantum operators in g and its subgraphs according to `map`. + Relabels the quantum operators in `g` and its subgraphs according to `map`. For example, `map = {1=>2, 3=>2}`` will find all quantum operators with labels 1 and 3, and then map them to 2. # Arguments: @@ -11,9 +11,8 @@ - `map`: mapping from old labels to the new ones """ function relabel!(g::FeynmanGraph, map::Dict{Int,Int}) - for i in eachindex(vertices(g)) - op = vertices(g)[i] + op = vertex(g, i) for j in eachindex(op.operators) qo = op.operators[j] if haskey(map, qo.label) @@ -22,17 +21,16 @@ function relabel!(g::FeynmanGraph, map::Dict{Int,Int}) end end - for i in eachindex(g.subgraphs) - relabel!(g.subgraphs[i], map) + for i in eachindex(subgraphs(g)) + relabel!(subgraph(g, i), map) end - return g end """ function relabel(g::FeynmanGraph, map::Dict{Int,Int}) - Returns a copy of g with quantum operators in g and its subgraphs relabeled according to `map`. + Returns a copy of `g` with quantum operators in `g` and its subgraphs relabeled according to `map`. For example, `map = {1=>2, 3=>2}` will find all quantum operators with labels 1 and 3, and then map them to 2. # Arguments: @@ -44,7 +42,7 @@ relabel(g::FeynmanGraph, map::Dict{Int,Int}) = relabel!(deepcopy(g), map) """ function collect_labels(g::FeynmanGraph) - Returns the list of sorted unique labels in graph g. + Returns the list of sorted unique labels in graph `g`. # Arguments: - `g::FeynmanGraph`: graph to find labels for @@ -52,7 +50,7 @@ relabel(g::FeynmanGraph, map::Dict{Int,Int}) = relabel!(deepcopy(g), map) function collect_labels(g::FeynmanGraph) labels = Vector{Int}([]) for i in eachindex(vertices(g)) - op = vertices(g)[i] + op = vertex(g, i) for j in eachindex(op.operators) qo = op.operators[j] if !(qo.label in labels) @@ -68,8 +66,8 @@ end """ function standardize_labels!(g::FeynmanGraph) - Finds all labels involved in g and its subgraphs and - modifies g by relabeling in standardized order, e.g., + Finds all labels involved in `g` and its subgraphs and + modifies `g` by relabeling in standardized order, e.g., (1, 4, 5, 7, ...) ↦ (1, 2, 3, 4, ....) # Arguments: @@ -88,8 +86,8 @@ end """ function standardize_labels!(g::FeynmanGraph) - Finds all labels involved in g and its subgraphs and returns - a copy of g relabeled in a standardized order, e.g., + Finds all labels involved in `g` and its subgraphs and returns + a copy of `g` relabeled in a standardized order, e.g., (1, 4, 5, 7, ...) ↦ (1, 2, 3, 4, ....) # Arguments: @@ -100,8 +98,8 @@ standardize_labels(g::FeynmanGraph) = standardize_labels!(deepcopy(g)) """ function replace_subgraph!(g::AbstractGraph, w::AbstractGraph, m::AbstractGraph) - Modifies g by replacing the subgraph w with a new graph m. - For Feynman diagrams, subgraphs w and m should have the same diagram type, orders, and external indices. + Modifies `g` by replacing the subgraph `w` with a new graph `m`. + For Feynman diagrams, subgraphs `w` and `m` should have the same diagram type, orders, and external indices. # Arguments: - `g::AbstractGraph`: graph to be modified @@ -117,9 +115,9 @@ function replace_subgraph!(g::AbstractGraph, w::AbstractGraph, m::AbstractGraph) @assert external_indices(w) == external_indices(m) "Old and new subgraph should have the same external indices" end for node in PreOrderDFS(g) - for (i, child) in enumerate(children(node)) - if isequiv(child, w, :id) - node.subgraphs[i] = m + for (i, sub_g) in enumerate(subgraphs(node)) + if isequiv(sub_g, w, :id) + set_subgraph!(node, m, i) return end end @@ -129,8 +127,8 @@ end """ function replace_subgraph(g::AbstractGraph, w::AbstractGraph, m::AbstractGraph) - Creates a modified copy of g by replacing the subgraph w with a new graph m. - For Feynman diagrams, subgraphs w and m should have the same diagram type, orders, and external indices. + Creates a modified copy of `g` by replacing the subgraph `w` with a new graph `m`. + For Feynman diagrams, subgraphs `w` and `m` should have the same diagram type, orders, and external indices. # Arguments: - `g::AbstractGraph`: graph to be modified @@ -147,9 +145,9 @@ function replace_subgraph(g::AbstractGraph, w::AbstractGraph, m::AbstractGraph) end g_new = deepcopy(g) for node in PreOrderDFS(g_new) - for (i, child) in enumerate(children(node)) - if isequiv(child, w, :id) - node.subgraphs[i] = m + for (i, sub_g) in enumerate(subgraphs(node)) + if isequiv(sub_g, w, :id) + set_subgraph!(node, m, i) break end end @@ -163,19 +161,19 @@ end Recursively flattens chains of subgraphs within the given graph `g` by merging certain trivial unary subgraphs into their parent graphs in the in-place form. - Acts only on subgraphs of g with the following structure: 𝓞 --- 𝓞' --- ⋯ --- 𝓞'' ⋯ (!), + Acts only on subgraphs of `g` with the following structure: 𝓞 --- 𝓞' --- ⋯ --- 𝓞'' ⋯ (!), where the stop-case (!) represents a leaf, a non-trivial unary operator 𝓞'''(g) != g, or a non-unary operation. # Arguments: - `g::AbstractGraph`: graph to be modified """ function flatten_chains!(g::AbstractGraph) - for (i, sub_g) in enumerate(g.subgraphs) - if onechild(sub_g) && unary_istrivial(sub_g.operator) + for (i, sub_g) in enumerate(subgraphs(g)) + if unary_istrivial(sub_g) && onechild(sub_g) flatten_chains!(sub_g) - - g.subgraph_factors[i] *= sub_g.subgraph_factors[1] - g.subgraphs[i] = eldest(sub_g) + new_factor = subgraph_factor(g, i) * subgraph_factor(sub_g) + set_subgraph_factor!(g, new_factor, i) + set_subgraph!(g, eldest(sub_g), i) end end return g @@ -193,98 +191,58 @@ end flatten_chains(g::AbstractGraph) = flatten_chains!(deepcopy(g)) """ - function merge_linear_combination(g::Graph) + function merge_linear_combination(g::AbstractGraph) - Returns a copy of graph g with multiplicative prefactors factorized, - e.g., 3*g1 + 5*g2 + 7*g1 + 9*g2 ↦ 10*g1 + 14*g2. Does nothing if the - graph g does not represent a Sum operation. + Modifies a computational graph `g` by factorizing multiplicative prefactors, e.g., + 3*g1 + 5*g2 + 7*g1 + 9*g2 ↦ 10*g1 + 14*g2 = linear_combination(g1, g2, 10, 14). + Returns a linear combination of unique subgraphs and their total prefactors. + Does nothing if the graph `g` does not represent a Sum operation. # Arguments: -- `g::Graph`: graph to be modified +- `g::AbstractGraph`: graph to be modified """ -function merge_linear_combination(g::Graph{F,W}) where {F,W} - if g.operator == Sum - added = falses(length(g.subgraphs)) - subg_fac = eltype(g.subgraph_factors)[] - subg = eltype(g.subgraphs)[] +function merge_linear_combination!(g::AbstractGraph) + if operator(g) == Sum + subg = subgraphs(g) + subg_fac = subgraph_factors(g) + added = falses(length(subg)) + merged_subg = eltype(subg)[] + merged_subg_fac = eltype(subg_fac)[] k = 0 for i in eachindex(added) added[i] && continue - push!(subg, g.subgraphs[i]) - push!(subg_fac, g.subgraph_factors[i]) + push!(merged_subg, subg[i]) + push!(merged_subg_fac, subg_fac[i]) added[i] = true k += 1 - for j in (i+1):length(g.subgraphs) - if added[j] == false && isequiv(g.subgraphs[i], g.subgraphs[j], :id) + for j in (i+1):length(subg) + if added[j] == false && isequiv(subg[i], subg[j], :id) added[j] = true - subg_fac[k] += g.subgraph_factors[j] + merged_subg_fac[k] += subg_fac[j] end end end - g_merged = Graph(subg; subgraph_factors=subg_fac, operator=Sum(), ftype=F, wtype=W) - return g_merged - else - return g + set_subgraphs!(g, merged_subg) + set_subgraph_factors!(g, merged_subg_fac) end + return g end """ - function merge_linear_combination(g::FeynmanGraph) + function merge_linear_combination(g::AbstractGraph) - Returns a copy of Feynman graph g with multiplicative prefactors factorized, + Returns a copy of computational graph `g` with multiplicative prefactors factorized, e.g., 3*g1 + 5*g2 + 7*g1 + 9*g2 ↦ 10*g1 + 14*g2 = linear_combination(g1, g2, 10, 14). Returns a linear combination of unique subgraphs and their total prefactors. - Does nothing if the graph g does not represent a Sum operation. + Does nothing if the graph `g` does not represent a Sum operation. # Arguments: -- `g::FeynmanGraph`: graph to be modified +- `g::AbstractGraph`: graph to be modified """ -function merge_linear_combination(g::FeynmanGraph{F,W}) where {F,W} - if g.operator == Sum - added = falses(length(g.subgraphs)) - subg_fac = eltype(g.subgraph_factors)[] - subg = eltype(g.subgraphs)[] - k = 0 - for i in eachindex(added) - added[i] && continue - push!(subg, g.subgraphs[i]) - push!(subg_fac, g.subgraph_factors[i]) - added[i] = true - k += 1 - for j in (i+1):length(g.subgraphs) - if added[j] == false && isequiv(g.subgraphs[i], g.subgraphs[j], :id) - added[j] = true - subg_fac[k] += g.subgraph_factors[j] - end - end - end - g_merged = FeynmanGraph(subg, g.properties; subgraph_factors=subg_fac, operator=Sum(), ftype=F, wtype=W) - return g_merged - else - return g - end -end - -function merge_linear_combination!(g::Graph{F,W}) where {F,W} - if g.operator == Sum - g_merged = merge_linear_combination(g) - g.subgraphs = g_merged.subgraphs - g.subgraph_factors = g_merged.subgraph_factors - end - return g -end - -function merge_linear_combination!(g::FeynmanGraph{F,W}) where {F,W} - if g.operator == Sum - g_merged = merge_linear_combination(g) - g.subgraphs = g_merged.subgraphs - g.subgraph_factors = g_merged.subgraph_factors - end - return g -end +merge_linear_combination(g::AbstractGraph) = merge_linear_combination!(deepcopy(g)) """ - function merge_multi_product(g::Graph{F,W}) where {F,W} + function merge_multi_product!(g::Graph{F,W}) where {F,W} Merge multiple products within a computational graph `g` if they share the same operator (`Prod`). If `g.operator == Prod`, this function will merge `N` identical subgraphs into a single subgraph with a power operator `Power(N)`. @@ -297,7 +255,7 @@ end - A merged computational graph with potentially fewer subgraphs if there were repeating subgraphs with the `Prod` operator. If the input graph's operator isn't `Prod`, the function returns the input graph unchanged. """ -function merge_multi_product(g::Graph{F,W}) where {F,W} +function merge_multi_product!(g::Graph{F,W}) where {F,W} if g.operator == Prod unique_graphs = Vector{Graph{F,W}}() unique_factors = F[] @@ -315,30 +273,38 @@ function merge_multi_product(g::Graph{F,W}) where {F,W} end if length(unique_factors) == 1 - g_merged = Graph(unique_graphs; subgraph_factors=unique_factors, operator=Power(repeated_counts[1]), ftype=F, wtype=W) + g.subgraphs = unique_graphs + g.subgraph_factors = unique_factors + g.operator = typeof(Power(repeated_counts[1])) else - subgraphs = Vector{Graph{F,W}}() + _subgraphs = Vector{Graph{F,W}}() for (idx, g) in enumerate(unique_graphs) if repeated_counts[idx] == 1 - push!(subgraphs, g) + push!(_subgraphs, g) else - push!(subgraphs, Graph([g], operator=Power(repeated_counts[idx]), ftype=F, wtype=W)) + push!(_subgraphs, Graph([g], operator=Power(repeated_counts[idx]), ftype=F, wtype=W)) end end - g_merged = Graph(subgraphs; subgraph_factors=unique_factors, operator=Prod(), ftype=F, wtype=W) + g.subgraphs = _subgraphs + g.subgraph_factors = unique_factors + g.operator = Prod end - return g_merged - else - return g end + return g end -function merge_multi_product!(g::Graph{F,W}) where {F,W} - if g.operator == Prod - g_merged = merge_multi_product(g) - g.subgraphs = g_merged.subgraphs - g.subgraph_factors = g_merged.subgraph_factors - g.operator = g_merged.operator - end - return g -end \ No newline at end of file +""" + function merge_multi_product(g::Graph{F,W}) where {F,W} + + Returns a copy of computational graph `g` with multiple products merged if they share the same operator (`Prod`). + If `g.operator == Prod`, this function will merge `N` identical subgraphs into a single subgraph with a power operator `Power(N)`. + The function ensures each unique subgraph is counted and merged appropriately, preserving any distinct subgraph_factors associated with them. + +# Arguments: +- `g::Graph`: graph to be modified + +# Returns +- A merged computational graph with potentially fewer subgraphs if there were repeating subgraphs + with the `Prod` operator. If the input graph's operator isn't `Prod`, the function returns the input graph unchanged. +""" +merge_multi_product(g::AbstractGraph) = merge_multi_product!(deepcopy(g)) diff --git a/src/computational_graph/tree_properties.jl b/src/computational_graph/tree_properties.jl index ef463732..b315e13d 100644 --- a/src/computational_graph/tree_properties.jl +++ b/src/computational_graph/tree_properties.jl @@ -1,9 +1,9 @@ -##################### AbstractTrees interface for AbstracGraphs ########################### +##################### AbstractTrees interface for AbstractGraphs ########################### ## Things that make printing prettier -AbstractTrees.printnode(io::IO, g::AbstractGraph) = print(io, "\u001b[32m$(g.id)\u001b[0m : $g") +AbstractTrees.printnode(io::IO, g::AbstractGraph) = print(io, "\u001b[32m$(id(g))\u001b[0m : $g") -## Guarantee type-stable tree iteration for Graphs, StableGraphs, and FeynmanGraphs +## Guarantee type-stable tree iteration for Graphs and FeynmanGraphs AbstractTrees.NodeType(::Graph) = HasNodeType() AbstractTrees.NodeType(::FeynmanGraph) = HasNodeType() AbstractTrees.nodetype(::Graph{F,W}) where {F,W} = Graph{F,W} @@ -18,7 +18,7 @@ Base.IteratorEltype(::Type{<:TreeIterator{FeynmanGraph{F,W}}}) where {F,W} = Bas Base.eltype(::Type{<:TreeIterator{FeynmanGraph{F,W}}}) where {F,W} = FeynmanGraph{F,W} function AbstractTrees.children(g::AbstractGraph) - return g.subgraphs + return subgraphs(g) end ##################### Tree properties ########################### @@ -31,7 +31,7 @@ end # Arguments: - `g::AbstractGraph`: graph to be analyzed """ -haschildren(g::AbstractGraph) = isempty(g.subgraphs) == false +haschildren(g::AbstractGraph) = isempty(subgraphs(g)) == false """ function onechild(g::AbstractGraph) @@ -51,7 +51,7 @@ onechild(g::AbstractGraph) = length(children(g)) == 1 # Arguments: - `g::AbstractGraph`: graph to be analyzed """ -isleaf(g::AbstractGraph) = isempty(g.subgraphs) +isleaf(g::AbstractGraph) = isempty(subgraphs(g)) """ function isbranch(g::AbstractGraph) @@ -92,9 +92,9 @@ end """ function isfactorless(g::AbstractGraph) if isleaf(g) - return isapprox_one(g.factor) + return isapprox_one(factor(g)) else - return all(isapprox_one.([g.factor; g.subgraph_factors])) + return all(isapprox_one.([factor(g); subgraph_factors(g)])) end end @@ -108,7 +108,7 @@ end """ function eldest(g::AbstractGraph) @assert haschildren(g) "Graph has no children!" - return children(g)[1] + return subgraph(g) end """ diff --git a/test/computational_graph.jl b/test/computational_graph.jl index 37ab49bf..c165f24c 100644 --- a/test/computational_graph.jl +++ b/test/computational_graph.jl @@ -9,6 +9,114 @@ struct O2 <: Graphs.AbstractOperator end struct O3 <: Graphs.AbstractOperator end Graphs.unary_istrivial(::Type{O}) where {O<:Union{O1,O2,O3}} = true +@testset verbose = true "AbstractGraph interface" begin + mutable struct ConcreteGraph <: Graphs.AbstractGraph + id::Int + name::String + orders::Vector{Int} + operator::DataType + subgraphs::Vector{ConcreteGraph} + subgraph_factors::Vector{Float64} + factor::Float64 + weight::Float64 + function ConcreteGraph(subgraphs=[]; name="", orders=zeros(Int, 0), operator=O(), subgraph_factors=[], factor=1.0, weight=1.0) + return new(Graphs.uid(), name, orders, typeof(operator), subgraphs, subgraph_factors, factor, weight) + end + end + + Graphs.uidreset() + g1 = ConcreteGraph(; operator=O1()) + g2 = ConcreteGraph(; operator=O2()) + g3 = ConcreteGraph(; operator=O3()) + g = ConcreteGraph([g1, g2, g3]; subgraph_factors=[2, 3, 5], operator=O()) + gp = ConcreteGraph([g1, g2, g3]; subgraph_factors=[2, 3, 5], operator=O()) + h = ConcreteGraph([g1, g2, g3]; name="h", subgraph_factors=[2, 3, 5], operator=O()) + + # weight(g::AbstractGraph) is an abstract method + @test isnothing(Graphs.weight(ConcreteGraph())) + + # Base.:+(g1::AbstractGraph, g2::AbstractGraph) is an abstract method + err = AssertionError() + try + g1 + g2 + catch err + end + @test err isa ErrorException + @test err.msg == "Method not yet implemented for user-defined graph type ConcreteGraph." + + ### AbstractGraph interface for ConcreteGraph ### + + # Getters + Graphs.id(g::ConcreteGraph) = g.id + Graphs.name(g::ConcreteGraph) = g.name + Graphs.orders(g::ConcreteGraph) = g.orders + Graphs.operator(g::ConcreteGraph) = g.operator + Graphs.factor(g::ConcreteGraph) = g.factor + Graphs.weight(g::ConcreteGraph) = g.weight + Graphs.subgraph(g::ConcreteGraph, i=1) = g.subgraphs[i] + Graphs.subgraphs(g::ConcreteGraph) = g.subgraphs + Graphs.subgraph_factor(g::ConcreteGraph, i=1) = g.subgraph_factors[i] + Graphs.subgraph_factors(g::ConcreteGraph) = g.subgraph_factors + + # Setters + Graphs.set_name!(g::ConcreteGraph, name::AbstractString) = (g.name = name) + Graphs.set_subgraph!(g::ConcreteGraph, subgraph::ConcreteGraph, i=1) = (g.subgraphs[i] = subgraph) + Graphs.set_subgraphs!(g::ConcreteGraph, subgraphs::Vector{ConcreteGraph}) = (g.subgraphs = subgraphs) + Graphs.set_subgraph_factor!(g::ConcreteGraph, subgraph_factor::Float64, i=1) = (g.subgraph_factors[i] = subgraph_factor) + Graphs.set_subgraph_factors!(g::ConcreteGraph, subgraph_factors::AbstractVector) = (g.subgraph_factors = subgraph_factors) + + ############################### + + @testset "Traits" begin + @test Graphs.unary_istrivial(g1) == true + @test Graphs.unary_istrivial(g2) == true + @test Graphs.unary_istrivial(g3) == true + @test Graphs.unary_istrivial(g) == false + end + @testset "Getters" begin + @test Graphs.id(g) == 4 + @test Graphs.name(g) == "" + @test Graphs.orders(g) == zeros(Int, 0) + @test Graphs.operator(g) == O + @test Graphs.factor(g) == 1.0 + @test Graphs.weight(g) == 1.0 + @test Graphs.subgraph(g) == g1 + @test Graphs.subgraph(g, 2) == g2 + @test Graphs.subgraphs(g) == [g1, g2, g3] + @test Graphs.subgraphs(g, [2, 1]) == [g2, g1] # default method + @test Graphs.subgraph_factor(g) == 2.0 + @test Graphs.subgraph_factor(g, 2) == 3.0 + @test Graphs.subgraph_factors(g) == [2.0, 3.0, 5.0] + @test Graphs.subgraph_factors(g, [2, 1]) == [3.0, 2.0] # default method + end + @testset "Setters" begin + Graphs.set_name!(g, "g") + @test Graphs.name(g) == "g" + Graphs.set_subgraph!(g, g2, 1) + @test Graphs.subgraph(g) == g2 + Graphs.set_subgraphs!(g, [g1, g2, g3]) + @test Graphs.subgraphs(g) == [g1, g2, g3] + Graphs.set_subgraphs!(g, [g3, g1, g2], [3, 1, 2]) # default method + @test Graphs.subgraphs(g) == [g1, g2, g3] + Graphs.set_subgraph_factor!(g, 0.0, 1) + @test Graphs.subgraph_factor(g) == 0.0 + Graphs.set_subgraph_factors!(g, [2.0, 3.0, 5.0]) + @test Graphs.subgraph_factors(g) == [2.0, 3.0, 5.0] + Graphs.set_subgraph_factors!(g, [5.0, 2.0, 3.0], [3, 1, 2]) # default method + @test Graphs.subgraph_factors(g) == [2.0, 3.0, 5.0] + end + @testset "Equivalence" begin + Graphs.set_name!(g, Graphs.name(gp)) + @test g == g + @test g != gp + @test Graphs.isequal(g, g) + @test Graphs.isequal(g, gp) == false + @test Graphs.isequiv(g, gp, :id) + @test Graphs.isequiv(g, h, :id) == false + @test Graphs.isequiv(g, h, :id, :name) == true + end +end + @testset verbose = true "Graph" begin @testset verbose = true "Operations" begin g1 = Graph([]) @@ -155,7 +263,7 @@ Graphs.unary_istrivial(::Type{O}) where {O<:Union{O1,O2,O3}} = true @test h8.subgraph_factors == [36] @test isequiv(h7_lc, h8, :id) end - @testset "Merge multi prodict" begin + @testset "Merge multi-pproduct" begin g1 = Graph([]) g2 = Graph([], factor=2) g3 = Graph([], factor=3) @@ -222,7 +330,7 @@ Graphs.unary_istrivial(::Type{O}) where {O<:Union{O1,O2,O3}} = true Graphs.flatten_all_chains!(rvec) @test rvec == [r1, r2, r3] end - @testset "merge all linear combinations" begin + @testset "Merge all linear combinations" begin g1 = Graph([]) g2 = 2 * g1 g3 = Graph([], factor=3.0) @@ -240,7 +348,7 @@ Graphs.unary_istrivial(::Type{O}) where {O<:Union{O1,O2,O3}} = true Graphs.merge_all_linear_combinations!(h0) @test isequiv(h0.subgraphs[1], _h, :id) end - @testset "Merge all multi prodicts" begin + @testset "Merge all multi-products" begin g1 = Graph([]) g2 = Graph([], factor=2) g3 = Graph([], factor=3) @@ -299,35 +407,35 @@ end @test external_indices(g1) == [1, 5, 9, 10] @test external_operators(g1) == 𝑓⁺(1)𝑓⁺(5)𝑓⁺(9)𝑓⁺(10) @test external_legs(g1) == [false, false, true, true] - parameters = FeynmanProperties( + properties = FeynmanProperties( diagram_type(g1), vertices(g1), topology(g1), external_indices(g1), external_legs(g1), ) - parameters_no_topology = FeynmanProperties( + properties_no_topology = FeynmanProperties( diagram_type(g1), vertices(g1), [], external_indices(g1), external_legs(g1), ) - @test parameters == g1.properties - @test parameters != parameters_no_topology - @test parameters_no_topology == drop_topology(g1.properties) + @test properties == g1.properties + @test properties != properties_no_topology + @test properties_no_topology == drop_topology(g1.properties) end @testset "Equivalence" begin g1_new_instance = FeynmanGraph(V; topology=[[2, 6], [3, 7], [4, 9], [8, 10]], external_indices=[1, 5, 9, 10], external_legs=[false, false, true, true]) - g1_from_parameters = FeynmanGraph(V, g1.properties) + g1_from_properties = FeynmanGraph(V, g1.properties) # Test equivalence modulo fields id/factor @test isequiv(g1, g1_new_instance) == false - @test isequiv(g1, g1_from_parameters) == false + @test isequiv(g1, g1_from_properties) == false @test isequiv(g1, g2p, :id) == false @test isequiv(g1, g2p, :factor) == false @test isequiv(g1, g1_new_instance, :id) - @test isequiv(g1, g1_from_parameters, :id) + @test isequiv(g1, g1_from_properties, :id) @test isequiv(g1, g2p, :id, :factor) # Test inequivalence when subgraph lengths are different t = g1 + g1 @@ -671,6 +779,25 @@ end end end +@testset verbose = true "Conversions" begin + g = Graph([]; factor=-1.0, operator=Graphs.Sum()) + g1 = Graph([]; operator=O1()) + g2 = Graph([]; operator=O2()) + g_feyn = propagator(𝑓⁺(1)𝑓⁻(2)) # equivalent to g after conversion + # Test constructor for FeynmanGraph from Graph and FeynmanProperties + g_feyn_conv = FeynmanGraph(g, g_feyn.properties) + @test isequiv(g_feyn, g_feyn_conv, :id) + # Test implicit and explicit FeynmanGraph -> Graph conversion + g_conv_implicit_v1::Graph = g_feyn + g_conv_implicit_v2::Graph{Float64,Float64} = g_feyn + g_conv_explicit_v1 = convert(Graph, g_feyn) + g_conv_explicit_v2 = convert(Graph{Float64,Float64}, g_feyn) + @test isequiv(g, g_conv_implicit_v1, :id) + @test isequiv(g, g_conv_implicit_v2, :id) + @test isequiv(g, g_conv_explicit_v1, :id) + @test isequiv(g, g_conv_explicit_v2, :id) +end + @testset verbose = true "Evaluation" begin using FeynmanDiagram.ComputationalGraphs: eval!