From b963b9eea311d39d4873fcbb8f48bec7a51ca7d2 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Sun, 22 Oct 2023 18:02:34 -0400 Subject: [PATCH 1/9] Update AbstractGraph interface, add Graph to/from FeynmanGraph conversions --- src/backend/compiler.jl | 2 +- src/backend/static.jl | 63 +++--- src/computational_graph/ComputationalGraph.jl | 1 + src/computational_graph/abstractgraph.jl | 200 +++++++++++++++++- src/computational_graph/conversions.jl | 34 +++ src/computational_graph/feynmangraph.jl | 98 ++++++--- src/computational_graph/graph.jl | 52 +++-- src/computational_graph/io.jl | 20 +- src/computational_graph/optimize.jl | 26 +-- src/computational_graph/transform.jl | 165 ++++++--------- src/computational_graph/tree_properties.jl | 16 +- test/computational_graph.jl | 113 +++++++++- 12 files changed, 570 insertions(+), 220 deletions(-) create mode 100644 src/computational_graph/conversions.jl 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 003fc07d..6034f6e3 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 return "(g$(subgraphs[1].id) * $(subgraph_factors[1]))" else @@ -6,7 +18,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 return "(g$(subgraphs[1].id))" else @@ -14,7 +26,7 @@ function _to_static(::Type{ComputationalGraphs.Prod}, subgraphs::Vector{Graph{F, end end -function _to_static(::Type{ComputationalGraphs.Sum}, subgraphs::Vector{FeynmanGraph{F,W}}, subgraph_factors::Vector{F}) where {F,W} +function to_static(::Type{ComputationalGraphs.Sum}, subgraphs::Vector{FeynmanGraph{F,W}}, subgraph_factors::Vector{F}) where {F,W} if length(subgraphs) == 1 return "(g$(subgraphs[1].id) * $(subgraph_factors[1]))" else @@ -22,7 +34,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 return "(g$(subgraphs[1].id))" else @@ -30,28 +42,29 @@ function _to_static(::Type{ComputationalGraphs.Prod}, subgraphs::Vector{FeynmanG end 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 for graph in graphs for g in PostOrderDFS(graph) #leaf first search - if g.id in root - target = "root[$(findfirst(x -> x == g.id, root))]" + if id(g) in root + target = "root[$(findfirst(x -> x == id(g), root))]" else - target = "g$(g.id)" + target = "g$(id(g))" end - if isempty(g.subgraphs) #leaf + if isempty(subgraphs(g)) #leaf body *= " $target = leaf[$leafidx]\n " leafidx += 1 else - body *= " $target = $(_to_static(g.operator, g.subgraphs, g.subgraph_factors))*$(g.factor)\n " + body *= " $target = $(to_static(operator(g), subgraphs(g), subgraph_factors(g)))*$(factor(g))\n " end end end @@ -60,7 +73,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`. @@ -69,26 +82,26 @@ 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 = "" for graph in graphs for g in PostOrderDFS(graph) #leaf first search - if g.id in root - target = "root[$(findfirst(x -> x == g.id, root))]" + if id(g) in root + target = "root[$(findfirst(x -> x == id(g), root))]" else - target = "g$(g.id)" + target = "g$(id(g))" end - if isempty(g.subgraphs) #leaf - g.name == "compiled" && continue - body *= " $target = leafVal[$(leafMap[g.id])]\n " - g.name = "compiled" + if isempty(subgraphs(g)) #leaf + name(g) == "compiled" && continue + body *= " $target = leafVal[$(leafMap[id(g)])]\n " + set_name!(g, "compiled") else - body *= " $target = $(_to_static(g.operator, g.subgraphs, g.subgraph_factors))*$(g.factor)\n " + body *= " $target = $(to_static(operator(g), subgraphs(g), subgraph_factors(g)))*$(factor(g))\n " end end end @@ -97,7 +110,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. @@ -119,7 +132,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) @@ -127,7 +140,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 7b9a3b64..73e29654 100644 --- a/src/computational_graph/ComputationalGraph.jl +++ b/src/computational_graph/ComputationalGraph.jl @@ -21,6 +21,7 @@ export unary_istrivial, isassociative, isequiv include("graph.jl") include("feynmangraph.jl") +include("conversions.jl") export Graph, FeynmanGraph, FeynmanProperties # export DiagramType diff --git a/src/computational_graph/abstractgraph.jl b/src/computational_graph/abstractgraph.jl index ca9a05c9..24b1a4c6 100644 --- a/src/computational_graph/abstractgraph.jl +++ b/src/computational_graph/abstractgraph.jl @@ -24,11 +24,192 @@ 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 subgraphs(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_name!(g::AbstractGraph, name::AbstractString) + + Update the name of graph `g` to `name`. +""" +function set_name!(g::AbstractGraph, name::AbstractString) 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 # graph weights approximately equal 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 @@ -40,17 +221,20 @@ 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 + (weight(a) ≈ weight(b)) == false && return false # graph weights approximately equal + # 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 diff --git a/src/computational_graph/conversions.jl b/src/computational_graph/conversions.jl new file mode 100644 index 00000000..1665fa6c --- /dev/null +++ b/src/computational_graph/conversions.jl @@ -0,0 +1,34 @@ + +""" + function Base.convert(Graph, g::FeynmanGraph) + + 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{Graph{F,W}}, g::FeynmanGraph{F,W}) where {F,W} + return Graph{F,W}(g.subgraphs; subgraph_factors=g.subgraph_factors, name=g.name, operator=g.operator, orders=g.orders, factor=g.factor, weight=g.weight) +end + +function Base.convert(::Type{FeynmanGraph{F,W}}, 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 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, c2) where {F,W} = linear_combination(Base.promote(g1, g2)..., c1, c2) +linear_combination(g1::FeynmanGraph{F,W}, g2::Graph{F,W}, c1, c2) where {F,W} = linear_combination(Base.promote(g1, g2)..., c1, c2) +linear_combination(graphs::Vector{Union{Graph{F,W},FeynmanGraph{F,W}}}, constants::AbstractVector) where {F,W} = linear_combination(Base.promote(graphs)..., constants) +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)...) +Base.:*(g1::Graph, g2::FeynmanGraph) = error("Multiplication of Feynman graphs is not well defined!") +Base.:*(g1::FeynmanGraph, g2::Graph) = error("Multiplication of Feynman graphs is not well defined!") diff --git a/src/computational_graph/feynmangraph.jl b/src/computational_graph/feynmangraph.jl index ccbf6f8f..d20dc013 100644 --- a/src/computational_graph/feynmangraph.jl +++ b/src/computational_graph/feynmangraph.jl @@ -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. @@ -122,13 +122,13 @@ 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. @@ -144,10 +144,51 @@ mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph @assert length(properties.external_indices) == length(properties.external_legs) 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, properties::FeynmanProperties) + @assert length(properties.external_indices) == length(properties.external_legs) + return new{ftype,wtype}(uid(), g.name, g.orders, properties, g.subgraphs, g.subgraph_factors, typeof(g.operator), g.factor, g.weight) + end end -""" - function is_external_operators(g::FeynmanGraph, i::Int) +### 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_name!(g::FeynmanGraph, name::AbstractString) = (g.name = name) +set_subgraph!(g::FeynmanGraph, subgraph::FeynmanGraph, i=1) = (g.subgraphs[i] = subgraph) +set_subgraphs!(g::FeynmanGraph, subgraphs::Vector{FeynmanGraph}) = (g.subgraphs = subgraphs) +set_subgraphs!(g::FeynmanGraph, subgraphs::Vector{FeynmanGraph}, indices::AbstractVector{Int}) = (g.subgraphs[indices] = subgraphs) +set_subgraph_factor!(g::FeynmanGraph, subgraph_factor::FeynmanGraph, i=1) = (g.subgraph_factors[i] = subgraph_factor) +set_subgraph_factors!(g::FeynmanGraph, subgraph_factors::AbstractVector) = (g.subgraph_factors = subgraph_factors) +set_subgraph_factors!(g::FeynmanGraph, subgraph_factors::AbstractVector, indices::AbstractVector{Int}) = (g.subgraph_factors[indices] = subgraph_factors) + +############################### + +""" +function is_external_operators(g::FeynmanGraph, i::Int) Check if `i::Int` in the external indices of FeynmanGraph `g`. """ @@ -168,18 +209,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) @@ -229,7 +271,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`. @@ -237,7 +279,7 @@ 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) @@ -248,7 +290,7 @@ function Base.:*(g1::FeynmanGraph{F,W}, c2::C) where {F,W,C} 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`. @@ -256,7 +298,7 @@ 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) @@ -267,16 +309,16 @@ function Base.:*(c1::C, g2::FeynmanGraph{F,W}) where {F,W,C} 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`. - 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 - `g2` second Feynman graph """ -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} @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." @@ -297,18 +339,18 @@ 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) 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 (𝐜 ⋅ 𝐠). All input Feynman graphs - must have the same diagram type, orders, and external vertices. + graph representing the linear combination (𝐜 ⋅ 𝐠). + All input diagrams must have the same diagram type, orders, and external vertices. # Arguments: - `g1` first Feynman graph - `g2` second Feynman graph """ -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) 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." @@ -316,7 +358,7 @@ 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)) - g = FeynmanGraph(graphs, properties; subgraph_factors=constants, operator=Sum(), orders=orders(g1), ftype=F, wtype=W) + g = FeynmanGraph(graphs, properties; subgraph_factors=F.(constants), operator=Sum(), orders=orders(g1), ftype=F, wtype=W) # Convert multiplicative links to in-place form for (i, sub_g) in enumerate(g.subgraphs) if sub_g.operator == Prod && onechild(sub_g) @@ -330,8 +372,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 @@ -345,7 +387,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 @@ -356,7 +398,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 f07e77f9..d7140e38 100644 --- a/src/computational_graph/graph.jl +++ b/src/computational_graph/graph.jl @@ -29,7 +29,7 @@ mutable struct Graph{F,W} <: AbstractGraph # Graph id::Int name::String # "" by default orders::Vector{Int} - + subgraphs::Vector{Graph{F,W}} subgraph_factors::Vector{F} @@ -61,15 +61,35 @@ mutable struct Graph{F,W} <: AbstractGraph # Graph end end -""" - function orders(g::Graph) +### AbstractGraph interface for Graph ### - Returns the derivative orders (::Vector{Int}) of Graph `g`. -""" +# Getters +id(g::Graph) = g.id +name(g::Graph) = g.name orders(g::Graph) = g.orders - -""" - function Base.:*(g1::Graph{F,W}, c2::C) where {F,W,C} +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_name!(g::Graph, name::AbstractString) = (g.name = name) +set_subgraph!(g::Graph, subgraph::Graph, i=1) = (g.subgraphs[i] = subgraph) +set_subgraphs!(g::Graph, subgraphs::Vector{Graph}) = (g.subgraphs = subgraphs) +set_subgraphs!(g::Graph, subgraphs::Vector{Graph}, indices::AbstractVector{Int}) = (g.subgraphs[indices] = subgraphs) +set_subgraph_factor!(g::Graph, subgraph_factor::Graph, i=1) = (g.subgraph_factors[i] = subgraph_factor) +set_subgraph_factors!(g::Graph, subgraph_factors::AbstractVector) = (g.subgraph_factors = subgraph_factors) +set_subgraph_factors!(g::Graph, subgraph_factors::AbstractVector, indices::AbstractVector{Int}) = (g.subgraph_factors[indices] = subgraph_factors) + +############################### + +""" + function Base.:*(g1::Graph{F,W}, c2) where {F,W} Returns a graph representing the scalar multiplication `g1*c2`. @@ -77,7 +97,7 @@ 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 g1.operator == Prod && onechild(g1) @@ -88,7 +108,7 @@ function Base.:*(g1::Graph{F,W}, c2::C) where {F,W,C} 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`. @@ -96,7 +116,7 @@ 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 g2.operator == Prod && onechild(g2) @@ -107,7 +127,7 @@ function Base.:*(c1::C, g2::Graph{F,W}) where {F,W,C} 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`. Graphs `g1` and `g2` must have the same orders. @@ -118,7 +138,7 @@ end - `c1` first scalar multiple - `c2` second scalar multiple """ -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} @assert orders(g1) == orders(g2) "g1 and g2 have different orders." g = Graph([g1, g2]; subgraph_factors=[F(c1), F(c2)], operator=Sum(), orders=orders(g1), ftype=F, wtype=W) # Convert multiplicative links to in-place form @@ -134,7 +154,7 @@ function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1::C, c2::C) where 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::Vector{C}) 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 @@ -145,11 +165,11 @@ end - `graphs` vector of computational graphs - `constants` vector of scalar multiples """ -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) where {F,W} @assert alleq(orders.(graphs)) "Graphs do not all have the same order." # parameters = union(getproperty.(graphs, :parameters)) g1 = graphs[1] - g = Graph(graphs; subgraph_factors=constants, operator=Sum(), orders=orders(g1), ftype=F, wtype=W) + g = Graph(graphs; subgraph_factors=F.(constants), operator=Sum(), orders=orders(g1), ftype=F, wtype=W) # Convert multiplicative links to in-place form for (i, sub_g) in enumerate(g.subgraphs) if sub_g.operator == Prod && onechild(sub_g) diff --git a/src/computational_graph/io.jl b/src/computational_graph/io.jl index 32e20e88..dda83be0 100644 --- a/src/computational_graph/io.jl +++ b/src/computational_graph/io.jl @@ -27,20 +27,20 @@ function short_orders(orders) end function _stringrep(graph::AbstractGraph, color=true) - namestr = isempty(graph.name) ? "" : "-$(graph.name)" - idstr = "$(graph.id)$namestr" + namestr = isempty(name(graph)) ? "" : "-$(name(graph))" + idstr = "$(id(graph))$namestr" if graph isa FeynmanGraph idstr *= ":$(_ops_to_str(vertices(graph)))" end - fstr = short(graph.factor, one(graph.factor)) - wstr = short(graph.weight) + 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 +50,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(["$(g.id)" for g in subgraphs(graph)], ",") typestr = "($typestr)" end print(io, "$(_stringrep(graph, true))$typestr") @@ -82,10 +82,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 b5bddf9a..146a0c0f 100644 --- a/src/computational_graph/optimize.jl +++ b/src/computational_graph/optimize.jl @@ -19,7 +19,7 @@ end function merge_all_chain_prefactors!(g::AbstractGraph; verbose=0) verbose > 0 && println("merge prefactors of all nodes representing trivial unary chains toward root level.") # Post-order DFS - for sub_g in g.subgraphs + for sub_g in subgraphs(g) merge_all_chain_prefactors!(sub_g) merge_chain_prefactors!(sub_g) end @@ -31,7 +31,7 @@ function merge_all_chain_prefactors!(graphs::AbstractVector{<:AbstractGraph}; ve verbose > 0 && println("merge prefactors of all nodes representing trivial unary chains toward root level.") # Post-order DFS for g in graphs - merge_all_chain_prefactors!(g.subgraphs) + merge_all_chain_prefactors!(subgraphs(g)) merge_chain_prefactors!(g) end return graphs @@ -40,7 +40,7 @@ end function merge_all_factorless_chains!(g::AbstractGraph; verbose=0) verbose > 0 && println("merge all nodes representing factorless trivial unary chains.") # Post-order DFS - for sub_g in g.subgraphs + for sub_g in subgraphs(g) merge_all_factorless_chains!(sub_g) merge_factorless_chain!(sub_g) end @@ -52,7 +52,7 @@ function merge_all_factorless_chains!(graphs::AbstractVector{<:AbstractGraph}; v verbose > 0 && println("merge all nodes representing factorless trivial unary chains.") # Post-order DFS for g in graphs - merge_all_factorless_chains!(g.subgraphs) + merge_all_factorless_chains!(subgraphs(g)) merge_factorless_chain!(g) end return graphs @@ -75,7 +75,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 @@ -87,7 +87,7 @@ function merge_all_linear_combinations!(graphs::AbstractVector{<:AbstractGraph}; 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 @@ -103,14 +103,14 @@ function unique_leaves(_graphs::AbstractVector{<:AbstractGraph}) flag = true for (ie, e) in enumerate(uniqueGraph) 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 + mapping[id(g)] = idx idx += 1 end end @@ -126,20 +126,20 @@ function remove_duplicated_leaves!(graphs::AbstractVector{<:AbstractGraph}; verb 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))") 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, uniqueLeaf[leafMap[id(sub_g)]], si) end end end diff --git a/src/computational_graph/transform.jl b/src/computational_graph/transform.jl index a1b3629e..972719ac 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 @@ -119,7 +117,7 @@ function replace_subgraph!(g::AbstractGraph, w::AbstractGraph, m::AbstractGraph) for node in PreOrderDFS(g) for (i, child) in enumerate(children(node)) if isequiv(child, w, :id) - node.subgraphs[i] = m + 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 @@ -149,7 +147,7 @@ function replace_subgraph(g::AbstractGraph, w::AbstractGraph, m::AbstractGraph) for node in PreOrderDFS(g_new) for (i, child) in enumerate(children(node)) if isequiv(child, w, :id) - node.subgraphs[i] = m + set_subgraph!(node, m, i) break end end @@ -162,7 +160,7 @@ end Simplifies `g` in-place if it represents a factorless trivial unary chain. For example, +(+(+g)) ↦ g. - Does nothing unless g has the following structure: 𝓞 --- 𝓞' --- ⋯ --- 𝓞'' ⋯ (!), + Does nothing unless `g` has the following structure: 𝓞 --- 𝓞' --- ⋯ --- 𝓞'' ⋯ (!), where the stop-case (!) represents a leaf, a non-trivial unary operator 𝓞'''(g) != g, a node with non-unity multiplicative prefactor, or a non-unary operation. @@ -170,7 +168,7 @@ end - `g::AbstractGraph`: graph to be modified """ function merge_factorless_chain!(g::AbstractGraph) - while unary_istrivial(g.operator) && onechild(g) && isfactorless(g) + while unary_istrivial(g) && onechild(g) && isfactorless(g) child = eldest(g) for field in fieldnames(typeof(g)) value = getproperty(child, field) @@ -186,7 +184,7 @@ end Returns a simplified copy of `g` if it represents a factorless trivial unary chain. Otherwise, returns the original graph. For example, +(+(+g)) ↦ g. - Does nothing unless g has the following structure: 𝓞 --- 𝓞' --- ⋯ --- 𝓞'' ⋯ (!), + Does nothing unless `g` has the following structure: 𝓞 --- 𝓞' --- ⋯ --- 𝓞'' ⋯ (!), where the stop-case (!) represents a leaf, a non-trivial unary operator 𝓞'''(g) != g, a node with non-unity multiplicative prefactor, or a non-unary operation. @@ -194,7 +192,7 @@ end - `g::AbstractGraph`: graph to be modified """ function merge_factorless_chain(g::AbstractGraph) - while unary_istrivial(g.operator) && onechild(g) && isfactorless(g) + while unary_istrivial(g) && onechild(g) && isfactorless(g) g = eldest(g) end return g @@ -203,29 +201,30 @@ end """ function merge_chain_prefactors!(g::AbstractGraph) - Simplifies subgraphs of g representing trivial unary chains by merging their + Simplifies subgraphs of `g` representing trivial unary chains by merging their subgraph factors toward root level, e.g., 2*(3*(5*g)) + 7*(9*(h)) ↦ 30*(*(*g)) + 63*(*h). - 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 merge_chain_prefactors!(g::AbstractGraph) - for (i, child) in enumerate(g.subgraphs) + for (i, child) in enumerate(subgraphs(g)) total_chain_factor = 1 while onechild(child) # Break case: end of trivial unary chain - unary_istrivial(child.operator) == false && break + unary_istrivial(child) == false && break # Move this subfactor to running total - total_chain_factor *= child.subgraph_factors[1] - child.subgraph_factors[1] = 1 + total_chain_factor *= subgraph_factor(child) + set_subgraph_factor!(child, 1) # Descend one level child = eldest(child) end # Update g subfactor with total factors from children - g.subgraph_factors[i] *= total_chain_factor + new_factor = subgraph_factor(g, i) * total_chain_factor + set_subgraph_factor!(g, new_factor, i) end return g end @@ -233,10 +232,10 @@ end """ function merge_chain_prefactors(g::AbstractGraph) - Returns a copy of g with subgraphs representing trivial unary chains simplified by merging + Returns a copy of `g` with subgraphs representing trivial unary chains simplified by merging their subgraph factors toward root level, e.g., 2*(3*(5*g)) + 7*(9*(h)) ↦ 30*(*(*g)) + 63*(*h). - 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: @@ -247,10 +246,10 @@ merge_chain_prefactors(g::AbstractGraph) = merge_chain_prefactors!(deepcopy(g)) """ function merge_chains!(g::AbstractGraph) - Converts subgraphs of g representing trivial unary chains + Converts subgraphs of `g` representing trivial unary chains to in-place form, e.g., 2*(3*(5*g)) + 7*(9*(h)) ↦ 30*g + 63*h. - 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: @@ -258,7 +257,7 @@ merge_chain_prefactors(g::AbstractGraph) = merge_chain_prefactors!(deepcopy(g)) """ function merge_chains!(g::AbstractGraph) merge_chain_prefactors!(g) # shift chain subgraph factors towards root level - for sub_g in g.subgraphs # prune factorless chain subgraphs + for sub_g in subgraphs(g) # prune factorless chain subgraphs merge_factorless_chain!(sub_g) end return g @@ -267,10 +266,10 @@ end """ function merge_chains(g::AbstractGraph) - Returns a copy of a graph g with subgraphs representing trivial unary chain + Returns a copy of a graph `g` with subgraphs representing trivial unary chain simplified to in-place form, e.g., 2*(3*(5*g)) + 7*(9*(h)) ↦ 30*g + 63*h. - 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: @@ -279,92 +278,52 @@ end merge_chains(g::AbstractGraph) = merge_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)) diff --git a/src/computational_graph/tree_properties.jl b/src/computational_graph/tree_properties.jl index f983313c..d56e8edb 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 diff --git a/test/computational_graph.jl b/test/computational_graph.jl index bfeb5d43..0b3c53b5 100644 --- a/test/computational_graph.jl +++ b/test/computational_graph.jl @@ -9,6 +9,103 @@ 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, 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) + @test isnothing(Graphs.weight(ConcreteGraph())) # weight(g::AbstractGraph) is an abstract method + + ### 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([]) @@ -276,35 +373,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 From b0f74c236573fd4d10e2e5a7b8e575433418a5ed Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Tue, 24 Oct 2023 04:03:53 -0400 Subject: [PATCH 2/9] Bugfix --- src/computational_graph/abstractgraph.jl | 7 +++++-- src/computational_graph/graph.jl | 2 +- src/computational_graph/optimize.jl | 6 +++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/computational_graph/abstractgraph.jl b/src/computational_graph/abstractgraph.jl index 81c5bbcf..fa3210ad 100644 --- a/src/computational_graph/abstractgraph.jl +++ b/src/computational_graph/abstractgraph.jl @@ -260,7 +260,7 @@ end # 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 # graph weights approximately equal + (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) == weight(a) && getproperty(b, :weight) == weight(b) continue # skip graph weights if already accounted for @@ -279,7 +279,10 @@ Base.:(==)(a::AbstractGraph, b::AbstractGraph) = Base.isequal(a, b) """ function isequiv(a::AbstractGraph, b::AbstractGraph, args...) typeof(a) != typeof(b) && return false - (weight(a) ≈ weight(b)) == false && return false # graph weights approximately equal + # 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 diff --git a/src/computational_graph/graph.jl b/src/computational_graph/graph.jl index 2b783da1..8376d2a7 100644 --- a/src/computational_graph/graph.jl +++ b/src/computational_graph/graph.jl @@ -271,7 +271,7 @@ end - `c2`: second scalar multiple (defaults to 1). """ function multi_product(g1::Graph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} - @assert alleq(orders.(graphs)) "Graphs do not all have the same order." + @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] diff --git a/src/computational_graph/optimize.jl b/src/computational_graph/optimize.jl index 37e8c6f6..22dc0686 100644 --- a/src/computational_graph/optimize.jl +++ b/src/computational_graph/optimize.jl @@ -354,19 +354,19 @@ function remove_duplicated_leaves!(graphs::Union{Tuple,AbstractVector{<:Abstract end """ - function burn_from_targetleaves!(graphs::AbstractVector{<:AbstractGraph}, targetleaves_id::AbstractVector{Int}; verbose=0) + function burn_from_targetleaves!(graphs::AbstractVector{G}, targetleaves_id::AbstractVector{Int}; verbose=0) where {G <: AbstractGraph} In-place remove all nodes connected to the target leaves via "Prod" operators. # Arguments: -- `graphs::AbstractVector{<:AbstractGraph}`: A vector of graphs. +- `graphs::AbstractVector{G}`: A vector of graphs. - `targetleaves_id::AbstractVector{Int}`: Vector of target leafs' id. - `verbose`: Level of verbosity (default: 0). # Returns: - The id of a constant graph with a zero factor if any graph in `graphs` was completely burnt; otherwise, `nothing`. """ -function burn_from_targetleaves!(graphs::AbstractVector{<:AbstractGraph}, targetleaves_id::AbstractVector{Int}; verbose=0) +function burn_from_targetleaves!(graphs::AbstractVector{G}, targetleaves_id::AbstractVector{Int}; verbose=0) where {G<:AbstractGraph} verbose > 0 && println("remove all nodes connected to the target leaves via Prod operators.") graphs_sum = linear_combination(graphs, one.(eachindex(graphs))) From 502572ba51fde2a2a83da2ae983d3f29f8b4e947 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Tue, 24 Oct 2023 04:15:12 -0400 Subject: [PATCH 3/9] WIP: Fix factor bug & update tests --- src/computational_graph/feynmangraph.jl | 1 - src/computational_graph/graph.jl | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/computational_graph/feynmangraph.jl b/src/computational_graph/feynmangraph.jl index 74636f2d..326f5b1d 100644 --- a/src/computational_graph/feynmangraph.jl +++ b/src/computational_graph/feynmangraph.jl @@ -360,7 +360,6 @@ function linear_combination(g1::FeynmanGraph{F,W}, g2::FeynmanGraph{F,W}, c1=F(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(), orders=orders(g1), ftype=F, wtype=W) diff --git a/src/computational_graph/graph.jl b/src/computational_graph/graph.jl index 8703b6e3..76f950a6 100644 --- a/src/computational_graph/graph.jl +++ b/src/computational_graph/graph.jl @@ -124,6 +124,7 @@ function Base.:*(g1::Graph{F,W}, c2) where {F,W} # 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 @@ -143,6 +144,7 @@ function Base.:*(c1, g2::Graph{F,W}) where {F,W} # 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 @@ -169,10 +171,12 @@ function linear_combination(g1::Graph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) wh # 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) && onechild(g2) subgraph_factors[2] *= g2.subgraph_factors[1] + # subgraph_factors[2] *= g2.subgraph_factors[1] * g2.factor subgraphs[2] = g2.subgraphs[1] end @@ -211,6 +215,7 @@ function linear_combination(graphs::Vector{Graph{F,W}}, constants::AbstractVecto for (i, sub_g) in enumerate(graphs) 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 @@ -283,10 +288,12 @@ function multi_product(g1::Graph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where { # 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) && onechild(g2) subgraph_factors[2] *= g2.subgraph_factors[1] + # subgraph_factors[2] *= g2.subgraph_factors[1] * g2.factor subgraphs[2] = g2.subgraphs[1] end @@ -324,6 +331,7 @@ function multi_product(graphs::Vector{Graph{F,W}}, constants::AbstractVector=one for (i, sub_g) in enumerate(graphs) 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 From 6db6d5bccd77732aa5899449ecc9cadbd2b63a3d Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Tue, 24 Oct 2023 04:27:03 -0400 Subject: [PATCH 4/9] Update conversions --- src/computational_graph/conversions.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/computational_graph/conversions.jl b/src/computational_graph/conversions.jl index ee61f09b..de50a51f 100644 --- a/src/computational_graph/conversions.jl +++ b/src/computational_graph/conversions.jl @@ -23,9 +23,12 @@ end 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, c2) where {F,W} = linear_combination(Base.promote(g1, g2)..., c1, c2) -linear_combination(g1::FeynmanGraph{F,W}, g2::Graph{F,W}, c1, c2) where {F,W} = linear_combination(Base.promote(g1, g2)..., c1, c2) -linear_combination(graphs::Vector{Union{Graph{F,W},FeynmanGraph{F,W}}}, constants::AbstractVector) where {F,W} = linear_combination(Base.promote(graphs)..., constants) +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) +linear_combination(graphs::Vector{Union{Graph{F,W},FeynmanGraph{F,W}}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} = linear_combination(Base.promote(graphs)..., constants) +multi_product(g1::Graph{F,W}, g2::FeynmanGraph{F,W}, c1=F(1), c2=F(1)) where {F,W} = multi_product(Base.promote(g1, g2)..., c1, c2) +multi_product(g1::FeynmanGraph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} = multi_product(Base.promote(g1, g2)..., c1, c2) +multi_product(graphs::Vector{Union{Graph{F,W},FeynmanGraph{F,W}}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} = multi_product(Base.promote(graphs)..., constants) Base.:*(g1::Graph, g2::FeynmanGraph) = error("Multiplication of Feynman graphs is not well defined!") Base.:*(g1::FeynmanGraph, g2::Graph) = error("Multiplication of Feynman graphs is not well defined!") Base.:+(g1::Graph{F,W}, g2::FeynmanGraph{F,W}) where {F,W} = Base.:+(Base.promote(g1, g2)...) From 465c1ab1e6fe46eb5b7bc72c76fb1e332a007194 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Tue, 24 Oct 2023 06:43:09 -0400 Subject: [PATCH 5/9] Bugfix for conversions --- src/FeynmanDiagram.jl | 2 +- src/computational_graph/ComputationalGraph.jl | 2 +- src/computational_graph/conversions.jl | 12 +-- src/computational_graph/feynmangraph.jl | 4 +- test/computational_graph.jl | 77 +++++++++++++++++-- 5 files changed, 79 insertions(+), 18 deletions(-) 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/computational_graph/ComputationalGraph.jl b/src/computational_graph/ComputationalGraph.jl index d5fa98b8..9a1af0b3 100644 --- a/src/computational_graph/ComputationalGraph.jl +++ b/src/computational_graph/ComputationalGraph.jl @@ -29,7 +29,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 Prod, Sum # export DiagramType, Interaction, ExternalVertex, Propagator, SelfEnergy, VertexDiag, GreenDiag, GenericDiag diff --git a/src/computational_graph/conversions.jl b/src/computational_graph/conversions.jl index de50a51f..d45c68a0 100644 --- a/src/computational_graph/conversions.jl +++ b/src/computational_graph/conversions.jl @@ -1,6 +1,6 @@ """ - function Base.convert(Graph, g::FeynmanGraph) + 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). @@ -8,11 +8,11 @@ # Arguments: - `g` computational graph """ -function Base.convert(::Type{Graph{F,W}}, g::FeynmanGraph{F,W}) where {F,W} - return Graph{F,W}(g.subgraphs; subgraph_factors=g.subgraph_factors, name=g.name, operator=g.operator, orders=g.orders, factor=g.factor, weight=g.weight) +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{F,W}}, g::Graph{F,W}) where {F,W} +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." @@ -33,5 +33,5 @@ Base.:*(g1::Graph, g2::FeynmanGraph) = error("Multiplication of Feynman graphs i Base.:*(g1::FeynmanGraph, g2::Graph) = error("Multiplication of Feynman graphs is not well defined!") 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)...) +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)...) diff --git a/src/computational_graph/feynmangraph.jl b/src/computational_graph/feynmangraph.jl index 326f5b1d..7f955990 100644 --- a/src/computational_graph/feynmangraph.jl +++ b/src/computational_graph/feynmangraph.jl @@ -162,10 +162,10 @@ mutable struct FeynmanGraph{F,W} <: AbstractGraph # FeynmanGraph - `g` computational graph - `properties::FeynmanProperties` diagrammatic properties, e.g., the operator vertices and topology """ - function FeynmanGraph(g::Graph, properties::FeynmanProperties) + 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{ftype,wtype}(uid(), g.name, g.orders, properties, g.subgraphs, g.subgraph_factors, typeof(g.operator), g.factor, g.weight) + return new{F,W}(uid(), g.name, g.orders, properties, g.subgraphs, g.subgraph_factors, g.operator, g.factor, g.weight) end end diff --git a/test/computational_graph.jl b/test/computational_graph.jl index 82595cc1..09622555 100644 --- a/test/computational_graph.jl +++ b/test/computational_graph.jl @@ -19,18 +19,18 @@ Graphs.unary_istrivial(::Type{O}) where {O<:Union{O1,O2,O3}} = true 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, operator, subgraphs, subgraph_factors, factor, weight) + 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) + 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())) @@ -853,6 +853,67 @@ end end end +@testset verbose = true "Conversions" begin + # Test constructor for FeynmanGraph from Graph and FeynmanProperties + 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 + g_feyn_conv = FeynmanGraph(g, g_feyn.properties) + @test isequiv(g_feyn, g_feyn_conv, :id) + + # Test automatic FeynmanGraph -> Graph conversion + g_conv::Graph = g_feyn + @test isequiv(g, g_conv, :id) + + # Test automatic FeynmanGraph -> Graph promotion in arithmetic operations + conversion_successful = true + local l1, l2, l3, l4, l5, l6, l7, r7, l8, r8 + try + l1 = g_feyn + g1 + l2 = g_feyn - g1 + l3 = linear_combination(g_feyn, g1, 2, 3) + l4 = linear_combination(g_feyn, g, 2, 3) + l5 = linear_combination([g_feyn, g, g1, g2], [2, 5, 3, 9]) + l6 = multi_product(g_feyn, g1, 2, 3) + l7 = multi_product(g_feyn, g, 2, 3) + l8 = multi_product([g_feyn, g, g1, g2], [2, 5, 3, 9]) + r7 = multi_product(g, g, 2, 3) + r8 = multi_product([g, g, g1, g2], [2, 5, 3, 9]) + catch + conversion_successful = false + end + @test conversion_successful + Graphs.optimize!([l1, l2, l3, l4, l5, l6, l7, r7, l8, r8]) # cache unique leaves + + @test isequiv(l1, g + g1, :id) + @test isequiv(l2, g - g1, :id) + @test isequiv(l3, 2 * g + 3 * g1, :id) + @test isequiv(l4, linear_combination([g], [5]), :id) + @test isequiv(l5, linear_combination([g, g1, g2], [7, 3, 9]), :id) + @test isequiv(l6, multi_product(g, g1, 2, 3), :id) + + # TODO: Refine multiple Prod -> Power conversion + @test_broken isequiv(l7, r7, :id) + @test_broken isequiv(l8, r8, :id) + + # FeynmanGraph multiplication is undefined + err1 = AssertionError() + err2 = AssertionError() + try + g * g_feyn + catch err1 + end + try + g_feyn * g + catch err2 + end + @test err1 isa ErrorException + @test err2 isa ErrorException + @test err1.msg == "Multiplication of Feynman graphs is not well defined!" + @test err2.msg == "Multiplication of Feynman graphs is not well defined!" +end + @testset verbose = true "Evaluation" begin using FeynmanDiagram.ComputationalGraphs: eval! From 91e447f2003cc3ba0ef9cd0379431ad8fee82a85 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Wed, 25 Oct 2023 14:20:00 -0400 Subject: [PATCH 6/9] Set factor type to a number --- src/computational_graph/feynmangraph.jl | 6 +++--- src/computational_graph/graph.jl | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/computational_graph/feynmangraph.jl b/src/computational_graph/feynmangraph.jl index 7f955990..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. @@ -56,7 +56,7 @@ drop_topology(p::FeynmanProperties) = FeynmanProperties(p.diagtype, p.vertices, - `subgraphs::Vector{FeynmanGraph{F,W}}` vector of sub-diagrams - `subgraph_factors::Vector{F}` scalar multiplicative factors associated with each subdiagram - `operator::DataType` node operation (Sum, Prod, etc.) -- `factor::F` total scalar multiplicative factor for the diagram +- `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} diff --git a/src/computational_graph/graph.jl b/src/computational_graph/graph.jl index 76f950a6..59089f19 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} From c731c69f0247592bd207a853645628b901271201 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Thu, 26 Oct 2023 01:41:33 -0400 Subject: [PATCH 7/9] Bugfix --- src/computational_graph/abstractgraph.jl | 2 ++ src/computational_graph/conversions.jl | 14 +++++------ test/computational_graph.jl | 32 ++++++++++++------------ 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/computational_graph/abstractgraph.jl b/src/computational_graph/abstractgraph.jl index fa3210ad..66651d27 100644 --- a/src/computational_graph/abstractgraph.jl +++ b/src/computational_graph/abstractgraph.jl @@ -305,6 +305,8 @@ end 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)) diff --git a/src/computational_graph/conversions.jl b/src/computational_graph/conversions.jl index d45c68a0..13feae30 100644 --- a/src/computational_graph/conversions.jl +++ b/src/computational_graph/conversions.jl @@ -19,19 +19,19 @@ function Base.convert(::Type{FeynmanGraph}, g::Graph{F,W}) where {F,W} ) end -# Automatically promote FeynmanGraph to Graph for arithmetic operations +# 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) -linear_combination(graphs::Vector{Union{Graph{F,W},FeynmanGraph{F,W}}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} = linear_combination(Base.promote(graphs)..., constants) -multi_product(g1::Graph{F,W}, g2::FeynmanGraph{F,W}, c1=F(1), c2=F(1)) where {F,W} = multi_product(Base.promote(g1, g2)..., c1, c2) -multi_product(g1::FeynmanGraph{F,W}, g2::Graph{F,W}, c1=F(1), c2=F(1)) where {F,W} = multi_product(Base.promote(g1, g2)..., c1, c2) -multi_product(graphs::Vector{Union{Graph{F,W},FeynmanGraph{F,W}}}, constants::AbstractVector=ones(F, length(graphs))) where {F,W} = multi_product(Base.promote(graphs)..., constants) -Base.:*(g1::Graph, g2::FeynmanGraph) = error("Multiplication of Feynman graphs is not well defined!") -Base.:*(g1::FeynmanGraph, g2::Graph) = error("Multiplication of Feynman graphs is not well defined!") 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/test/computational_graph.jl b/test/computational_graph.jl index 09622555..0bc50a70 100644 --- a/test/computational_graph.jl +++ b/test/computational_graph.jl @@ -868,38 +868,30 @@ end # Test automatic FeynmanGraph -> Graph promotion in arithmetic operations conversion_successful = true - local l1, l2, l3, l4, l5, l6, l7, r7, l8, r8 + local l1, l2, l3, l4, l5 try l1 = g_feyn + g1 l2 = g_feyn - g1 l3 = linear_combination(g_feyn, g1, 2, 3) l4 = linear_combination(g_feyn, g, 2, 3) l5 = linear_combination([g_feyn, g, g1, g2], [2, 5, 3, 9]) - l6 = multi_product(g_feyn, g1, 2, 3) - l7 = multi_product(g_feyn, g, 2, 3) - l8 = multi_product([g_feyn, g, g1, g2], [2, 5, 3, 9]) - r7 = multi_product(g, g, 2, 3) - r8 = multi_product([g, g, g1, g2], [2, 5, 3, 9]) catch conversion_successful = false end @test conversion_successful - Graphs.optimize!([l1, l2, l3, l4, l5, l6, l7, r7, l8, r8]) # cache unique leaves + Graphs.optimize!([l1, l2, l3, l4, l5]) # cache unique leaves @test isequiv(l1, g + g1, :id) @test isequiv(l2, g - g1, :id) @test isequiv(l3, 2 * g + 3 * g1, :id) @test isequiv(l4, linear_combination([g], [5]), :id) @test isequiv(l5, linear_combination([g, g1, g2], [7, 3, 9]), :id) - @test isequiv(l6, multi_product(g, g1, 2, 3), :id) - - # TODO: Refine multiple Prod -> Power conversion - @test_broken isequiv(l7, r7, :id) - @test_broken isequiv(l8, r8, :id) # FeynmanGraph multiplication is undefined err1 = AssertionError() err2 = AssertionError() + err3 = AssertionError() + err4 = AssertionError() try g * g_feyn catch err1 @@ -908,10 +900,18 @@ end g_feyn * g catch err2 end - @test err1 isa ErrorException - @test err2 isa ErrorException - @test err1.msg == "Multiplication of Feynman graphs is not well defined!" - @test err2.msg == "Multiplication of Feynman graphs is not well defined!" + try + multi_product(g_feyn, g1, 2, 3) + catch err3 + end + try + multi_product(g, g_feyn, 2, 3) + catch err4 + end + errs = [err1, err2, err3, err4] + errmsg = "Multiplication of Feynman graphs is not well defined!" + @test all(err isa ErrorException for err in errs) + @test all(err.msg == errmsg for err in errs) end @testset verbose = true "Evaluation" begin From 11207c71fd1219b8f7660c1d4922130d3686c6b1 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Thu, 26 Oct 2023 14:15:23 -0400 Subject: [PATCH 8/9] Remove auto-conversions --- src/computational_graph/conversions.jl | 28 ++++++------ test/computational_graph.jl | 62 +++++--------------------- 2 files changed, 24 insertions(+), 66 deletions(-) diff --git a/src/computational_graph/conversions.jl b/src/computational_graph/conversions.jl index 13feae30..d1133291 100644 --- a/src/computational_graph/conversions.jl +++ b/src/computational_graph/conversions.jl @@ -19,19 +19,19 @@ function Base.convert(::Type{FeynmanGraph}, g::Graph{F,W}) where {F,W} ) 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} +# # 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)...) +# # 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!") +# # 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/test/computational_graph.jl b/test/computational_graph.jl index 0bc50a70..5f901d3f 100644 --- a/test/computational_graph.jl +++ b/test/computational_graph.jl @@ -854,64 +854,22 @@ end end @testset verbose = true "Conversions" begin - # Test constructor for FeynmanGraph from Graph and FeynmanProperties 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 automatic FeynmanGraph -> Graph conversion - g_conv::Graph = g_feyn - @test isequiv(g, g_conv, :id) - - # Test automatic FeynmanGraph -> Graph promotion in arithmetic operations - conversion_successful = true - local l1, l2, l3, l4, l5 - try - l1 = g_feyn + g1 - l2 = g_feyn - g1 - l3 = linear_combination(g_feyn, g1, 2, 3) - l4 = linear_combination(g_feyn, g, 2, 3) - l5 = linear_combination([g_feyn, g, g1, g2], [2, 5, 3, 9]) - catch - conversion_successful = false - end - @test conversion_successful - - Graphs.optimize!([l1, l2, l3, l4, l5]) # cache unique leaves - @test isequiv(l1, g + g1, :id) - @test isequiv(l2, g - g1, :id) - @test isequiv(l3, 2 * g + 3 * g1, :id) - @test isequiv(l4, linear_combination([g], [5]), :id) - @test isequiv(l5, linear_combination([g, g1, g2], [7, 3, 9]), :id) - - # FeynmanGraph multiplication is undefined - err1 = AssertionError() - err2 = AssertionError() - err3 = AssertionError() - err4 = AssertionError() - try - g * g_feyn - catch err1 - end - try - g_feyn * g - catch err2 - end - try - multi_product(g_feyn, g1, 2, 3) - catch err3 - end - try - multi_product(g, g_feyn, 2, 3) - catch err4 - end - errs = [err1, err2, err3, err4] - errmsg = "Multiplication of Feynman graphs is not well defined!" - @test all(err isa ErrorException for err in errs) - @test all(err.msg == errmsg for err in errs) + # 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 From 2b18b45899a4df79f03275ba59337c96ede8a410 Mon Sep 17 00:00:00 2001 From: dcerkoney Date: Thu, 26 Oct 2023 14:41:06 -0400 Subject: [PATCH 9/9] Fix typos --- test/computational_graph.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/computational_graph.jl b/test/computational_graph.jl index cb0371c5..c165f24c 100644 --- a/test/computational_graph.jl +++ b/test/computational_graph.jl @@ -263,7 +263,7 @@ end @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) @@ -330,7 +330,7 @@ end 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) @@ -348,7 +348,7 @@ end 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)