From 38558c326899ce86a80b2841374e96bbcd70d1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 24 Sep 2024 00:00:13 -0400 Subject: [PATCH 01/18] Init visualization in VSCode with ECharts --- src/Tenet.jl | 2 ++ src/Visualization.jl | 68 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/Visualization.jl diff --git a/src/Tenet.jl b/src/Tenet.jl index 820be50ad..8acdbab92 100644 --- a/src/Tenet.jl +++ b/src/Tenet.jl @@ -41,6 +41,8 @@ export MPS, MPO export evolve!, expect, overlap +include("Visualization.jl") + # reexports from EinExprs export einexpr, inds diff --git a/src/Visualization.jl b/src/Visualization.jl new file mode 100644 index 000000000..88c324a9d --- /dev/null +++ b/src/Visualization.jl @@ -0,0 +1,68 @@ +using Pkg.Artifacts + +# const echarts = read(joinpath(artifact"echarts", "node_modules", "echarts", "dist", "echarts.js"), String) +const echarts_url = "https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js" + +Base.show(io::IO, ::MIME"text/html", tn::AbstractTensorNetwork) = show(io, MIME"juliavscode/html", tn) +function Base.show(io::IO, ::MIME"juliavscode/html", @nospecialize(tn::AbstractTensorNetwork)) + tn = transform(tn, Tenet.HyperFlatten) + appid = gensym("tenet-graph") + tensormap = IdDict([tensor => i for (i, tensor) in enumerate(tensors(tn))]) + + nodes = collect(values(tensormap)) + + return print( + io, + """ + +
+ + """, + ) +end From fc0597331b451ab3c6526fa787ebe3741959fd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= <15837247+mofeing@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:22:56 -0400 Subject: [PATCH 02/18] Remove Pkg.Artifacts for now --- src/Visualization.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Visualization.jl b/src/Visualization.jl index 88c324a9d..691aba878 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -1,5 +1,3 @@ -using Pkg.Artifacts - # const echarts = read(joinpath(artifact"echarts", "node_modules", "echarts", "dist", "echarts.js"), String) const echarts_url = "https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js" From 247b6fde677b7ee1b6587ac02130ed18775a318e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Tue, 24 Sep 2024 23:12:56 -0400 Subject: [PATCH 03/18] Locally provide `echarts.js` library Works if offline --- Project.toml | 2 ++ src/Visualization.jl | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index 4ad992171..5b6dcbed5 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" BijectiveDicts = "d089a002-103b-496c-a4e3-58f90cf955b0" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DeltaArrays = "10b0fc19-5ccc-4427-889b-d75dd6306188" +ECharts_jll = "ffd69456-1935-58d2-abba-ba12e8909167" EinExprs = "b1794770-133b-4de1-afb4-526377e9f4c5" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" KeywordDispatch = "5888135b-5456-5c80-a1b6-c91ef8180460" @@ -64,6 +65,7 @@ ChainRulesTestUtils = "1" Combinatorics = "1.0" Dagger = "0.18" DeltaArrays = "0.1.1" +ECharts_jll = "5" EinExprs = "0.5, 0.6" FiniteDifferences = "0.12" GraphMakie = "0.4,0.5" diff --git a/src/Visualization.jl b/src/Visualization.jl index 691aba878..bdd59c6a7 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -1,7 +1,5 @@ -# const echarts = read(joinpath(artifact"echarts", "node_modules", "echarts", "dist", "echarts.js"), String) -const echarts_url = "https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js" +using ECharts_jll -Base.show(io::IO, ::MIME"text/html", tn::AbstractTensorNetwork) = show(io, MIME"juliavscode/html", tn) function Base.show(io::IO, ::MIME"juliavscode/html", @nospecialize(tn::AbstractTensorNetwork)) tn = transform(tn, Tenet.HyperFlatten) appid = gensym("tenet-graph") @@ -12,7 +10,9 @@ function Base.show(io::IO, ::MIME"juliavscode/html", @nospecialize(tn::AbstractT return print( io, """ - +
-
- - """, + """{ + "\$schema": "https://vega.github.io/schema/vega-lite/v3.json", + "description": "A simple bar chart with embedded data.", + "data": { + "values": [ + {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, + {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, + {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52} + ] + }, + "mark": "bar", + "encoding": { + "x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}}, + "y": {"field": "b", "type": "quantitative"} + } + }""", + # """{ + # "\$schema": "https://vega.github.io/schema/vega/v5.json", + # "width": 600, + # "height": 600, + # "signals": [ + # {"name": "center.x", "update": "width / 2"}, + # {"name": "center.y", "update": "height / 2"}, + # ], + # "data": [ + # { + # "name": "vertex-data", + # "format": {"type": "json" }, + # "values": [$( + # join(map(vertices(graph)) do v + # return "{ \"name\": \"$v\", \"group\": $( + # if v ∈ ghostnodes + # 0 # "\"ghost\"" + # elseif v ∈ hypernodes + # 1 # "\"hyper\"" + # else + # 2 # "\"tensor\"" + # end) }" + # end, + # ","))], + # }, + # { + # "name": "edge-data", + # "format": {"type": "json" }, + # "values": [$( + # join(map(inds(tn)) do i + # nodes = tensors(tn; intersects=i) + # if length(nodes) == 1 + # # TODO + # v = tensormap[only(nodes)] + # return "{ source: \"$v\", target: \"$v\", name: \"$i\" }" + # elseif length(nodes) == 2 + # a, b = nodes + # index = get(hypermap, i, i) + # return "{ source: \"$(tensormap[a])\", target: \"$(tensormap[b])\", name: \"$index\" }" + # end + # end, + # ','))], + # } + # ], + # "marks": [ + # { + # "name": "vertices", + # "type": "symbol", + # "from": {"data": "vertex-data"}, + # "encode": { + # }, + # "transform": [ + # { + # "type": "force", + # "static": false, + # "iterations": 100, + # "forces": [ + # {"force": "center", "x": {"signal": "center.x"}, "y": {"signal": "center.y"}}, + # {"force": "collide", "radius": 10}, + # {"force": "nbody", "strength": -100}, + # {"force": "link", "links": "edge-data", "distance": 100}, + # ] + # } + # ] + # }, + # { + # "type": "path", + # "from": {"data": "edge-data"}, + # "encode": { + # "update": { + # "stroke": {"value": "#ccc"}, + # "strokeWidth": {"value": 2}, + # "tooltip": {"field": "name"} + # } + # }, + # "transform": [ + # { + # "type": "linkpath", + # "require": {"signal": "force"}, + # "shape": "line", + # "sourceX": "datum.source.x", + # "sourceY": "datum.source.y", + # "targetX": "datum.target.x", + # "targetY": "datum.target.y" + # } + # ] + # } + # ] + # }""", ) end + +# function draw(io::IO, @nospecialize(tn::AbstractTensorNetwork)) +# tn, graph, tensormap, hypermap, hypernodes, ghostnodes = graph_representation(tn) +# hypermap = Dict(Iterators.flatten([[i => v for i in k] for (k, v) in hypermap])) + +# appid = gensym("tenet-graph") +# return print( +# io, +# """ +# +#
+# +# """, +# ) +# end From a2f354474711ddafaac4dfe12228f91f357aadc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sat, 23 Nov 2024 14:15:21 +0100 Subject: [PATCH 08/18] Replace ECharts_jll for Vega Lite vizualization --- Project.toml | 2 - src/Visualization.jl | 289 ++++++++++++++----------------------------- 2 files changed, 95 insertions(+), 196 deletions(-) diff --git a/Project.toml b/Project.toml index 5b6dcbed5..4ad992171 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" BijectiveDicts = "d089a002-103b-496c-a4e3-58f90cf955b0" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DeltaArrays = "10b0fc19-5ccc-4427-889b-d75dd6306188" -ECharts_jll = "ffd69456-1935-58d2-abba-ba12e8909167" EinExprs = "b1794770-133b-4de1-afb4-526377e9f4c5" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" KeywordDispatch = "5888135b-5456-5c80-a1b6-c91ef8180460" @@ -65,7 +64,6 @@ ChainRulesTestUtils = "1" Combinatorics = "1.0" Dagger = "0.18" DeltaArrays = "0.1.1" -ECharts_jll = "5" EinExprs = "0.5, 0.6" FiniteDifferences = "0.12" GraphMakie = "0.4,0.5" diff --git a/src/Visualization.jl b/src/Visualization.jl index bc34a9083..d82d5a17f 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -1,4 +1,3 @@ -using ECharts_jll using Graphs: Graphs, vertices, edges function graph_representation(tn::AbstractTensorNetwork) @@ -39,201 +38,103 @@ for v in ["application/vnd.vegalite.v3+json", "application/vnd.vegalite.v4+json" @eval Base.Multimedia.istextmime(::MIME{$(Symbol(v))}) = true end -Base.show(io::IO, ::MIME"application/vnd.vegalite.v3+json", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) +Base.show(io::IO, ::MIME"application/vnd.vegalite.v5+json", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) function draw(io::IO, @nospecialize(tn::AbstractTensorNetwork)) tn, graph, tensormap, hypermap, hypernodes, ghostnodes = graph_representation(tn) hypermap = Dict(Iterators.flatten([[i => v for i in k] for (k, v) in hypermap])) - return print( - io, - """{ - "\$schema": "https://vega.github.io/schema/vega-lite/v3.json", - "description": "A simple bar chart with embedded data.", - "data": { - "values": [ - {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, - {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, - {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52} - ] - }, - "mark": "bar", - "encoding": { - "x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}}, - "y": {"field": "b", "type": "quantitative"} - } - }""", - # """{ - # "\$schema": "https://vega.github.io/schema/vega/v5.json", - # "width": 600, - # "height": 600, - # "signals": [ - # {"name": "center.x", "update": "width / 2"}, - # {"name": "center.y", "update": "height / 2"}, - # ], - # "data": [ - # { - # "name": "vertex-data", - # "format": {"type": "json" }, - # "values": [$( - # join(map(vertices(graph)) do v - # return "{ \"name\": \"$v\", \"group\": $( - # if v ∈ ghostnodes - # 0 # "\"ghost\"" - # elseif v ∈ hypernodes - # 1 # "\"hyper\"" - # else - # 2 # "\"tensor\"" - # end) }" - # end, - # ","))], - # }, - # { - # "name": "edge-data", - # "format": {"type": "json" }, - # "values": [$( - # join(map(inds(tn)) do i - # nodes = tensors(tn; intersects=i) - # if length(nodes) == 1 - # # TODO - # v = tensormap[only(nodes)] - # return "{ source: \"$v\", target: \"$v\", name: \"$i\" }" - # elseif length(nodes) == 2 - # a, b = nodes - # index = get(hypermap, i, i) - # return "{ source: \"$(tensormap[a])\", target: \"$(tensormap[b])\", name: \"$index\" }" - # end - # end, - # ','))], - # } - # ], - # "marks": [ - # { - # "name": "vertices", - # "type": "symbol", - # "from": {"data": "vertex-data"}, - # "encode": { - # }, - # "transform": [ - # { - # "type": "force", - # "static": false, - # "iterations": 100, - # "forces": [ - # {"force": "center", "x": {"signal": "center.x"}, "y": {"signal": "center.y"}}, - # {"force": "collide", "radius": 10}, - # {"force": "nbody", "strength": -100}, - # {"force": "link", "links": "edge-data", "distance": 100}, - # ] - # } - # ] - # }, - # { - # "type": "path", - # "from": {"data": "edge-data"}, - # "encode": { - # "update": { - # "stroke": {"value": "#ccc"}, - # "strokeWidth": {"value": 2}, - # "tooltip": {"field": "name"} - # } - # }, - # "transform": [ - # { - # "type": "linkpath", - # "require": {"signal": "force"}, - # "shape": "line", - # "sourceX": "datum.source.x", - # "sourceY": "datum.source.y", - # "targetX": "datum.target.x", - # "targetY": "datum.target.y" - # } - # ] - # } - # ] - # }""", - ) -end - -# function draw(io::IO, @nospecialize(tn::AbstractTensorNetwork)) -# tn, graph, tensormap, hypermap, hypernodes, ghostnodes = graph_representation(tn) -# hypermap = Dict(Iterators.flatten([[i => v for i in k] for (k, v) in hypermap])) -# appid = gensym("tenet-graph") -# return print( -# io, -# """ -# -#
-# -# """, -# ) -# end + return print(io, json) +end From 9fef47588ce7667836e6c5bd587314b960f60d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sat, 23 Nov 2024 15:29:10 +0100 Subject: [PATCH 09/18] fix edges to ghost nodes --- src/Visualization.jl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Visualization.jl b/src/Visualization.jl index d82d5a17f..8613a88cb 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -76,14 +76,19 @@ function draw(io::IO, @nospecialize(tn::AbstractTensorNetwork)) join(map(inds(tn)) do i # NOTE Vega uses 0-indexing nodes = tensors(tn; intersects=i) - if length(nodes) == 1 - # TODO - v = tensormap[only(nodes)] - 1 - return "{ \"source\": $v, \"target\": $v, \"value\": \"$i\" }" - elseif length(nodes) == 2 + + # regular nodes + if length(nodes) == 2 a, b = nodes index = get(hypermap, i, i) return "{ \"source\": $(tensormap[a] - 1), \"target\": $(tensormap[b] - 1), \"value\": \"$index\" }" + + # nodes with open indices + elseif length(nodes) == 1 + vertex = tensormap[only(nodes)] - 1 + # TODO is this correct? what if more than 1 open index (e.g. MPO) + ghost = ntensors(tn) + vertex + return "{ \"source\": $vertex, \"target\": $ghost, \"value\": \"$i\" }" end end, ",\n\t\t") From a38bb2ec9513ba6af32c085454225ee9ce83ef08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sat, 23 Nov 2024 15:31:31 +0100 Subject: [PATCH 10/18] fixes for Vega MIME --- src/Visualization.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Visualization.jl b/src/Visualization.jl index 8613a88cb..5bccbdc4a 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -30,15 +30,13 @@ function graph_representation(tn::AbstractTensorNetwork) return tn, graph, tensormap, hypermap, hypernodes, ghostnodes end -# TODO use `Base.ENV["VSCODE_PID"]` to detect if running in vscode notebook -# Base.show(io::IO, ::MIME"text/html", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) -# Base.show(io::IO, ::MIME"juliavscode/html", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) - -for v in ["application/vnd.vegalite.v3+json", "application/vnd.vegalite.v4+json", "application/vnd.vegalite.v5+json"] - @eval Base.Multimedia.istextmime(::MIME{$(Symbol(v))}) = true +# TODO use `Base.ENV["VSCODE_PID"]` to detect if running in vscode notebook? +# TODO move this to VSCodeServer.jl +function Base.Multimedia.istextmime(::MIME"application/vnd.vega.v5+json") + return true end -Base.show(io::IO, ::MIME"application/vnd.vegalite.v5+json", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) +Base.show(io::IO, ::MIME"application/vnd.vega.v5+json", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) function draw(io::IO, @nospecialize(tn::AbstractTensorNetwork)) tn, graph, tensormap, hypermap, hypernodes, ghostnodes = graph_representation(tn) hypermap = Dict(Iterators.flatten([[i => v for i in k] for (k, v) in hypermap])) From a7b18ac9e38d9e732b4e7af971b58b1f5e912649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sat, 23 Nov 2024 15:31:52 +0100 Subject: [PATCH 11/18] fix --- src/Visualization.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Visualization.jl b/src/Visualization.jl index 5bccbdc4a..857048954 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -1,6 +1,7 @@ using Graphs: Graphs, vertices, edges function graph_representation(tn::AbstractTensorNetwork) + tn = TensorNetwork(tn) hypermap = Tenet.hyperflatten(tn) if !isempty(hypermap) tn = transform(tn, Tenet.HyperFlatten) From 31dfa8b3d4fc704ea59c87958ba5ca84871f6b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 24 Nov 2024 14:07:38 +0100 Subject: [PATCH 12/18] fix GraphMakie extension call to `graph_representation` --- ext/TenetGraphMakieExt.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/TenetGraphMakieExt.jl b/ext/TenetGraphMakieExt.jl index 763a1bdf1..879e75307 100644 --- a/ext/TenetGraphMakieExt.jl +++ b/ext/TenetGraphMakieExt.jl @@ -46,7 +46,7 @@ function GraphMakie.graphplot!(f::Union{Figure,GridPosition}, tn::Tenet.Abstract end function GraphMakie.graphplot!(ax::Union{Axis,Axis3}, tn::Tenet.AbstractTensorNetwork; labels=false, kwargs...) - tn, graph, _, hypermap, copytensors, ghostnodes = graph_representation(tn) + tn, graph, _, hypermap, copytensors, ghostnodes = Tenet.graph_representation(tn) # configure graphics # TODO refactor hardcoded values into constants From 887aa8fab3036df70faf324918dfada488e92187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 24 Nov 2024 14:12:25 +0100 Subject: [PATCH 13/18] refactor vega+json spec generation --- Project.toml | 2 + src/Visualization.jl | 236 +++++++++++++++++++++++++------------------ 2 files changed, 139 insertions(+), 99 deletions(-) diff --git a/Project.toml b/Project.toml index 4ad992171..897bb12c8 100644 --- a/Project.toml +++ b/Project.toml @@ -13,6 +13,7 @@ Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" KeywordDispatch = "5888135b-5456-5c80-a1b6-c91ef8180460" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" OMEinsum = "ebe7aa44-baf0-506c-a96f-8464559b3922" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" ScopedValues = "7e506255-f358-4e82-b7e4-beb19740aa63" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -76,6 +77,7 @@ KrylovKit = "0.7, 0.8" LinearAlgebra = "1.10" Makie = "0.18,0.19,0.20, 0.21" OMEinsum = "0.7, 0.8" +OrderedCollections = "1.6.3" PythonCall = "0.9" Quac = "0.3" Random = "1.10" diff --git a/src/Visualization.jl b/src/Visualization.jl index 857048954..afd11aac1 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -1,4 +1,5 @@ using Graphs: Graphs, vertices, edges +using OrderedCollections: OrderedDict function graph_representation(tn::AbstractTensorNetwork) tn = TensorNetwork(tn) @@ -38,107 +39,144 @@ function Base.Multimedia.istextmime(::MIME"application/vnd.vega.v5+json") end Base.show(io::IO, ::MIME"application/vnd.vega.v5+json", @nospecialize(tn::AbstractTensorNetwork)) = draw(io, tn) -function draw(io::IO, @nospecialize(tn::AbstractTensorNetwork)) + +@inline draw(io::IO, args...) = draw(IOContext(io), args...) + +function draw(io::IOContext, @nospecialize(tn::AbstractTensorNetwork)) + width = get(io, :width, 800) + height = get(io, :height, 600) + tn, graph, tensormap, hypermap, hypernodes, ghostnodes = graph_representation(tn) hypermap = Dict(Iterators.flatten([[i => v for i in k] for (k, v) in hypermap])) - json = """{ - "\$schema": "https://vega.github.io/schema/vega/v5.json", - "width": 600, - "height": 600, - "signals": [ - {"name": "center.x", "update": "width / 2"}, - {"name": "center.y", "update": "height / 2"}, - ], - "data": [ - { - "name": "vertex-data", - "format": {"type": "json" }, - "values": [\n\t\t$( - join(map(vertices(graph)) do v - return "{ \"name\": \"$v\", \"group\": $( - if v ∈ ghostnodes - 0 # "\"ghost\"" - elseif v ∈ hypernodes - 1 # "\"hyper\"" - else - 2 # "\"tensor\"" - end) }" - end, - ",\n\t\t")) - ] - }, - { - "name": "edge-data", - "format": {"type": "json" }, - "values": [\n\t\t$( - join(map(inds(tn)) do i - # NOTE Vega uses 0-indexing - nodes = tensors(tn; intersects=i) - - # regular nodes - if length(nodes) == 2 - a, b = nodes - index = get(hypermap, i, i) - return "{ \"source\": $(tensormap[a] - 1), \"target\": $(tensormap[b] - 1), \"value\": \"$index\" }" - - # nodes with open indices - elseif length(nodes) == 1 - vertex = tensormap[only(nodes)] - 1 - # TODO is this correct? what if more than 1 open index (e.g. MPO) - ghost = ntensors(tn) + vertex - return "{ \"source\": $vertex, \"target\": $ghost, \"value\": \"$i\" }" - end - end, - ",\n\t\t") - ) - ] - } + spec = OrderedDict{Symbol,Any}() + spec[Symbol("\$schema")] = "https://vega.github.io/schema/vega/v5.json" + spec[:width] = width + spec[:height] = height + spec[:signals] = [ + OrderedDict(:name => "center.x", :update => "width / 2", :__collapse => true), + OrderedDict(:name => "center.y", :update => "height / 2", :__collapse => true), + ] + vdata = OrderedDict{Symbol,Any}() + edata = OrderedDict{Symbol,Any}() + spec[:data] = [vdata, edata] + vmarks = OrderedDict{Symbol,Any}() + emarks = OrderedDict{Symbol,Any}() + spec[:marks] = [vmarks, emarks] + + # vertex data + vdata[:name] = "vertex-data" + vdata[:format] = OrderedDict(:type => "json", :__collapse => true) + vdata[:values] = map(vertices(graph)) do v + group = if v ∈ ghostnodes + 0 + elseif v ∈ hypernodes + 1 + else + 2 + end + OrderedDict(:name => v, :group => group, :__collapse => true) + end + + # edge data + edata[:name] = "edge-data" + edata[:format] = OrderedDict(:type => "json", :__collapse => true) + edata[:values] = map(inds(tn)) do i + nodes = tensors(tn; intersects=i) + + if length(nodes) == 2 + # regular nodes + a, b = nodes + index = get(hypermap, i, i) + OrderedDict( + :source => tensormap[a] - 1, + :target => tensormap[b] - 1, + :value => string(index), + :__collapse => true, + ) + + elseif length(nodes) == 1 + # nodes with open indices + vertex = tensormap[only(nodes)] - 1 + ghost = ntensors(tn) + vertex + OrderedDict(:source => vertex, :target => ghost, :value => string(i), :__collapse => true) + end + end + + # vertex marks + vmarks[:name] = "vertices" + vmarks[:type] = "symbol" + vmarks[:from] = OrderedDict(:data => "vertex-data", :__collapse => true) + vmarks[:transform] = [ + OrderedDict( + :type => "force", + :static => false, + :iterations => 1000, + :forces => [ + OrderedDict( + :force => "center", + :x => OrderedDict(:signal => "center.x", :__collapse => true), + :y => OrderedDict(:signal => "center.y", :__collapse => true), + :__collapse => true, + ), + OrderedDict(:force => "collide", :radius => 10, :__collapse => true), + OrderedDict(:force => "nbody", :strength => -100, :__collapse => true), + OrderedDict(:force => "link", :links => "edge-data", :distance => 100, :__collapse => true), ], - "marks": [ - { - "name": "vertices", - "type": "symbol", - "from": {"data": "vertex-data"}, - "encode": { - }, - "transform": [ - { - "type": "force", - "static": false, - "iterations": 1000, - "forces": [ - {"force": "center", "x": {"signal": "center.x"}, "y": {"signal": "center.y"}}, - {"force": "collide", "radius": 10}, - {"force": "nbody", "strength": -100}, - {"force": "link", "links": "edge-data", "distance": 100}, - ] - } - ] - }, - { - "type": "path", - "from": {"data": "edge-data"}, - "encode": { - "update": { - "stroke": {"value": "#ccc"}, - "strokeWidth": {"value": 2}, - "tooltip": {"field": "name"} - } - }, - "transform": [ - { - "type": "linkpath", - "shape": "line", - "sourceX": "datum.source.x", - "sourceY": "datum.source.y", - "targetX": "datum.target.x", - "targetY": "datum.target.y" - } - ] - } - ] - }""" - - return print(io, json) + ), + ] + + # edge marks + emarks[:type] = "path" + emarks[:from] = OrderedDict(:data => "edge-data", :__collapse => true) + emarks[:transform] = [ + OrderedDict( + :type => "linkpath", + :shape => "line", + :sourceX => "datum.source.x", + :sourceY => "datum.source.y", + :targetX => "datum.target.x", + :targetY => "datum.target.y", + ), + ] + + return print_json(io, spec) +end + +function print_json(io::IO, spec; indent::Int=0) + spec = copy(spec) + collapse = get(spec, :__collapse, false) + delete!(spec, :__collapse) + + print(io, '\t'^indent * "{" * (collapse ? "" : "\n")) + + for (i, (key, value)) in enumerate(spec) + print(io, (collapse ? "" : '\t'^(indent + 1)) * "\"$key\": ") + + if isa(value, AbstractDict) + print_json(io, value; indent=0) + + elseif isa(value, Vector) + print(io, "[") + for (j, v) in enumerate(value) + print(io, collapse ? "" : "\n") + print_json(io, v; indent=indent + 2) + + islast = j == length(value) + print(io, (islast ? "" : ",")) + end + print(io, "\n" * '\t'^(indent + 1) * "]") + + elseif isa(value, AbstractString) + print(io, "\"$value\"") + + else + print(io, value) + end + + islast = i == length(spec) + print(io, (islast ? "" : ",") * (collapse ? "" : "\n")) + end + + print(io, (collapse ? "" : '\t'^indent) * "}") end From 7451afebf4efb0302a4c656053acbfb05724ab78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 24 Nov 2024 18:33:23 +0100 Subject: [PATCH 14/18] fixes and more customization for vega plots --- src/Visualization.jl | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Visualization.jl b/src/Visualization.jl index afd11aac1..09711ccee 100644 --- a/src/Visualization.jl +++ b/src/Visualization.jl @@ -110,8 +110,8 @@ function draw(io::IOContext, @nospecialize(tn::AbstractTensorNetwork)) vmarks[:transform] = [ OrderedDict( :type => "force", - :static => false, - :iterations => 1000, + :static => get(io, :(var"force.static"), false), + :iterations => get(io, :(var"force.iterations"), 10000), :forces => [ OrderedDict( :force => "center", @@ -119,9 +119,14 @@ function draw(io::IOContext, @nospecialize(tn::AbstractTensorNetwork)) :y => OrderedDict(:signal => "center.y", :__collapse => true), :__collapse => true, ), - OrderedDict(:force => "collide", :radius => 10, :__collapse => true), - OrderedDict(:force => "nbody", :strength => -100, :__collapse => true), - OrderedDict(:force => "link", :links => "edge-data", :distance => 100, :__collapse => true), + OrderedDict(:force => "collide", :radius => get(io, :(var"force.radius"), 1), :__collapse => true), + OrderedDict(:force => "nbody", :strength => get(io, :(var"force.strength"), -10), :__collapse => true), + OrderedDict( + :force => "link", + :links => "edge-data", + :distance => get(io, :(var"force.distance"), 10), + :__collapse => true, + ), ], ), ] @@ -139,11 +144,20 @@ function draw(io::IOContext, @nospecialize(tn::AbstractTensorNetwork)) :targetY => "datum.target.y", ), ] + emarks[:encode] = OrderedDict( + :update => OrderedDict( + :stroke => OrderedDict(:value => "#ccc", :__collapse => true), + :strokeWidth => OrderedDict(:value => get(io, :(var"edge.width"), 2), :__collapse => true), + :tooltip => OrderedDict(:field => "value", :__collapse => true), + ), + ) return print_json(io, spec) end -function print_json(io::IO, spec; indent::Int=0) +print_json(io::IOContext, spec::AbstractString) = print(io, "\"" * spec * "\"") +function print_json(io::IOContext, spec::AbstractDict) + indent = get(io, :indent, 0) spec = copy(spec) collapse = get(spec, :__collapse, false) delete!(spec, :__collapse) @@ -154,13 +168,13 @@ function print_json(io::IO, spec; indent::Int=0) print(io, (collapse ? "" : '\t'^(indent + 1)) * "\"$key\": ") if isa(value, AbstractDict) - print_json(io, value; indent=0) + print_json(IOContext(io, :indent => 0), value) elseif isa(value, Vector) print(io, "[") for (j, v) in enumerate(value) print(io, collapse ? "" : "\n") - print_json(io, v; indent=indent + 2) + print_json(IOContext(io, :indent => indent + 2), v) islast = j == length(value) print(io, (islast ? "" : ",")) From 786e3e47693852c71e485a28f91a8e1a256fdcbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 24 Nov 2024 19:08:45 +0100 Subject: [PATCH 15/18] add kroki and abbr support for markdown --- docs/package.json | 2 ++ docs/src/.vitepress/config.mts | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/package.json b/docs/package.json index 5633b4976..f6f59e4c0 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,8 +5,10 @@ "docs:preview": "vitepress preview build/.documenter" }, "dependencies": { + "@kazumatu981/markdown-it-kroki": "^1.3.5", "@shikijs/transformers": "^1.1.7", "markdown-it": "^14.1.0", + "markdown-it-abbr": "^2.0.0", "markdown-it-footnote": "^4.0.0", "markdown-it-mathjax3": "^4.3.2", "vitepress": "^1.1.4", diff --git a/docs/src/.vitepress/config.mts b/docs/src/.vitepress/config.mts index bdb14a1c3..eaa384e5a 100644 --- a/docs/src/.vitepress/config.mts +++ b/docs/src/.vitepress/config.mts @@ -2,6 +2,8 @@ import { defineConfig } from 'vitepress' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import mathjax3 from "markdown-it-mathjax3"; import footnote from "markdown-it-footnote"; +import kroki from "@kazumatu981/markdown-it-kroki"; +import abbr from "markdown-it-abbr"; const baseTemp = { base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs! @@ -36,9 +38,11 @@ export default defineConfig({ markdown: { math: true, config(md) { - md.use(tabsMarkdownPlugin), - md.use(mathjax3), - md.use(footnote) + md.use(tabsMarkdownPlugin) + md.use(mathjax3) + md.use(footnote) + md.use(kroki) + md.use(abbr) }, theme: { light: "github-light", From 48e3358cf7570de7bc206a504ab0320b10286141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 24 Nov 2024 19:16:37 +0100 Subject: [PATCH 16/18] fix typo --- docs/src/api/tensornetwork.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/api/tensornetwork.md b/docs/src/api/tensornetwork.md index c2da8f50a..78a8a3a52 100644 --- a/docs/src/api/tensornetwork.md +++ b/docs/src/api/tensornetwork.md @@ -29,5 +29,5 @@ Tenet.ContractSimplification Tenet.DiagonalReduction Tenet.AntiDiagonalGauging Tenet.Truncate -Tenet.SplitSimplificationd +Tenet.SplitSimplification ``` From e6d6f0cc88420eca63857695859bb8af33d1cbb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Sun, 24 Nov 2024 19:21:57 +0100 Subject: [PATCH 17/18] refactor kroki diagram --- docs/Project.toml | 1 - docs/src/developer/type-hierarchy.md | 15 +++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index b2c25ef34..063e50ef4 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -5,7 +5,6 @@ DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" DocumenterVitepress = "4710194d-e776-4893-9690-8d956a29c365" EinExprs = "b1794770-133b-4de1-afb4-526377e9f4c5" GraphMakie = "1ecd5474-83a3-4783-bb4f-06765db800d2" -Kroki = "b3565e16-c1f2-4fe9-b4ab-221c88942068" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" NetworkLayout = "46757867-2c16-5918-afeb-47bfcb05e46a" Tenet = "85d41934-b9cd-44e1-8730-56d86f15f3ec" diff --git a/docs/src/developer/type-hierarchy.md b/docs/src/developer/type-hierarchy.md index 4839859f7..a0a38cace 100644 --- a/docs/src/developer/type-hierarchy.md +++ b/docs/src/developer/type-hierarchy.md @@ -1,9 +1,5 @@ # Inheritance and Traits -```@setup kroki -using Kroki -``` - Julia (and in general, all modern languages like Rust or Go) implement Object Oriented Programming (OOP) in a rather restricted form compared to popular OOP languages like Java, C++ or Python. In particular, they forbid _structural inheritance_; i.e. inheriting fields from parent superclass(es). @@ -13,17 +9,17 @@ Julia design space on this topic is not completely clear. Julia has _abstract ty As of the time of writing, the type hierarchy of Tenet looks like this: -```@example kroki -mermaid"""graph TD +```mermaid +flowchart TD id1(AbstractTensorNetwork) id2(AbstractQuantum) id3(AbstractAnsatz) id4(AbstractMPO) id5(AbstractMPS) id1 -->|inherits| id2 -->|inherits| id3 -->|inherits| id4 -->|inherits| id5 - id1 -->|inherits| TensorNetwork - id2 -->|inherits| Quantum - id3 -->|inherits| Ansatz + id1 -->|implements| TensorNetwork + id2 -->|implements| Quantum + id3 -->|implements| Ansatz id3 -->|inherits| Product id4 -->|inherits| MPO id5 -->|inherits| MPS @@ -36,5 +32,4 @@ mermaid"""graph TD style id3 stroke-dasharray: 5 5 style id4 stroke-dasharray: 5 5 style id5 stroke-dasharray: 5 5 -""" ``` From e702f524192418a20cf0bacff696961ebfd6b9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20S=C3=A1nchez=20Ram=C3=ADrez?= Date: Mon, 25 Nov 2024 02:01:53 +0100 Subject: [PATCH 18/18] refactor plots --- docs/make.jl | 3 +- docs/src/manual/ansatz/mps.md | 49 ++++++++++-------------------- docs/src/manual/transformations.md | 11 ++++--- 3 files changed, 24 insertions(+), 39 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 0474b13f7..238f291b1 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,8 +2,7 @@ using Documenter using DocumenterVitepress using DocumenterCitations using Tenet -using CairoMakie -using GraphMakie +using Makie using LinearAlgebra DocMeta.setdocmeta!(Tenet, :DocTestSetup, :(using Tenet); recursive=true) diff --git a/docs/src/manual/ansatz/mps.md b/docs/src/manual/ansatz/mps.md index 91190139e..4245bf838 100644 --- a/docs/src/manual/ansatz/mps.md +++ b/docs/src/manual/ansatz/mps.md @@ -1,33 +1,26 @@ # Matrix Product States (MPS) -Matrix Product States (MPS) are a Quantum Tensor Network ansatz whose tensors are laid out in a 1D chain. -Due to this, these networks are also known as _Tensor Trains_ in other mathematical fields. -Depending on the boundary conditions, the chains can be open or closed (i.e. periodic boundary conditions). - ```@setup viz using Makie -Makie.inline!(true) -set_theme!(resolution=(800,200)) - using CairoMakie - -using Tenet +using GraphMakie using NetworkLayout -``` - -```@example viz -fig = Figure() # hide - -tn_open = rand(MatrixProduct{State,Open}, n=10, χ=4) # hide -tn_periodic = rand(MatrixProduct{State,Periodic}, n=10, χ=4) # hide +using Tenet -plot!(fig[1,1], tn_open, layout=Spring(iterations=1000, C=0.5, seed=100)) # hide -plot!(fig[1,2], tn_periodic, layout=Spring(iterations=1000, C=0.5, seed=100)) # hide +# Page(offline=true) +# WGLMakie.activate!() +CairoMakie.activate!(type = "svg") +Makie.inline!(true) +set_theme!(resolution=(800,200)) +``` -Label(fig[1,1, Bottom()], "Open") # hide -Label(fig[1,2, Bottom()], "Periodic") # hide +Matrix Product States (MPS) are a Quantum Tensor Network ansatz whose tensors are laid out in a 1D chain. +Due to this, these networks are also known as _Tensor Trains_ in other mathematical fields. +Depending on the boundary conditions, the chains can be open or closed (i.e. periodic boundary conditions). -fig # hide +```@example viz +tn = rand(MPS; n=10, maxdim=2) # hide +graphplot(tn; layout=Stress()) # hide ``` ## Matrix Product Operators (MPO) @@ -36,18 +29,8 @@ Matrix Product Operators (MPO) are the operator version of [Matrix Product State The major difference between them is that MPOs have 2 indices per site (1 input and 1 output) while MPSs only have 1 index per site (i.e. an output). ```@example viz -fig = Figure() # hide - -tn_open = rand(MatrixProduct{Operator,Open}, n=10, χ=4) # hide -tn_periodic = rand(MatrixProduct{Operator,Periodic}, n=10, χ=4) # hide - -plot!(fig[1,1], tn_open, layout=Spring(iterations=1000, C=0.5, seed=100)) # hide -plot!(fig[1,2], tn_periodic, layout=Spring(iterations=1000, C=0.5, seed=100)) # hide - -Label(fig[1,1, Bottom()], "Open") # hide -Label(fig[1,2, Bottom()], "Periodic") # hide - -fig # hide +tn = rand(MPO, n=10, maxdim=2) # hide +graphplot(tn; layout=Stress()) # hide ``` In `Tenet`, the generic `MatrixProduct` ansatz implements this topology. Type variables are used to address their functionality (`State` or `Operator`) and their boundary conditions (`Open` or `Periodic`). diff --git a/docs/src/manual/transformations.md b/docs/src/manual/transformations.md index 5a2c97885..49cb16bb7 100644 --- a/docs/src/manual/transformations.md +++ b/docs/src/manual/transformations.md @@ -2,11 +2,14 @@ ```@setup plot using Makie -Makie.inline!(true) -using GraphMakie using CairoMakie -using Tenet +using GraphMakie using NetworkLayout +using Tenet + +CairoMakie.activate!(type = "svg") +Makie.inline!(true) +set_theme!(resolution=(800,200)) ``` In tensor network computations, it is good practice to apply various transformations to simplify the network structure, reduce computational cost, or prepare the network for further operations. These transformations modify the network's structure locally by permuting, contracting, factoring or truncating tensors. @@ -123,7 +126,7 @@ tn = TensorNetwork([ #hide reduced = transform(tn, Tenet.SplitSimplification) #hide graphplot!(fig[1, 1], tn; layout=Stress(), labels=true) #hide -graphplot!(fig[1, 2], reduced, layout=Spring(C=11); labels=true) #hide +graphplot!(fig[1, 2], reduced; layout=Spring(C=11), labels=true) #hide Label(fig[1, 1, Bottom()], "Original") #hide Label(fig[1, 2, Bottom()], "Transformed") #hide