├── heatdata.csv ├── .gitignore ├── _config.yml ├── docs ├── _config.yml ├── src │ └── index.md ├── make.jl └── build │ ├── search_index.js │ ├── assets │ ├── themeswap.js │ ├── search.js │ └── documenter.js │ ├── search │ └── index.html │ └── index.html ├── .idea ├── .gitignore ├── vcs.xml ├── misc.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── modules.xml └── QuNet.iml ├── plots ├── cost_gridsize.pdf ├── cost_gridsize.png ├── cost_maxpaths.pdf ├── cost_maxpaths.png ├── cost_temporal.pdf ├── cost_temporal.png ├── cost_userpair.pdf ├── cost_userpair.png ├── path_gridsize.pdf ├── path_gridsize.png ├── path_temporal.pdf ├── path_temporal.png ├── path_userpair.pdf ├── path_userpair.png ├── avepath_userpair.pdf ├── avepath_userpair.png ├── cost_percolation.pdf ├── cost_percolation.png ├── network_drawing.pdf ├── path_percolation.pdf ├── path_percolation.png ├── temporal_drawing.pdf ├── bandwidth_with_userpairs.pdf ├── bandwidth_with_userpairs.png ├── bandwidth_with_memory_rate.pdf └── bandwidth_with_memory_rate.png ├── QuNet-Paper ├── multiheat.pdf ├── multiheat.png ├── singleheat.pdf ├── .idea │ ├── vcs.xml │ ├── misc.xml │ ├── inspectionProfiles │ │ └── profiles_settings.xml │ ├── modules.xml │ ├── QuNet-Paper.iml │ └── workspace.xml ├── heatmap.py ├── heatmap.jl ├── SatPlotDemo.jl └── MultiPathDemo.jl ├── .vscode ├── settings.json └── launch.json ├── src ├── workspace.code-workspace ├── TypeTree.jl ├── install_packages.jl ├── GraphInterface.jl ├── Plot.jl ├── Percolation.jl ├── QuNet.jl ├── Utilities.jl ├── CostVector.jl ├── Node.jl ├── Channel.jl ├── scrap.jl ├── Network.jl ├── Benchmarking.jl ├── TemporalGraphs.jl └── Routing.jl ├── test ├── network-library │ ├── barbell.jl │ ├── smalltemp.jl │ ├── simple_satnet.jl │ ├── simple_network.jl │ ├── small_square.jl │ ├── bridge.jl │ ├── greedy_test.jl │ └── shortest_path_test.jl ├── sandbox.jl └── runtests.jl ├── .github └── workflows │ ├── TagBot.yml │ └── CompatHelper.yml ├── data ├── bandwidth_with_memory_rate.txt └── percolation.txt ├── LICENSE ├── Project.toml └── README.md /heatdata.csv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /plots/cost_gridsize.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_gridsize.pdf -------------------------------------------------------------------------------- /plots/cost_gridsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_gridsize.png -------------------------------------------------------------------------------- /plots/cost_maxpaths.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_maxpaths.pdf -------------------------------------------------------------------------------- /plots/cost_maxpaths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_maxpaths.png -------------------------------------------------------------------------------- /plots/cost_temporal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_temporal.pdf -------------------------------------------------------------------------------- /plots/cost_temporal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_temporal.png -------------------------------------------------------------------------------- /plots/cost_userpair.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_userpair.pdf -------------------------------------------------------------------------------- /plots/cost_userpair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_userpair.png -------------------------------------------------------------------------------- /plots/path_gridsize.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_gridsize.pdf -------------------------------------------------------------------------------- /plots/path_gridsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_gridsize.png -------------------------------------------------------------------------------- /plots/path_temporal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_temporal.pdf -------------------------------------------------------------------------------- /plots/path_temporal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_temporal.png -------------------------------------------------------------------------------- /plots/path_userpair.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_userpair.pdf -------------------------------------------------------------------------------- /plots/path_userpair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_userpair.png -------------------------------------------------------------------------------- /QuNet-Paper/multiheat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/QuNet-Paper/multiheat.pdf -------------------------------------------------------------------------------- /QuNet-Paper/multiheat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/QuNet-Paper/multiheat.png -------------------------------------------------------------------------------- /QuNet-Paper/singleheat.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/QuNet-Paper/singleheat.pdf -------------------------------------------------------------------------------- /plots/avepath_userpair.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/avepath_userpair.pdf -------------------------------------------------------------------------------- /plots/avepath_userpair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/avepath_userpair.png -------------------------------------------------------------------------------- /plots/cost_percolation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_percolation.pdf -------------------------------------------------------------------------------- /plots/cost_percolation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/cost_percolation.png -------------------------------------------------------------------------------- /plots/network_drawing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/network_drawing.pdf -------------------------------------------------------------------------------- /plots/path_percolation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_percolation.pdf -------------------------------------------------------------------------------- /plots/path_percolation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/path_percolation.png -------------------------------------------------------------------------------- /plots/temporal_drawing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/temporal_drawing.pdf -------------------------------------------------------------------------------- /plots/bandwidth_with_userpairs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/bandwidth_with_userpairs.pdf -------------------------------------------------------------------------------- /plots/bandwidth_with_userpairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/bandwidth_with_userpairs.png -------------------------------------------------------------------------------- /plots/bandwidth_with_memory_rate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/bandwidth_with_memory_rate.pdf -------------------------------------------------------------------------------- /plots/bandwidth_with_memory_rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drpeterrohde/QuNet/HEAD/plots/bandwidth_with_memory_rate.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "julia.environmentPath": "/Users/peterrohde/Library/Mobile Documents/com~apple~CloudDocs/Code/QuNet/src" 3 | } -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # QuNet 2 | 3 | ```@meta 4 | CurrentModule = QuNet 5 | ``` 6 | 7 | ```@autodocs 8 | Modules = [QuNet] 9 | ``` 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /QuNet-Paper/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /QuNet-Paper/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, Main.QuNet 2 | 3 | makedocs( 4 | sitename = "QuNet", 5 | modules = [QuNet] 6 | ) 7 | 8 | # deploydocs( 9 | # deps = Deps.pip("pygments", "mkdocs", "python-markdown-math") 10 | # ) 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".." 5 | } 6 | ], 7 | "settings": { 8 | "julia.environmentPath": "/Users/peterrohde/Library/Mobile Documents/com~apple~CloudDocs/Code/QuNet/src" 9 | } 10 | } -------------------------------------------------------------------------------- /QuNet-Paper/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /test/network-library/barbell.jl: -------------------------------------------------------------------------------- 1 | barbell = QNetwork() 2 | A = BasicNode("A") 3 | B = BasicNode("B") 4 | AB = BasicChannel(A, B) 5 | AB.costs = Dict("loss"=>1, "Z"=>0.5) 6 | for i in [A, B, AB] 7 | add(barbell, i) 8 | end 9 | refresh_graph!(barbell) 10 | -------------------------------------------------------------------------------- /test/network-library/smalltemp.jl: -------------------------------------------------------------------------------- 1 | """ 2 | A small 2x2 GridNetwork extended in time by 2 steps 3 | All edges, including temporal ones, have unit costs 4 | """ 5 | 6 | using QuNet 7 | 8 | g = GridNetwork(2,2) 9 | smalltemp = QuNet.TemporalGraph(g, 2, memory_costs=unit_costvector()) 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QuNet-Paper/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/TypeTree.jl: -------------------------------------------------------------------------------- 1 | function showtypetree(T, level = 0) 2 | println("\t"^level, last(split(string(T), "."))) 3 | for t in subtypes(T) 4 | showtypetree(t, level + 1) 5 | end 6 | end 7 | 8 | """ 9 | Display the QuNet type tree 10 | """ 11 | function qunet_type_tree() 12 | showtypetree(QuNet.QObject) 13 | end 14 | -------------------------------------------------------------------------------- /.idea/QuNet.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/network-library/simple_satnet.jl: -------------------------------------------------------------------------------- 1 | simple_satnet = QNetwork() 2 | S = PlanSatNode("S") 3 | S.velocity = Velocity(100, 0, 0) 4 | B.location = Coords(1000, 0, 0) 5 | S.location = Coords(500, 0, 1000) 6 | AS = AirChannel(A, S) 7 | SB = AirChannel(B, S) 8 | for i in [A, B, S, AS, SB] 9 | add(simple_satnet, i) 10 | end 11 | refresh_graph!(simple_satnet) 12 | -------------------------------------------------------------------------------- /QuNet-Paper/.idea/QuNet-Paper.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | issue_comment: 4 | types: 5 | - created 6 | workflow_dispatch: 7 | jobs: 8 | TagBot: 9 | if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: JuliaRegistries/TagBot@v1 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | ssh: ${{ secrets.DOCUMENTER_KEY }} 16 | -------------------------------------------------------------------------------- /test/network-library/simple_network.jl: -------------------------------------------------------------------------------- 1 | simple_network = QNetwork() 2 | A = BasicNode("A") 3 | C = BasicNode("C") 4 | B = BasicNode("B") 5 | AB = BasicChannel(A, B, exp_cost=false) 6 | AC = BasicChannel(A, C, exp_cost=false) 7 | CB = BasicChannel(C, B, exp_cost=false) 8 | AB.costs = unit_costvector() 9 | AC.costs = unit_costvector() 10 | CB.costs = unit_costvector() 11 | 12 | for i in [A, B, C, AB, AC, CB] 13 | add(simple_network, i) 14 | end 15 | 16 | refresh_graph!(simple_network) 17 | -------------------------------------------------------------------------------- /src/install_packages.jl: -------------------------------------------------------------------------------- 1 | using Pkg 2 | 3 | dependencies = [ 4 | "GraphRecipes", 5 | "LightGraphs", 6 | "SimpleWeightedGraphs", 7 | "GraphPlot", 8 | "MetaGraphs", 9 | "Documenter", 10 | "StatsBase", 11 | "LinearAlgebra", 12 | "Statistics", 13 | "Colors", 14 | "Plots", 15 | "LaTeXStrings", 16 | "Cairo", 17 | "Compose", 18 | "SparseArrays", 19 | "QuadGK", 20 | "SatelliteToolbox", 21 | "GR"] 22 | 23 | Pkg.add(dependencies) 24 | Pkg.update(dependencies) 25 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | on: 3 | schedule: 4 | - cron: 0 0 * * * 5 | workflow_dispatch: 6 | jobs: 7 | CompatHelper: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Pkg.add("CompatHelper") 11 | run: julia -e 'using Pkg; Pkg.add("CompatHelper")' 12 | - name: CompatHelper.main() 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} 16 | run: julia -e 'using CompatHelper; CompatHelper.main()' 17 | -------------------------------------------------------------------------------- /test/network-library/small_square.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Redundent code, should replace this in runtests when I get the chance 3 | """ 4 | 5 | small_square = QNetwork() 6 | A = BasicNode("A") 7 | B = BasicNode("B") 8 | C = BasicNode("C") 9 | D = BasicNode("D") 10 | AB = BasicChannel(A, B) 11 | BC = BasicChannel(B, C) 12 | AD = BasicChannel(A, D) 13 | DC = BasicChannel(D, C) 14 | 15 | AB.costs = unit_costvector() 16 | BC.costs = unit_costvector() 17 | AD.costs = unit_costvector() 18 | DC.costs = unit_costvector() 19 | 20 | for i in [A, B, C, D, AB, BC, AD, DC] 21 | add(small_square, i) 22 | end 23 | 24 | refresh_graph!(small_square) 25 | -------------------------------------------------------------------------------- /test/network-library/bridge.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Two end-user pairs (A1, B1) (A2, B2) are seperated by a single bridge (M1, M2) 3 | which they'll have to compete for. 4 | """ 5 | bridge = QNetwork() 6 | A1 = BasicNode("A1") 7 | A2 = BasicNode("A2") 8 | M1 = BasicNode("M1") 9 | M2 = BasicNode("M2") 10 | B1 = BasicNode("B1") 11 | B2 = BasicNode("B2") 12 | 13 | A1M1 = BasicChannel(A1, M1) 14 | A2M1 = BasicChannel(A2, M1) 15 | M1M2 = BasicChannel(M1, M2) 16 | M2B1 = BasicChannel(M2, B1) 17 | M2B2 = BasicChannel(M2, B2) 18 | 19 | for i in [A1M1, A2M1, M1M2, M2B1, M2B2] 20 | i.costs = unit_costvector() 21 | end 22 | 23 | for i in [A1, A2, M1, M2, B1, B2, A1M1, A2M1, M1M2, M2B1, M2B2] 24 | add(bridge, i) 25 | end 26 | refresh_graph!(bridge) 27 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "julia", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/Program", 12 | "stopOnEntry": false 13 | }, 14 | { 15 | "type": "julia", 16 | "request": "launch", 17 | "name": "Run active Julia file", 18 | "program": "${file}", 19 | "stopOnEntry": false, 20 | "cwd": "${workspaceFolder}", 21 | "juliaEnv": "${command:activeJuliaEnvironment}" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /test/network-library/greedy_test.jl: -------------------------------------------------------------------------------- 1 | """ 2 | A graph to test greedy multi path routing 3 | """ 4 | 5 | greedy_test = QNetwork() 6 | A = BasicNode("A") 7 | B = BasicNode("B") 8 | M1 = BasicNode("M1") 9 | M2 = BasicNode("M2") 10 | M3 = BasicNode("M3") 11 | 12 | AM1 = BasicChannel(A, M1, exp_cost=false) 13 | AM2 = BasicChannel(A, M2, exp_cost=false) 14 | AM3 = BasicChannel(A, M3, exp_cost=false) 15 | M1B = BasicChannel(M1, B, exp_cost=false) 16 | M2B = BasicChannel(M2, B, exp_cost=false) 17 | M3B = BasicChannel(M3, B, exp_cost=false) 18 | 19 | AM1.costs = Dict("loss"=>0.5, "Z"=>0.5) 20 | AM2.costs = Dict("loss"=>1.0, "Z"=>1.0) 21 | AM3.costs = Dict("loss"=>1.5, "Z"=>1.5) 22 | M1B.costs = Dict("loss"=>0.5, "Z"=>0.5) 23 | M2B.costs = Dict("loss"=>1.0, "Z"=>1.0) 24 | M3B.costs = Dict("loss"=>1.5, "Z"=>1.5) 25 | 26 | for i in [A, B, M1, M2, M3, AM1, AM2, AM3, M1B, M2B, M3B] 27 | add(greedy_test, i) 28 | end 29 | 30 | refresh_graph!(greedy_test) 31 | -------------------------------------------------------------------------------- /data/bandwidth_with_memory_rate.txt: -------------------------------------------------------------------------------- 1 | grid_size = 10 2 | num_trials = 5000 3 | time_depth = 8 4 | num_pairs = 50 5 | perc_range = (0.0, 0.05, 1.0) 6 | asynchronus_weight = 100 7 | bandwidth_ratios = Any[1.007833333333333, 0.9836866666666665, 0.99725, 1.0133633333333327, 1.024373333333333, 1.036746666666666, 1.0484399999999994, 1.0562899999999995, 1.0622199999999993, 1.073226666666666, 1.0732799999999996, 1.0800499999999993, 1.0804466666666663, 1.0852199999999996, 1.0895433333333326, 1.0933099999999996, 1.0896333333333326, 1.0930066666666665, 1.0956199999999998, 1.0971099999999996, 1.1010099999999998] 8 | bandwidth_error = Any[0.002326079830069855, 0.002265407711453345, 0.00228766562014483, 0.00232992606524113, 0.0022954462138738063, 0.002301480081531711, 0.0022876153789698184, 0.00228563848738573, 0.002264436811529817, 0.0022576016290543582, 0.002250291407749945, 0.0022489627703042797, 0.0022316692391921907, 0.0021785692900088316, 0.002180728802467135, 0.0022086722734458784, 0.0022092916847297455, 0.0021698519138259585, 0.0021927754128162847, 0.002149384822461914, 0.002183209186102885] 9 | -------------------------------------------------------------------------------- /src/GraphInterface.jl: -------------------------------------------------------------------------------- 1 | """ 2 | File to parse QNetwork with other graph types 3 | """ 4 | 5 | import SparseArrays:dropzeros! 6 | 7 | """ 8 | Remove both directions of the SimpleWeightedGraph edge properly as opposed 9 | to just setting it to zero. 10 | """ 11 | function hard_rem_edge!(graph::SimpleWeightedDiGraph, src::Int64, dst::Int64) 12 | rem_edge!(graph, src, dst) 13 | rem_edge!(graph, dst, src) 14 | dropzeros!(graph.weights) 15 | end 16 | 17 | """ 18 | Convert a path specifying node ints to a list of SimpleEdges 19 | """ 20 | function int_to_simpleedge(path::Vector{Tuple{Int, Int}}) 21 | new_path = [] 22 | for edge in path 23 | new_edge = LightGraphs.SimpleEdge(edge[1], edge[2]) 24 | push!(new_path, new_edge) 25 | end 26 | return new_path 27 | end 28 | 29 | """ 30 | Convert a path of SimpleEdges into a list of Int tuples 31 | """ 32 | function simpleedge_to_int(path::Vector{LightGraphs.SimpleGraphs.SimpleEdge{Int64}}) 33 | new_path = [] 34 | for edge in path 35 | new_edge = (edge.src, edge.dst) 36 | push!(new_path, new_edge) 37 | end 38 | return new_path 39 | end 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hudson Leone and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/network-library/shortest_path_test.jl: -------------------------------------------------------------------------------- 1 | """ 2 | A graph to test shortest_path functions. 3 | 4 | The shortest path of this graph in terms of loss is: 5 | A(1) - M3(5) - B(2) 6 | With costs of loss:10.0, Z:2.0 7 | 8 | The shortest path in terms of loss is: 9 | A(1) - M1(3) - B(2) 10 | With costs of loss:2.0, Z:10.0 11 | """ 12 | 13 | shortest_path_test = QNetwork() 14 | A = BasicNode("A") 15 | B = BasicNode("B") 16 | M1 = BasicNode("M1") 17 | M2 = BasicNode("M2") 18 | M3 = BasicNode("M3") 19 | 20 | AM1 = BasicChannel(A, M1, exp_cost=false) 21 | AM2 = BasicChannel(A, M2, exp_cost=false) 22 | AM3 = BasicChannel(A, M3, exp_cost=false) 23 | M1B = BasicChannel(M1, B, exp_cost=false) 24 | M2B = BasicChannel(M2, B, exp_cost=false) 25 | M3B = BasicChannel(M3, B, exp_cost=false) 26 | 27 | AM1.costs = Dict("loss"=>1.0, "Z"=>5.0) 28 | AM2.costs = Dict("loss"=>3.0, "Z"=>3.0) 29 | AM3.costs = Dict("loss"=>5.0, "Z"=>1.0) 30 | M1B.costs = Dict("loss"=>1.0, "Z"=>5.0) 31 | M2B.costs = Dict("loss"=>3.0, "Z"=>3.0) 32 | M3B.costs = Dict("loss"=>5.0, "Z"=>1.0) 33 | 34 | for i in [A, B, M1, M2, M3, AM1, AM2, AM3, M1B, M2B, M3B] 35 | add(shortest_path_test, i) 36 | end 37 | 38 | refresh_graph!(shortest_path_test) 39 | -------------------------------------------------------------------------------- /src/Plot.jl: -------------------------------------------------------------------------------- 1 | using Cairo, Compose 2 | 3 | function plot_network(graph::AbstractGraph, user_paths, locs_x, locs_y) 4 | used_edges = [] 5 | used_by = Dict() 6 | 7 | colour_pal = [colorant"lightgrey", colorant"orange", colorant"lightslateblue", colorant"green"] 8 | 9 | # nodecolor = ["blue" for i in 1:nv(graph)] 10 | 11 | this_user = 0 12 | for paths in user_paths 13 | this_user += 1 14 | for path in paths 15 | for edge in path 16 | push!(used_edges, edge) 17 | used_by[(edge.src,edge.dst)] = this_user 18 | end 19 | end 20 | end 21 | 22 | colours = [] 23 | widths = [] 24 | for edge in edges(graph) 25 | if edge in used_edges 26 | push!(colours, used_by[(edge.src,edge.dst)] + 1) 27 | push!(widths, 5) 28 | else 29 | push!(colours, 1) 30 | push!(widths, 1) 31 | end 32 | end 33 | 34 | mygplot = gplot(graph, locs_x, locs_y, edgestrokec=colour_pal[colours], 35 | edgelinewidth=widths, arrowlengthfrac=0.04)#, layout=spring_layout) 36 | # Save to pdf 37 | draw(PDF("plots/network_drawing.pdf", 16cm, 16cm), mygplot) 38 | end 39 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "QuNet" 2 | uuid = "ad9aa78f-8ca5-41a8-8827-8681ea26ae9b" 3 | authors = ["Peter Rohde ", "Hudson Leone "] 4 | version = "0.1.0" 5 | 6 | [deps] 7 | Cairo = "159f3aea-2a34-519c-b102-8c37f9878175" 8 | Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" 9 | Compose = "a81c6b42-2e10-5240-aca2-a61377ecd94b" 10 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 11 | GraphPlot = "a2cc645c-3eea-5389-862e-a155d0052231" 12 | LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" 13 | LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d" 14 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 15 | MetaGraphs = "626554b9-1ddb-594c-aa3c-2596fe9399a5" 16 | Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" 17 | QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" 18 | Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" 19 | SatelliteToolbox = "6ac157d9-b43d-51bb-8fab-48bf53814f4a" 20 | SimpleWeightedGraphs = "47aef6b3-ad0c-573a-a1e2-d07658019622" 21 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 22 | Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 23 | StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" 24 | 25 | [compat] 26 | Cairo = "1.0" 27 | Documenter = "0.26" 28 | julia = "1" 29 | 30 | [extras] 31 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 32 | 33 | [targets] 34 | test = ["Test"] 35 | -------------------------------------------------------------------------------- /QuNet-Paper/heatmap.py: -------------------------------------------------------------------------------- 1 | # Preceeding from heatmap.jl (start there if you haven't already) 2 | 3 | # Libraries 4 | import numpy as np 5 | import pandas as pd 6 | import matplotlib.pyplot as plt 7 | # This could be used for smoothing the heatmap out 8 | # from scipy.stats import kde 9 | 10 | # Import data 11 | df = pd.read_csv("~/.julia/dev/QuNet/data/heatmap.csv") 12 | 13 | # Extract data into x, y 14 | e = df["Efficiency"].tolist() 15 | f = df["Fidelity"].tolist() 16 | 17 | # Create a heatmap of end-user data 18 | fig, axes = plt.subplots() 19 | nbins = 40 20 | axes.hist2d(e, f, range=((0,1), (0.5, 1)), bins=nbins, cmap=plt.cm.hot) 21 | axes.set_xlabel("Efficiency") 22 | axes.set_ylabel("Fidelity") 23 | 24 | # Collect data for the QKD contour plots 25 | delta = 0.01 26 | x = np.arange(0.0, 1.0, delta) 27 | y = np.arange(0.5, 1.0, delta ) 28 | E, F = np.meshgrid(x, y) 29 | 30 | # End to end failure rate of a 100 x 100 grid lattice with 50 competing user pairs 31 | P0 = 0.201 32 | 33 | # Average rate of transmission per user pair 34 | R = (1-P0) * E 35 | 36 | # QKD contour 37 | Z = R * (1 - (F * np.log(1 - F)/np.log(2) + (1 - F) * np.log(F)/np.log(2))) 38 | 39 | # Overlay the contour for Z = 1, 2, 3, ... 40 | # CS = axes.contour(E, F, Z, levels = [1, 2, 3, 4, 5]) 41 | # axes.clabel(CS, inline=1, fontsize=10, fmt="%1.1f") 42 | # plt.show() 43 | plt.savefig("singleheat.pdf") 44 | -------------------------------------------------------------------------------- /src/Percolation.jl: -------------------------------------------------------------------------------- 1 | function percolate_vertices(graph::AbstractGraph, p)::AbstractGraph 2 | return percolate_vertices(graph, p, [0]) 3 | end 4 | 5 | """ 6 | percolate_vertices(graph, p, exclude) 7 | 8 | Perform vertex percolation on graph with probability p, excluding specified vertices. 9 | """ 10 | function percolate_vertices(graph::AbstractGraph, p, exclude::Array{Int64})::AbstractGraph 11 | perc_graph = deepcopy(graph) 12 | 13 | for vertex in vertices(graph) 14 | # if !(vertex in exclude) 15 | if rand(Float64) <= p 16 | rem_vertex!(perc_graph, vertex) 17 | end 18 | # end 19 | end 20 | 21 | return perc_graph 22 | end 23 | 24 | 25 | # TODO: Modify this so it removes edges in both directions 26 | function percolate_edges(graph::AbstractGraph, p)::AbstractGraph 27 | perc_graph = deepcopy(graph) 28 | 29 | for edge in edges(graph) 30 | if rand(Float64) <= p 31 | rem_edge!(perc_graph, edge) 32 | end 33 | end 34 | 35 | return perc_graph 36 | end 37 | 38 | 39 | function percolate_edges(network::QNetwork, p)::QNetwork 40 | perc_network = deepcopy(network) 41 | 42 | for channel in perc_network.channels 43 | if rand(Float64) <= p 44 | channel.active = false 45 | end 46 | end 47 | refresh_graph!(perc_network) 48 | return perc_network 49 | end 50 | -------------------------------------------------------------------------------- /docs/build/search_index.js: -------------------------------------------------------------------------------- 1 | var documenterSearchIndex = {"docs": 2 | [{"location":"#QuNet","page":"QuNet","title":"QuNet","text":"","category":"section"},{"location":"","page":"QuNet","title":"QuNet","text":"CurrentModule = QuNet","category":"page"},{"location":"","page":"QuNet","title":"QuNet","text":"Modules = [QuNet]","category":"page"},{"location":"#QuNet.BasicNode","page":"QuNet","title":"QuNet.BasicNode","text":"The default QNode object. Nothing special, but nothing unspecial either ;-)\"\n\n\n\n\n\n","category":"type"},{"location":"#QuNet.Coords","page":"QuNet","title":"QuNet.Coords","text":"Coordinates of the QNode object in two or three spatial dimensions\n\n\n\n\n\n","category":"type"},{"location":"#QuNet.add-Tuple{QNetwork,QNode}","page":"QuNet","title":"QuNet.add","text":"Add a QNode object to the network.\n\nExample:\n```\nusing QuNet\nQ = QNetwork()\nA = BasicNode(\"A\")\nadd(Q, A)\n```\n\n\n\n\n\n","category":"method"},{"location":"#QuNet.percolate_vertices-Tuple{LightGraphs.AbstractGraph,Any,Array{Int64,N} where N}","page":"QuNet","title":"QuNet.percolate_vertices","text":"percolate_vertices(graph, p, exclude)\n\nPerform vertex percolation on graph with probability p, excluding specified vertices.\n\n\n\n\n\n","category":"method"},{"location":"#QuNet.remove-Tuple{QNetwork,QNode}","page":"QuNet","title":"QuNet.remove","text":"Remove a Qnode object from the network.\n\nThe convention for removal echos that of LightGraphs.jl. Suppose a given network has N nodes, and we want to remove the node with the id v.\n\nCheck if v < N . If false, simply pop the node from QNetwork.nodes\nElse, swap the nodes v and N, then pop from QNetworks.nodes\n\n\n\n\n\n","category":"method"}] 3 | } 4 | -------------------------------------------------------------------------------- /QuNet-Paper/heatmap.jl: -------------------------------------------------------------------------------- 1 | using QuNet 2 | using DataFrames 3 | using CSV 4 | using Statistics 5 | 6 | # Graph + sampling parameters 7 | grid_size = 100 8 | num_pairs = 50 9 | num_trials = 100 10 | max_paths = 1 11 | 12 | # Get the "coordinates" associated with end users 13 | # that is, the associated efficiency and fidelity of the paths connecting them (if any) 14 | edge_costs = Dict("loss"=>0.05, "Z"=>0.05) 15 | net = GridNetwork(grid_size, grid_size, edge_costs=edge_costs) 16 | #usage: 17 | # (network::Union{QNetwork, QuNet.TemporalGraph}, 18 | # num_trials::Int64, num_pairs::Int64; max_paths=3, src_layer::Int64=-1, 19 | # dst_layer::Int64=-1, edge_perc_rate=0.0) 20 | coord_list = QuNet.heat_data(net, num_trials, num_pairs, max_paths=max_paths) 21 | 22 | # List comprehension to extract data 23 | e = [coord_list[i][1] for i in 1:length(coord_list)] 24 | f = [coord_list[i][2] for i in 1:length(coord_list)] 25 | 26 | # # Convert cood_list to data_frame and write to csv 27 | df = DataFrame(Efficiency = e, Fidelity = f) 28 | CSV.write("data/heatmap.csv", df) 29 | # Continue on from heatmap.py to generate the plot 30 | 31 | 32 | """ 33 | This function gets the end-to-end failure rate (P₀) of the graph in the heatmap 34 | plot. By my calculations, this is about 0.20 35 | """ 36 | function get_P0_for_graph() 37 | num_trials = 100 38 | max_paths = 4 39 | 40 | failure_counts = [] 41 | for i in 1:num_trials 42 | println("num_trial $i") 43 | edge_costs = Dict("loss"=>0.05, "Z"=>0.05) 44 | net = GridNetwork(grid_size, grid_size, edge_costs=edge_costs) 45 | user_pairs = QuNet.make_user_pairs(net, 50) 46 | pathset, pur_paths, pathuse_count = QuNet.greedy_multi_path!(net, QuNet.purify, user_pairs, 4) 47 | push!(failure_counts, pathuse_count[1]) 48 | end 49 | return(mean(failure_counts./num_pairs)) 50 | end 51 | -------------------------------------------------------------------------------- /src/QuNet.jl: -------------------------------------------------------------------------------- 1 | __precompile__(true) 2 | 3 | module QuNet 4 | 5 | # TODO: Update what we should be using vs importing. Be discriminating! 6 | using LightGraphs, SimpleWeightedGraphs, GraphPlot, MetaGraphs, GraphRecipes 7 | using LinearAlgebra, StatsBase, Statistics 8 | using Documenter, Colors, Plots, LaTeXStrings 9 | 10 | # using SatelliteToolbox 11 | # using QuadGK 12 | 13 | import SparseArrays: dropzeros! 14 | import Base: *, print, string 15 | import GraphPlot: gplot 16 | import QuadGK: quadgk 17 | import SatelliteToolbox: expatmosphere 18 | 19 | abstract type QObject end 20 | abstract type QNode <: QObject end 21 | abstract type QChannel <: QObject end 22 | 23 | TIME_STEP = 0.01 24 | 25 | # WARNING The order of these is fairly important. 26 | # Don't change them willy-nilly unless you like segfaults and screaming 27 | include("Network.jl") 28 | include("TemporalGraphs.jl") 29 | include("CostVector.jl") 30 | include("Node.jl") 31 | include("Channel.jl") 32 | include("Percolation.jl") 33 | include("Routing.jl") 34 | include("Plot.jl") 35 | include("Utilities.jl") 36 | include("Benchmarking.jl") 37 | include("GraphInterface.jl") 38 | include("TypeTree.jl") 39 | 40 | export 41 | # Abstract Classes 42 | QObject, QNode, QChannel, 43 | 44 | # Benchmarking.jl 45 | percolation_bench, dict_average, dict_err, make_user_pairs, net_performance, 46 | 47 | # Channel.jl 48 | BasicChannel, AirChannel, 49 | 50 | # CostVector.jl 51 | zero_costvector, unit_costvector, convert_costs, get_pathcv, 52 | 53 | # Network.jl 54 | QNetwork, GridNetwork, add, update, 55 | refresh_graph!, 56 | 57 | # Node.jl 58 | Coords, BasicNode, Velocity, PlanSatNode, 59 | 60 | # Percolation.jl 61 | 62 | # Plot.jl 63 | gplot, 64 | 65 | # GraphInterface.jl 66 | hard_rem_edge!, 67 | 68 | # Routing.jl 69 | shortest_path, 70 | 71 | # TemporalGraphs.jl 72 | 73 | # Utilities.jl 74 | dB_to_P, P_to_dB, Z_to_dB, dB_to_Z, purify 75 | 76 | # TypeTree.jl 77 | qunet_type_tree 78 | 79 | end 80 | -------------------------------------------------------------------------------- /src/Utilities.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities.jl contains conversion methods, purification schemes, 3 | and other miscelanious utilities 4 | """ 5 | 6 | 7 | """ 8 | Convert from decibelic loss to metric form 9 | """ 10 | function dB_to_P(dB::Float64)::Float64 11 | P = 10.0^(-dB/10) 12 | return P 13 | end 14 | 15 | """ 16 | Convert from metric form to decibelic loss 17 | """ 18 | function P_to_dB(P::Float64)::Float64 19 | dB = -10.0*log(10,P) 20 | return dB 21 | end 22 | 23 | """ 24 | Convert from dephasing probability to decibelic form 25 | """ 26 | function Z_to_dB(Z::Float64)::Float64 27 | dB = -10.0*log(10, 2*Z-1) 28 | return dB 29 | end 30 | 31 | """ 32 | Convert from decibelic dephasing to metric form 33 | """ 34 | function dB_to_Z(dB::Float64)::Float64 35 | Z = (10^(-dB/10) + 1)/2 36 | return Z 37 | end 38 | 39 | 40 | function purify_PBS(F1::Float64,F2::Float64)::(Float64,Float64) 41 | F = F1*F1 / (F1*F2 + (1-F1)(1-F2)) 42 | P = 1 43 | return (F,P) 44 | end 45 | 46 | 47 | function purify_CNOT(F1::Float64,F2::Float64)::(Float64,Float64) 48 | F = F1*F1 / (F1*F2 + (1-F1)(1-F2)) 49 | P = 1 50 | return (F,P) 51 | end 52 | 53 | """ 54 | Probabilistic purification scheme used in greedy_multi_path! 55 | Takes a list of cost vectors (dictionaries) as input and returns a cost vector 56 | """ 57 | function purify(cost_vectors::Vector{Dict{Any,Any}}, return_as_dB::Bool=true) 58 | @assert keys(zero_costvector()) == keys(cost_vectors[1]) "Incompatible keys" 59 | p_arr = [dB_to_P(i["loss"]) for i in cost_vectors] 60 | z_arr = [dB_to_Z(i["Z"]) for i in cost_vectors] 61 | 62 | p = prod(p_arr) * (prod(z_arr) + prod(1 .- z_arr)) 63 | z = prod(z_arr) / ((prod(z_arr) + prod(1 .- z_arr))) 64 | 65 | if return_as_dB == true 66 | return Dict("loss"=>P_to_dB(p), "Z"=>Z_to_dB(z)) 67 | else 68 | return Dict("loss"=>p, "Z"=>z) 69 | end 70 | end 71 | 72 | 73 | function purify(paths::Vector{<:QChannel}, return_as_dB::Bool=true) 74 | cost_vectors = Vector{Dict}() 75 | for path in paths 76 | cost = get_pathcv(path) 77 | push!(cost_vectors, cost) 78 | end 79 | purify(cost_vectors, return_as_dB) 80 | end 81 | -------------------------------------------------------------------------------- /test/sandbox.jl: -------------------------------------------------------------------------------- 1 | using QuNet 2 | using Statistics 3 | 4 | grid_size = 100 5 | num_pairs = 50 6 | num_trials = 100 7 | max_paths = 4 8 | 9 | failure_counts = [] 10 | for i in 1:num_trials 11 | println("num_trial $i") 12 | edge_costs = Dict("loss"=>0.05, "Z"=>0.05) 13 | net = GridNetwork(grid_size, grid_size, edge_costs=edge_costs) 14 | user_pairs = QuNet.make_user_pairs(net, 50) 15 | pathset, pur_paths, pathuse_count = QuNet.greedy_multi_path!(net, QuNet.purify, user_pairs, 4) 16 | push!(failure_counts, pathuse_count[1]) 17 | end 18 | println(mean(failure_counts./num_pairs)) 19 | println(failure_counts) 20 | 21 | 22 | # # Set up a Python backend before importing PyPlot 23 | # using PyCall 24 | # # Specify user GUI from options (:tk, :gtk3, :gtk, :qt5, :qt4, :qt, or :wx) 25 | # # pygui(:qt) 26 | # using PyPlot 27 | # 28 | # # # Test PyPlot 29 | # # # use x = linspace(0,2*pi,1000) in Julia 0.6 30 | # # x = range(0; stop=2*pi, length=1000); y = sin.(3 * x + 4 * cos.(2 * x)); 31 | # # plot(x, y, color="red", linewidth=2.0, linestyle="--") 32 | # # title("A sinusoidally modulated sinusoid") 33 | # # # Display figure with Julia backend 34 | # # display(gcf()) 35 | # # # Save as a Pdf 36 | # # savefig("sinusoid_test.pdf") 37 | # # #show() 38 | # 39 | # # Try heatmap 40 | # # Install numpy 41 | # using PyCall 42 | # np = pyimport("numpy") 43 | # kde = pyimport("scipy.stats.kde") 44 | # data = np.random.multivariate_normal([0, 0], [[1, 0.5], [0.5, 3]], 200) 45 | # Tdata = transpose(data) 46 | # x = Tdata[1, :] 47 | # y = Tdata[2, :] 48 | # 49 | # # # Evaluate a gaussian kde on a regular grid of nbins x nbins over data extents 50 | # # k = kde.gaussian_kde(Tdata) 51 | # # xi, yi = np.mgrid[minimum(x):maximum(x):nbins*1j, minimum(y):maximum(y):nbins*1j] 52 | # # zi = k(np.vstack([xi.flatten(), yi.flatten()])) 53 | # 54 | # fig, axs = plt.subplots(ncols=2, nrows=1, figsize=(21, 5)) 55 | # # Everything starts with a Scatterplot 56 | # axs[0].set_title('Scatterplot') 57 | # axs[0].plot(x, y, 'ko') 58 | # 59 | # # # plot a density 60 | # # axes[1].set_title('Calculate Gaussian KDE') 61 | # # axes[1].pcolormesh(xi, yi, zi.reshape(xi.shape), shading='auto', cmap=plt.cm.BuGn_r) 62 | # # 63 | # display(gcf()) 64 | -------------------------------------------------------------------------------- /src/CostVector.jl: -------------------------------------------------------------------------------- 1 | """ 2 | function zero_costvector() 3 | 4 | Returns a dictionary of the default costs "loss" and "Z" both initialised to 0. 5 | """ 6 | function zero_costvector()::Dict 7 | costVector::Dict{String, Float64} = Dict([("loss",0.0), ("Z",0.0)]) 8 | return costVector 9 | end 10 | 11 | """ 12 | function unit_costvector() 13 | 14 | Returns a dictionary of the default costs "loss" and "Z" both initialised to 1. 15 | """ 16 | function unit_costvector()::Dict 17 | costVector::Dict{String, Float64} = Dict([("loss",1.0), ("Z",1.0)]) 18 | return costVector 19 | end 20 | 21 | 22 | """ 23 | Convert a cost vector to or from metric form 24 | """ 25 | function convert_costs(cost_vector::Dict, to_metric::Bool=true) 26 | if to_metric == true 27 | cost_vector["loss"] = dB_to_P(cost_vector["loss"]) 28 | cost_vector["Z"] = dB_to_Z(cost_vector["Z"]) 29 | else 30 | cost_vector["loss"] = P_to_dB(cost_vector["loss"]) 31 | cost_vector["Z"] = Z_to_dB(cost_vector["Z"]) 32 | end 33 | return cost_vector 34 | end 35 | 36 | 37 | """ 38 | function get_pathcv(path::Array{<:QChannel, 1}) 39 | 40 | Returns a dictionary of costs for a path in a QNetwork. Not to be confused with 41 | path_length(), which finds the scalar sum of weights for a path in an abstract graph. 42 | """ 43 | function get_pathcv(path::Vector{<:QChannel}) 44 | if length(path) == 0 45 | return zero_costvector() 46 | end 47 | cost_vector = Dict() 48 | for key in keys(zero_costvector()) 49 | cost = 0 50 | cost = path[1].src.costs[key] 51 | for edge in path 52 | # Node costs 53 | cost += edge.dest.costs[key] 54 | # Edge costs 55 | cost += edge.costs[key] 56 | end 57 | cost_vector[key] = cost 58 | end 59 | return cost_vector 60 | end 61 | 62 | function get_pathcv(path::QChannel) 63 | get_pathcv([path]) 64 | end 65 | 66 | function get_pathcv(network::QNetwork, path::Vector{Tuple{Int64, Int64}})::Dict{String, Float64} 67 | pathcost = Dict{String, Float64}() 68 | for cost_id in keys(zero_costvector()) 69 | weight = 0.0 70 | for edge in path 71 | src = edge[1] 72 | dst = edge[2] 73 | weight += network.graph[cost_id].weights[src, dst] 74 | end 75 | pathcost[cost_id] = weight 76 | end 77 | return pathcost 78 | end 79 | 80 | function get_pathcv(temp::QuNet.TemporalGraph, path::Vector{Tuple{Int64, Int64}})::Dict{String, Float64} 81 | pathcost = Dict{String, Float64}() 82 | for cost_id in keys(zero_costvector()) 83 | g = temp.graph[cost_id] 84 | pathcost[cost_id] = path_length(g, path) 85 | end 86 | return pathcost 87 | end 88 | 89 | function get_pathcv(network::Union{QNetwork, QuNet.TemporalGraph}, 90 | path::Vector{LightGraphs.SimpleGraphs.SimpleEdge{Int64}})::Dict{String, Float64} 91 | 92 | new_path = Vector{Tuple{Int64, Int64}}() 93 | for edge in path 94 | new_edge = (edge.src, edge.dst) 95 | push!(new_path, new_edge) 96 | end 97 | pathcost = get_pathcv(network, new_path) 98 | return pathcost 99 | end 100 | -------------------------------------------------------------------------------- /docs/build/assets/themeswap.js: -------------------------------------------------------------------------------- 1 | // Small function to quickly swap out themes. Gets put into the tag.. 2 | function set_theme_from_local_storage() { 3 | // Intialize the theme to null, which means default 4 | var theme = null; 5 | // If the browser supports the localstorage and is not disabled then try to get the 6 | // documenter theme 7 | if(window.localStorage != null) { 8 | // Get the user-picked theme from localStorage. May be `null`, which means the default 9 | // theme. 10 | theme = window.localStorage.getItem("documenter-theme"); 11 | } 12 | // Check if the browser supports user color preference 13 | var darkPreference = false; 14 | // Check if the users preference is for dark color scheme 15 | if(window.matchMedia('(prefers-color-scheme: dark)').matches === true) { 16 | darkPreference = true; 17 | } 18 | // Initialize a few variables for the loop: 19 | // 20 | // - active: will contain the index of the theme that should be active. Note that there 21 | // is no guarantee that localStorage contains sane values. If `active` stays `null` 22 | // we either could not find the theme or it is the default (primary) theme anyway. 23 | // Either way, we then need to stick to the primary theme. 24 | // 25 | // - disabled: style sheets that should be disabled (i.e. all the theme style sheets 26 | // that are not the currently active theme) 27 | var active = null; var disabled = []; var darkTheme = null; 28 | for (var i = 0; i < document.styleSheets.length; i++) { 29 | var ss = document.styleSheets[i]; 30 | // The tag of each style sheet is expected to have a data-theme-name attribute 31 | // which must contain the name of the theme. The names in localStorage much match this. 32 | var themename = ss.ownerNode.getAttribute("data-theme-name"); 33 | // attribute not set => non-theme stylesheet => ignore 34 | if(themename === null) continue; 35 | // To distinguish the default (primary) theme, it needs to have the data-theme-primary 36 | // attribute set. 37 | var isprimary = (ss.ownerNode.getAttribute("data-theme-primary") !== null); 38 | // Check if the theme is primary dark theme 39 | var isDarkTheme = (ss.ownerNode.getAttribute("data-theme-primary-dark") !== null); 40 | // If ss is for dark theme then set the value of darkTheme to the name of the theme 41 | if(isDarkTheme) darkTheme = themename; 42 | // If we find a matching theme (and it's not the default), we'll set active to non-null 43 | if(themename === theme) active = i; 44 | // Store the style sheets of inactive themes so that we could disable them 45 | if(themename !== theme) disabled.push(ss); 46 | } 47 | if(active !== null) { 48 | // If we did find an active theme, we'll (1) add the theme--$(theme) class to 49 | document.getElementsByTagName('html')[0].className = "theme--" + theme; 50 | // and (2) disable all the other theme stylesheets 51 | disabled.forEach(function(ss){ 52 | ss.disabled = true; 53 | }); 54 | } 55 | else if(darkTheme !== null && darkPreference === true) { 56 | // If we did find an active theme, we'll (1) add the theme--$(theme) class to 57 | document.getElementsByTagName('html')[0].className = "theme--" + darkTheme; 58 | // and (2) disable all the other theme stylesheets 59 | disabled.forEach(function(ss){ 60 | if (ss.ownerNode.getAttribute("data-theme-name") !== darkTheme) { 61 | ss.disabled = true; 62 | } 63 | }); 64 | } 65 | } 66 | set_theme_from_local_storage(); 67 | -------------------------------------------------------------------------------- /docs/build/search/index.html: -------------------------------------------------------------------------------- 1 | 2 | Search · QuNet

Loading search...

    3 | -------------------------------------------------------------------------------- /QuNet-Paper/SatPlotDemo.jl: -------------------------------------------------------------------------------- 1 | using QuNet 2 | using Plots 3 | 4 | function get_costs_with_time(network::QNetwork, tmax::Float64, path::Array{<:QChannel, 1}) 5 | # Initialise dictionary of costs over time 6 | costs = Dict() 7 | for key in keys(zero_costvector()) 8 | costs[key] = [] 9 | end 10 | 11 | # Main 12 | if network.time != Float64(0) 13 | update(network, Float64(0)) 14 | end 15 | while network.time < tmax 16 | pathcost = get_pathcv(path) 17 | for key in keys(zero_costvector()) 18 | push!(costs[key], pathcost[key]) 19 | end 20 | update(network) 21 | end 22 | 23 | # Reset network back to default time 24 | update(network, Float64(0)) 25 | 26 | # Return array of costs 27 | return [QuNet.dB_to_P.(costs["loss"]), QuNet.dB_to_Z.(costs["Z"])] 28 | end 29 | 30 | """ 31 | This function takes a list of paths, purifies them and returns the costs over 32 | time. 33 | """ 34 | function purify_costs_with_time(network::QNetwork, tmax::Float64, 35 | paths::Vector{Vector}) 36 | 37 | loss_arr = [] 38 | Z_arr = [] 39 | 40 | if network.time != Float64(0) 41 | update(network, Float64(0)) 42 | end 43 | 44 | while network.time < tmax 45 | path_costs = Array{Dict{Any, Any}, 1}() 46 | for path in paths 47 | path_cost = get_pathcv(path) 48 | push!(path_costs, path_cost) 49 | end 50 | 51 | # Purify path costs together, add to respective arrays 52 | cost_vector = purify(path_costs, false) 53 | push!(loss_arr, cost_vector["loss"]) 54 | push!(Z_arr, cost_vector["Z"]) 55 | 56 | # Update Network 57 | update(Q) 58 | end 59 | return loss_arr, Z_arr 60 | end 61 | 62 | function best_classical_z(cost1, cost2) 63 | best_arr = [] 64 | i = 1 65 | maxint = length(cost1) 66 | while i <= maxint 67 | if cost1[i] > cost2[i] 68 | push!(best_arr, cost1[i]) 69 | else 70 | push!(best_arr, cost2[i]) 71 | end 72 | i += 1 73 | end 74 | return best_arr 75 | end 76 | 77 | 78 | # Main 79 | Q = QNetwork() 80 | A = BasicNode("A") 81 | B = BasicNode("B") 82 | S = PlanSatNode("S") 83 | 84 | B.location = Coords(500, 0, 0) 85 | S.location = Coords(-2000,0,1000) 86 | S.velocity = Velocity(1000, 0) 87 | 88 | AB = BasicChannel(A, B, exp_cost=true) 89 | AS = QuNet.AirChannel(A, S) 90 | SB = QuNet.AirChannel(S, B) 91 | 92 | for i in [A, S, AB, AS, SB] 93 | add(Q, i) 94 | end 95 | 96 | # Set time, get time array 97 | tmax = 10.0 98 | times = collect(0:QuNet.TIME_STEP:tmax) 99 | 100 | # Collect data 101 | c1 = get_costs_with_time(Q, tmax, [AS, SB]) 102 | c2 = get_costs_with_time(Q, tmax, [AB]) 103 | 104 | # Convert probabilities to loss 105 | c1[1] = 1 .- c1[1] 106 | c2[1] = 1 .- c2[1] 107 | 108 | # function purify(cost_vectors::Array{Dict{String,Float64}, 1}) 109 | pur_loss, pur_z = purify_costs_with_time(Q, tmax, [[AS, SB], [AB]]) 110 | pur_loss = 1 .- pur_loss 111 | 112 | # Get best classical cost 113 | best_arr = best_classical_z(c1[2], c2[2]) 114 | 115 | # Plot data 116 | plot(times, c1, title="Costs of Satellite Network over Time", 117 | label=["A-S-B (loss)" "A-S-B (Z)"], linewidth=1.5, ylims=(0.0,1.0)) 118 | plot!(times, c2, label=["A-B (loss)" "A-B (Z)"], linewidth=1.5) 119 | 120 | # Plot purified data 121 | plot!(times, pur_loss, label="pur_loss", linewidth = 1.5) 122 | plot!(times, pur_z, label="pur_z", linewidth = 1.5) 123 | 124 | # Plot extraneous things 125 | plot!(times, best_arr, label="best classical", style=:dash, lc="black", linewidth = 1.5) 126 | xlabel!("Time (seconds)") 127 | ylabel!("Network Costs") 128 | -------------------------------------------------------------------------------- /src/Node.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Coordinates of the QNode object in up to three spatial dimensions 3 | """ 4 | mutable struct Coords 5 | x::Float64 6 | y::Float64 7 | z::Float64 8 | 9 | Coords() = new(0,0,0) 10 | Coords(x,y) = new(x,y,0) 11 | Coords(x,y,z) = new(x,y,z) 12 | end 13 | 14 | """ 15 | The default QNode object. Nothing special, but nothing unspecial either ;-)" 16 | """ 17 | mutable struct BasicNode <: QNode 18 | name::String 19 | costs::Dict 20 | has_memory::Bool 21 | memory_costs::Dict 22 | id::Int64 23 | time::Int64 24 | active::Bool 25 | location::Coords 26 | 27 | BasicNode() = new("", zero_costvector(), true, zero_costvector(), 0, 0, true, Coords()) 28 | BasicNode(name) = new(string(name), zero_costvector(), true, zero_costvector(), 0, 0, true, Coords()) 29 | end 30 | 31 | """ 32 | Add a QNode object to the network. 33 | 34 | Example: 35 | 36 | ``` 37 | using QuNet 38 | Q = QNetwork() 39 | A = BasicNode("A") 40 | add(Q, A) 41 | ``` 42 | """ 43 | function add(network::QNetwork, node::QNode) 44 | push!(network.nodes, node) 45 | node.id = length(network.nodes) 46 | end 47 | 48 | """ 49 | Remove a Qnode object from the network. 50 | 51 | The convention for node removal in QuNet echos that of LightGraphs.jl. 52 | Suppose a given network has N nodes, and we want to remove the node with the 53 | id v: 54 | 55 | 1. Check if v < N . If false, simply pop the node from QNetwork.nodes 56 | 2. Else, swap the nodes v and N, then pop from QNetworks.nodes 57 | """ 58 | function remove(network::QNetwork, node::QNode) 59 | node_id = node.id 60 | if node_id != length(network.nodes) 61 | # Swap the node to be removed with the last node 62 | # (Same removal strategy as SimpleGraphs) 63 | tmp_node = deepcopy(node) 64 | N = last(network.nodes) 65 | node = N 66 | node.id = node_id 67 | N = tmp_node 68 | end 69 | pop!(network.nodes) 70 | end 71 | 72 | """ 73 | ```function update(node::QNode)``` 74 | 75 | Does nothing 76 | """ 77 | function update(node::QNode) 78 | end 79 | 80 | function update(node::QNode, old_time::Float64, new_time::Float64) 81 | end 82 | 83 | 84 | # Identical structure to Coords, but using a different name for distrinction 85 | """ 86 | ``` 87 | mutable struct Velocity 88 | x::Float64 89 | y::Float64 90 | z::Float64 91 | 92 | Velocity() = new(0,0,0) 93 | Velocity(x,y) = new(x,y,0) 94 | Velocity(x,y,z) = new(x,y,z) 95 | ``` 96 | 97 | Type for Cartesian Velocity in up to 3 spatial coordinates 98 | """ 99 | mutable struct Velocity 100 | x::Float64 101 | y::Float64 102 | z::Float64 103 | 104 | Velocity() = new(0,0,0) 105 | Velocity(x,y) = new(x,y,0) 106 | Velocity(x,y,z) = new(x,y,z) 107 | end 108 | 109 | """ 110 | ``` 111 | mutable struct PlanSatNode <: QNode 112 | name::String 113 | costs::Dict 114 | memory::Dict 115 | id::Int64 116 | time::Int64 117 | active::Bool 118 | location::Coords 119 | velocity::Velocity 120 | ``` 121 | 122 | The `PlanSatNode` type has all the functionality of a `BasicNode` but can move 123 | according to a fixed velocity. 124 | """ 125 | mutable struct PlanSatNode <: QNode 126 | name::String 127 | costs::Dict 128 | has_memory::Bool 129 | memory_costs::Dict 130 | id::Int64 131 | time::Int64 132 | active::Bool 133 | location::Coords 134 | velocity::Velocity 135 | PlanSatNode() = new("", zero_costvector(), true, unit_costvector(), 136 | 0, 0, true, Coords(), Velocity()) 137 | PlanSatNode(name) = new(string(name), zero_costvector(), true, zero_costvector(), 138 | 0, 0, true, Coords(), Velocity()) 139 | end 140 | 141 | """ 142 | ```function update(sat::PlanSatNode)``` 143 | 144 | Update the position of a planar satellite node by incrementing its current 145 | location with the distance covered by its velocity in `TIME_STEP` seconds. 146 | """ 147 | function update(sat::PlanSatNode, old_time::Float64, new_time::Float64) 148 | # Calculate new position from velocity 149 | sat.location.x += sat.velocity.x * (new_time - old_time) 150 | sat.location.y += sat.velocity.y * (new_time - old_time) 151 | sat.location.z += sat.velocity.z * (new_time - old_time) 152 | return 153 | end 154 | -------------------------------------------------------------------------------- /src/Channel.jl: -------------------------------------------------------------------------------- 1 | """ 2 | add(network::QNetwork, channel::QChannel) 3 | 4 | Add a channel to the network 5 | """ 6 | function add(network::QNetwork, channel::QChannel) 7 | push!(network.channels, channel) 8 | end 9 | 10 | function update(channel::QChannel, old_time::Float64, new_time::Float64) 11 | end 12 | 13 | 14 | """ 15 | distance(src::QNode, dest::QNode) 16 | 17 | Cartesian distance between two nodes 18 | """ 19 | function distance(src::QNode, dest::QNode) 20 | v = src.location 21 | w = dest.location 22 | return sqrt((v.x - w.x)^2 + (v.y - w.y)^2 + (v.z - w.z)^2) 23 | end 24 | 25 | 26 | """ 27 | mutable struct BasicChannel <: QChannel 28 | 29 | The default Channel type. Costs are assumed to be exponential with length 30 | """ 31 | mutable struct BasicChannel <: QChannel 32 | name::String 33 | costs::Dict 34 | src::QNode 35 | dest::QNode 36 | length::Float64 37 | active::Bool 38 | directed::Bool 39 | 40 | """ 41 | Initalise a Basic Channel with unit costs 42 | """ 43 | function BasicChannel(src::QNode, dest::QNode, ;exp_cost::Bool=false) 44 | tmpchannel = new("", unit_costvector(), src, dest, distance(src, dest), true, false) 45 | if exp_cost == true 46 | tmpchannel.costs = cost(tmpchannel) 47 | end 48 | return tmpchannel 49 | end 50 | 51 | function BasicChannel(name::String, src::QNode, dest::QNode, exp_cost::Bool=false) 52 | tmpchannel = new(name, unit_costvector(), src, dest, distance(src, dest), true, false) 53 | if exp_cost == true 54 | tmpchannel.costs = cost(tmpchannel) 55 | end 56 | return tmpchannel 57 | end 58 | 59 | function BasicChannel(name::String, src::QNode, dest::QNode, edge_cost::Dict) 60 | tmpchannel = new(name, edge_cost, src, dest, distance(src, dest), true, false) 61 | return tmpchannel 62 | end 63 | 64 | #BasicChannel(src::QNode, dest::QNode) = new("", unit_costvector(), src, dest, distance(src, dest), true, false) 65 | #BasicChannel(name::String, src::QNode, dest::QNode) = new(string(name), unit_costvector(), src, dest, distance(src, dest), true, false) 66 | end 67 | 68 | function cost(channel::BasicChannel) 69 | d = channel.length 70 | β = 0.001 71 | loss = P_to_dB(exp(-β * d)) 72 | Z = Z_to_dB((1 + exp(-β * d))/2) 73 | costVector = Dict([("loss",loss), ("Z",Z)]) 74 | return costVector 75 | end 76 | 77 | mutable struct AirChannel <: QChannel 78 | name::String 79 | costs::Dict 80 | src::QNode 81 | dest::QNode 82 | length::Float64 83 | active::Bool 84 | directed::Bool 85 | 86 | function AirChannel(src::QNode, dest::QNode) 87 | tmpchannel = new("", unit_costvector(), src, dest, distance(src, dest), true, false) 88 | tmpchannel.costs = cost(tmpchannel) 89 | return tmpchannel 90 | end 91 | 92 | function AirChannel(name::String, src::QNode, dest::QNode) 93 | tmpchannel = new(name, unit_costvector(), src, dest, distance(src, dest), true, false) 94 | tmpchannel.costs = cost(tmpchannel) 95 | return tmpchannel 96 | end 97 | end 98 | 99 | function cost(channel::AirChannel) 100 | """ 101 | Line integral for effective density 102 | 103 | / L' 104 | | rho(x * sin(theta)) dx 105 | / 0 106 | """ 107 | 108 | v = channel.src.location 109 | w = channel.dest.location 110 | L = channel.length 111 | # sin(theta) 112 | st = abs(v.z - w.z)/L 113 | # atmosphere function 114 | ρ = expatmosphere 115 | f(x) = ρ(x*st) 116 | # Effective atmospheric depth 117 | d = quadgk(f, 0, L)[1] 118 | 119 | β = 10e-5 120 | d₀ = 10e7 121 | 122 | # Calculate the decibelic forms of loss and Z 123 | P = exp(-β*d)*(d₀)^2/(d + d₀)^2 124 | Z = (1+exp(-β*d))/2 125 | 126 | # Put them in a cost vector and return 127 | costVector = Dict([("loss",P_to_dB(P)), ("Z",Z_to_dB(Z))]) 128 | return costVector 129 | end 130 | 131 | 132 | function update(channel::AirChannel, old_time::Float64, new_time::Float64) 133 | channel.length = distance(channel.src, channel.dest) 134 | channel.costs = cost(channel) 135 | end 136 | 137 | # BasicMemory(node::QNode) = new(node, unit_costvector()) 138 | # BasicMemory(node::QNode, costs::Dict) = new(node, costs) 139 | # end 140 | -------------------------------------------------------------------------------- /src/scrap.jl: -------------------------------------------------------------------------------- 1 | """ 2 | A file containing code that was recently rendered obsolete. Identical in function 3 | to a recycling bin. 4 | """ 5 | 6 | # Taken from MultiPathDemo.jl 7 | """ 8 | Return average average value for an array of cost vectors. 9 | If the length of the input is less than 1, mean is not well defined, 10 | the key values returned are 'nothing'. 11 | """ 12 | function dict_average(dict_list) 13 | avcosts = zero_costvector() 14 | 15 | if length(dict_list) == 0 16 | for cost_type in keys(avcosts) 17 | avcosts[cost_type] = NaN 18 | end 19 | return avcosts 20 | end 21 | 22 | for cost_type in keys(avcosts) 23 | costs = collect(map(x->x[cost_type], dict_list)) 24 | avcosts[cost_type] = mean(costs) 25 | end 26 | return avcosts 27 | end 28 | 29 | """ 30 | Return average standard error for an array of cost vectors. 31 | If the length of the input is less than 2, error is not well defined, 32 | the key values returned are 'nothing'. 33 | """ 34 | function dict_err(dict_list) 35 | averr = zero_costvector() 36 | len = length(dict_list) 37 | 38 | if len < 2 39 | for cost_type in keys(averr) 40 | averr[cost_type] = NaN 41 | end 42 | return averr 43 | end 44 | 45 | for cost_type in keys(averr) 46 | costs = collect(map(x->x[cost_type], dict_list)) 47 | averr[cost_type] = std(costs)/(sqrt(length(costs))) 48 | end 49 | return averr 50 | end 51 | 52 | """Generate a list of user_pairs for a QNetwork""" 53 | function make_user_pairs(QNetwork, num_pairs) 54 | num_nodes = length(QNetwork.nodes) 55 | @assert num_nodes >= num_pairs*2 "Graph space too small for number of pairs" 56 | rand_space = Array(collect(1:num_nodes)) 57 | pairs = Vector{Tuple}() 58 | i = 0 59 | while i < num_pairs 60 | idx = rand(1:length(rand_space)) 61 | u = rand_space[idx] 62 | deleteat!(rand_space, idx) 63 | idx = rand(1:length(rand_space)) 64 | v = rand_space[idx] 65 | deleteat!(rand_space, idx) 66 | chosen_pair = (u, v) 67 | push!(pairs, chosen_pair) 68 | i += 1 69 | end 70 | return pairs 71 | end 72 | 73 | 74 | """ 75 | Takes a network as input and return greedy_multi_path! performance statistics for some number of 76 | random user pairs. 77 | """ 78 | function net_performance(network::QNetwork, num_trials::Int64, num_pairs::Int64, 79 | with_err::Bool=false; max_paths=3) 80 | 81 | total_collisions = 0 82 | pfmnce_data = [] 83 | path_data = [] 84 | 85 | for i in 1:num_trials 86 | net = deepcopy(network) 87 | 88 | # No need to refresh graph here. GridNetwork is already ready to go 89 | #refresh_graph!(net) 90 | 91 | # Generate random communication pairs 92 | user_pairs = make_user_pairs(network, num_pairs) 93 | # NOTE Added max_paths here. Check me first if something goes wrong. 94 | net_data, collisions, ave_paths_used = QuNet.greedy_multi_path!(net, purify, user_pairs, max_paths) 95 | total_collisions += collisions 96 | push!(path_data, ave_paths_used) 97 | 98 | # If net_data contains nothing, 99 | filter!(x->x!=nothing, net_data) 100 | 101 | # Mean well defined only if data set > 0 102 | if length(net_data) > 0 103 | # Average the data 104 | ave = dict_average(net_data) 105 | push!(pfmnce_data, ave) 106 | end 107 | end 108 | 109 | if with_err == true 110 | # Performance data and error 111 | pfmnce_err = dict_err(pfmnce_data) 112 | pfmnce_data = dict_average(pfmnce_data) 113 | # Path data and error 114 | path_err = std(path_data) 115 | path_data = mean(path_data) 116 | return pfmnce_data, pfmnce_err, total_collisions, path_data, path_err 117 | end 118 | 119 | # Average path_data 120 | path_data = mean(path_data) 121 | pfmnce_data = dict_average(pfmnce_data) 122 | return pfmnce_data, total_collisions, path_data 123 | end 124 | 125 | 126 | function net_performance(tempgraph::QuNet.TemporalGraph, num_trials::Int64, 127 | user_pairs::Vector{Tuple}, with_err::Bool=false) 128 | 129 | total_collisions = 0 130 | pfmnce_data = [] 131 | 132 | for i in 1:num_trials 133 | # Copying network seems like it converts it to QNetwork? Test this 134 | net = deepcopy(tempgraph) 135 | 136 | net_data, collisions = QuNet.greedy_multi_path!(net, purify, user_pairs) 137 | total_collisions += collisions 138 | 139 | # If net_data contains nothing, 140 | filter!(x->x!=nothing, net_data) 141 | 142 | # Mean well defined only if data set > 0 143 | if length(net_data) > 0 144 | # Average the data 145 | ave = dict_average(net_data) 146 | push!(pfmnce_data, ave) 147 | end 148 | end 149 | 150 | if with_err == true 151 | # Standard error well defined only if sample size greater than 1 152 | pfmnce_err = dict_err(pfmnce_data) 153 | pfmnce_data = dict_average(pfmnce_data) 154 | return pfmnce_data, pfmnce_err, total_collisions 155 | end 156 | 157 | pfmnce_data = dict_average(pfmnce_data) 158 | return pfmnce_data, total_collisions 159 | end 160 | -------------------------------------------------------------------------------- /src/Network.jl: -------------------------------------------------------------------------------- 1 | """ 2 | The QNetwork type is a mutable structure that contains QNodes, QChannels, and 3 | a dictionary of costs to weighted LightGraphs. 4 | """ 5 | mutable struct QNetwork <: QObject 6 | name::String 7 | nodes::Array{QNode} 8 | channels::Array{QChannel} 9 | # Dictionary between costs and corresponding LightGraphs weighted graphs. 10 | graph::Dict 11 | time::Float64 12 | 13 | # QNetwork() = new("QuNet", [], [], Dict()) 14 | function QNetwork() 15 | network = new("QuNet", [], [], Dict(), 0.0) 16 | for cost_key in keys(zero_costvector()) 17 | network.graph[cost_key] = SimpleWeightedDiGraph() 18 | end 19 | return network 20 | end 21 | end 22 | 23 | function QNetwork(graph::AbstractGraph; edge_costs=unit_costvector()) 24 | network = QNetwork() 25 | 26 | vertexCount = 0 27 | for vertex in vertices(graph) 28 | vertexCount += 1 29 | add(network, BasicNode(string(vertexCount))) 30 | end 31 | 32 | edgeCount = 0 33 | for edge in edges(graph) 34 | edgeCount += 1 35 | add(network, BasicChannel(string(edgeCount), network.nodes[edge.src], network.nodes[edge.dst], 36 | edge_costs)) 37 | end 38 | 39 | return network 40 | end 41 | 42 | function print(network::QNetwork) 43 | println("name: ", network.name) 44 | println("nodes: ", length(network.nodes)) 45 | println("channels: ", length(network.channels)) 46 | end 47 | 48 | # NOTE Outdated refresh_graph! below. 49 | # We'll no longer use TemporalGraph structure to update graphs since it has 50 | # A lot more bells and whistles now. Good practice to have something custom built. 51 | # function refresh_graph!(network::QNetwork) 52 | # network.graph = TemporalGraph(network, 1).graph 53 | # end 54 | 55 | """ 56 | refresh_graph!(network::QNetwork) 57 | 58 | Converts a QNetwork into several weighted LightGraphs (one 59 | graph for each associated cost), then updates the QNetwork.graph attribute 60 | with these new graphs. 61 | """ 62 | function refresh_graph!(network::QNetwork) 63 | 64 | refreshed_graphs = Dict{String, SimpleWeightedDiGraph}() 65 | 66 | for cost_key in keys(zero_costvector()) 67 | refreshed_graphs[cost_key] = SimpleWeightedDiGraph() 68 | 69 | # Vertices 70 | add_vertices!(refreshed_graphs[cost_key], length(network.nodes)) 71 | 72 | # Channels 73 | for channel in network.channels 74 | if channel.active == true 75 | src = findfirst(x -> x == channel.src, network.nodes) 76 | dest = findfirst(x -> x == channel.dest, network.nodes) 77 | weight = channel.costs[cost_key] 78 | add_edge!(refreshed_graphs[cost_key], src, dest, weight) 79 | add_edge!(refreshed_graphs[cost_key], dest, src, weight) 80 | end 81 | end 82 | end 83 | network.graph = refreshed_graphs 84 | end 85 | 86 | """ 87 | GridNetwork(dim::Int64, dimY::Int64) 88 | 89 | Generates an X by Y grid network. 90 | """ 91 | function GridNetwork(dimX::Int64, dimY::Int64; edge_costs::Dict = unit_costvector()) 92 | graph = LightGraphs.grid([dimX,dimY]) 93 | net = QNetwork(graph; edge_costs=edge_costs) 94 | 95 | for x in 1:dimX 96 | for y in 1:dimY 97 | this = x + (y-1)*dimX 98 | net.nodes[this].location = Coords(x,y) 99 | end 100 | end 101 | 102 | refresh_graph!(net) 103 | return net 104 | end 105 | 106 | """ 107 | ```function update(network::QNetwork, new_time::Float64)``` 108 | 109 | The `update` function iterates through all objects in the network and updates 110 | them according to a new global time. 111 | """ 112 | function update(network::QNetwork, new_time::Float64) 113 | old_time = network.time 114 | for node in network.nodes 115 | update(node, old_time, new_time) 116 | end 117 | 118 | for channel in network.channels 119 | update(channel, old_time, new_time) 120 | end 121 | network.time = new_time 122 | end 123 | 124 | """ 125 | ```function update(network::QNetwork)``` 126 | 127 | This instance of update iterates through all objects in the network and updates 128 | them by the global time increment TIME_STEP defined in QuNet.jl 129 | """ 130 | function update(network::QNetwork) 131 | old_time = network.time 132 | new_time = old_time + TIME_STEP 133 | for node in network.nodes 134 | update(node, old_time, new_time) 135 | end 136 | 137 | for channel in network.channels 138 | update(channel, old_time, new_time) 139 | end 140 | network.time = new_time 141 | end 142 | 143 | 144 | """ 145 | getnode(network::QNetwork, id::Int64) 146 | 147 | Fetch the node object corresponding to the given ID / Name 148 | """ 149 | function getnode(network::QNetwork, id::Int64) 150 | return network.nodes[id] 151 | end 152 | 153 | 154 | function getnode(network::QNetwork, name::String) 155 | for node in network.nodes 156 | if node.name == name 157 | return node 158 | end 159 | end 160 | end 161 | 162 | 163 | function getchannel(network::QNetwork, src::Union{Int64, String}, 164 | dst::Union{Int64, String}) 165 | src = getnode(network, src) 166 | dst = getnode(network, dst) 167 | for channel in network.channels 168 | if channel.src == src && channel.dest == dst 169 | return channel 170 | end 171 | end 172 | end 173 | 174 | """ 175 | function add_qnode!(network::QNetwork; nodename::String="", nodetype::DataType=BasicNode) 176 | @assert nodetype in subtypes(QNode) 177 | new_node = nodetype(nodename) 178 | add(network, new_node) 179 | end 180 | """ 181 | 182 | """ 183 | function add_channel!(network::QNetwork, src::Union{Int64, String}, 184 | dst::Union{Int64, String}; 185 | name::string="", type=BasicChannel) 186 | src = getnode(src) 187 | dst = getnode(dst) 188 | @assert type in [BasicChannel, AirChannel] 189 | new_channel = type(name, src, dst) 190 | add(network, new_channel) 191 | end 192 | """ 193 | -------------------------------------------------------------------------------- /docs/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | QuNet · QuNet

    QuNet

    QuNet.BasicNodeType

    The default QNode object. Nothing special, but nothing unspecial either ;-)"

    source
    QuNet.CoordsType

    Coordinates of the QNode object in two or three spatial dimensions

    source
    QuNet.addMethod

    Add a QNode object to the network.

    Example:
    3 | ```
    4 | using QuNet
    5 | Q = QNetwork()
    6 | A = BasicNode("A")
    7 | add(Q, A)
    8 | ```
    source
    QuNet.percolate_verticesMethod
    percolate_vertices(graph, p, exclude)

    Perform vertex percolation on graph with probability p, excluding specified vertices.

    source
    QuNet.removeMethod

    Remove a Qnode object from the network.

    The convention for removal echos that of LightGraphs.jl. Suppose a given network has N nodes, and we want to remove the node with the id v.

    1. Check if v < N . If false, simply pop the node from QNetwork.nodes
    2. Else, swap the nodes v and N, then pop from QNetworks.nodes
    source
    9 | -------------------------------------------------------------------------------- /docs/build/assets/search.js: -------------------------------------------------------------------------------- 1 | // Generated by Documenter.jl 2 | requirejs.config({ 3 | paths: { 4 | 'lunr': 'https://cdnjs.cloudflare.com/ajax/libs/lunr.js/2.3.6/lunr.min', 5 | 'lodash': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min', 6 | 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min', 7 | } 8 | }); 9 | //////////////////////////////////////////////////////////////////////////////// 10 | require(['jquery', 'lunr', 'lodash'], function($, lunr, _) { 11 | 12 | $(document).ready(function() { 13 | // parseUri 1.2.2 14 | // (c) Steven Levithan 15 | // MIT License 16 | function parseUri (str) { 17 | var o = parseUri.options, 18 | m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), 19 | uri = {}, 20 | i = 14; 21 | 22 | while (i--) uri[o.key[i]] = m[i] || ""; 23 | 24 | uri[o.q.name] = {}; 25 | uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { 26 | if ($1) uri[o.q.name][$1] = $2; 27 | }); 28 | 29 | return uri; 30 | }; 31 | parseUri.options = { 32 | strictMode: false, 33 | key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], 34 | q: { 35 | name: "queryKey", 36 | parser: /(?:^|&)([^&=]*)=?([^&]*)/g 37 | }, 38 | parser: { 39 | strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, 40 | loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ 41 | } 42 | }; 43 | 44 | $("#search-form").submit(function(e) { 45 | e.preventDefault() 46 | }) 47 | 48 | // list below is the lunr 2.1.3 list minus the intersect with names(Base) 49 | // (all, any, get, in, is, only, which) and (do, else, for, let, where, while, with) 50 | // ideally we'd just filter the original list but it's not available as a variable 51 | lunr.stopWordFilter = lunr.generateStopWordFilter([ 52 | 'a', 53 | 'able', 54 | 'about', 55 | 'across', 56 | 'after', 57 | 'almost', 58 | 'also', 59 | 'am', 60 | 'among', 61 | 'an', 62 | 'and', 63 | 'are', 64 | 'as', 65 | 'at', 66 | 'be', 67 | 'because', 68 | 'been', 69 | 'but', 70 | 'by', 71 | 'can', 72 | 'cannot', 73 | 'could', 74 | 'dear', 75 | 'did', 76 | 'does', 77 | 'either', 78 | 'ever', 79 | 'every', 80 | 'from', 81 | 'got', 82 | 'had', 83 | 'has', 84 | 'have', 85 | 'he', 86 | 'her', 87 | 'hers', 88 | 'him', 89 | 'his', 90 | 'how', 91 | 'however', 92 | 'i', 93 | 'if', 94 | 'into', 95 | 'it', 96 | 'its', 97 | 'just', 98 | 'least', 99 | 'like', 100 | 'likely', 101 | 'may', 102 | 'me', 103 | 'might', 104 | 'most', 105 | 'must', 106 | 'my', 107 | 'neither', 108 | 'no', 109 | 'nor', 110 | 'not', 111 | 'of', 112 | 'off', 113 | 'often', 114 | 'on', 115 | 'or', 116 | 'other', 117 | 'our', 118 | 'own', 119 | 'rather', 120 | 'said', 121 | 'say', 122 | 'says', 123 | 'she', 124 | 'should', 125 | 'since', 126 | 'so', 127 | 'some', 128 | 'than', 129 | 'that', 130 | 'the', 131 | 'their', 132 | 'them', 133 | 'then', 134 | 'there', 135 | 'these', 136 | 'they', 137 | 'this', 138 | 'tis', 139 | 'to', 140 | 'too', 141 | 'twas', 142 | 'us', 143 | 'wants', 144 | 'was', 145 | 'we', 146 | 'were', 147 | 'what', 148 | 'when', 149 | 'who', 150 | 'whom', 151 | 'why', 152 | 'will', 153 | 'would', 154 | 'yet', 155 | 'you', 156 | 'your' 157 | ]) 158 | 159 | // add . as a separator, because otherwise "title": "Documenter.Anchors.add!" 160 | // would not find anything if searching for "add!", only for the entire qualification 161 | lunr.tokenizer.separator = /[\s\-\.]+/ 162 | 163 | // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names 164 | lunr.trimmer = function (token) { 165 | return token.update(function (s) { 166 | return s.replace(/^[^a-zA-Z0-9@!]+/, '').replace(/[^a-zA-Z0-9@!]+$/, '') 167 | }) 168 | } 169 | 170 | lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'juliaStopWordFilter') 171 | lunr.Pipeline.registerFunction(lunr.trimmer, 'juliaTrimmer') 172 | 173 | var index = lunr(function () { 174 | this.ref('location') 175 | this.field('title',{boost: 100}) 176 | this.field('text') 177 | documenterSearchIndex['docs'].forEach(function(e) { 178 | this.add(e) 179 | }, this) 180 | }) 181 | var store = {} 182 | 183 | documenterSearchIndex['docs'].forEach(function(e) { 184 | store[e.location] = {title: e.title, category: e.category, page: e.page} 185 | }) 186 | 187 | $(function(){ 188 | searchresults = $('#documenter-search-results'); 189 | searchinfo = $('#documenter-search-info'); 190 | searchbox = $('#documenter-search-query'); 191 | function update_search(querystring) { 192 | tokens = lunr.tokenizer(querystring) 193 | results = index.query(function (q) { 194 | tokens.forEach(function (t) { 195 | q.term(t.toString(), { 196 | fields: ["title"], 197 | boost: 100, 198 | usePipeline: true, 199 | editDistance: 0, 200 | wildcard: lunr.Query.wildcard.NONE 201 | }) 202 | q.term(t.toString(), { 203 | fields: ["title"], 204 | boost: 10, 205 | usePipeline: true, 206 | editDistance: 2, 207 | wildcard: lunr.Query.wildcard.NONE 208 | }) 209 | q.term(t.toString(), { 210 | fields: ["text"], 211 | boost: 1, 212 | usePipeline: true, 213 | editDistance: 0, 214 | wildcard: lunr.Query.wildcard.NONE 215 | }) 216 | }) 217 | }) 218 | searchinfo.text("Number of results: " + results.length) 219 | searchresults.empty() 220 | results.forEach(function(result) { 221 | data = store[result.ref] 222 | link = $(''+data.title+'') 223 | link.attr('href', documenterBaseURL+'/'+result.ref) 224 | if (data.category != "page"){ 225 | cat = $('('+data.category+', '+data.page+')') 226 | } else { 227 | cat = $('('+data.category+')') 228 | } 229 | li = $('
  • ').append(link).append(" ").append(cat) 230 | searchresults.append(li) 231 | }) 232 | } 233 | 234 | function update_search_box() { 235 | querystring = searchbox.val() 236 | update_search(querystring) 237 | } 238 | 239 | searchbox.keyup(_.debounce(update_search_box, 250)) 240 | searchbox.change(update_search_box) 241 | 242 | search_query_uri = parseUri(window.location).queryKey["q"] 243 | if(search_query_uri !== undefined) { 244 | search_query = decodeURIComponent(search_query_uri.replace(/\+/g, '%20')) 245 | searchbox.val(search_query) 246 | } 247 | update_search_box(); 248 | }) 249 | }) 250 | 251 | }) 252 | -------------------------------------------------------------------------------- /QuNet-Paper/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 1616372552285 76 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /docs/build/assets/documenter.js: -------------------------------------------------------------------------------- 1 | // Generated by Documenter.jl 2 | requirejs.config({ 3 | paths: { 4 | 'highlight-julia': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/julia.min', 5 | 'headroom': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.10.3/headroom.min', 6 | 'jqueryui': 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min', 7 | 'katex-auto-render': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/contrib/auto-render.min', 8 | 'jquery': 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min', 9 | 'headroom-jquery': 'https://cdnjs.cloudflare.com/ajax/libs/headroom/0.10.3/jQuery.headroom.min', 10 | 'katex': 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min', 11 | 'highlight': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/highlight.min', 12 | 'highlight-julia-repl': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.10/languages/julia-repl.min', 13 | }, 14 | shim: { 15 | "highlight-julia": { 16 | "deps": [ 17 | "highlight" 18 | ] 19 | }, 20 | "katex-auto-render": { 21 | "deps": [ 22 | "katex" 23 | ] 24 | }, 25 | "headroom-jquery": { 26 | "deps": [ 27 | "jquery", 28 | "headroom" 29 | ] 30 | }, 31 | "highlight-julia-repl": { 32 | "deps": [ 33 | "highlight" 34 | ] 35 | } 36 | } 37 | }); 38 | //////////////////////////////////////////////////////////////////////////////// 39 | require(['jquery', 'katex', 'katex-auto-render'], function($, katex, renderMathInElement) { 40 | $(document).ready(function() { 41 | renderMathInElement( 42 | document.body, 43 | { 44 | "delimiters": [ 45 | { 46 | "left": "$", 47 | "right": "$", 48 | "display": false 49 | }, 50 | { 51 | "left": "$$", 52 | "right": "$$", 53 | "display": true 54 | }, 55 | { 56 | "left": "\\[", 57 | "right": "\\]", 58 | "display": true 59 | } 60 | ] 61 | } 62 | 63 | ); 64 | }) 65 | 66 | }) 67 | //////////////////////////////////////////////////////////////////////////////// 68 | require(['jquery', 'highlight', 'highlight-julia', 'highlight-julia-repl'], function($, hljs) { 69 | $(document).ready(function() { 70 | hljs.initHighlighting(); 71 | }) 72 | 73 | }) 74 | //////////////////////////////////////////////////////////////////////////////// 75 | require(['jquery', 'headroom', 'headroom-jquery'], function($, Headroom) { 76 | 77 | // Manages the top navigation bar (hides it when the user starts scrolling down on the 78 | // mobile). 79 | window.Headroom = Headroom; // work around buggy module loading? 80 | $(document).ready(function() { 81 | $('#documenter .docs-navbar').headroom({ 82 | "tolerance": {"up": 10, "down": 10}, 83 | }); 84 | }) 85 | 86 | }) 87 | //////////////////////////////////////////////////////////////////////////////// 88 | require(['jquery'], function($) { 89 | 90 | // Modal settings dialog 91 | $(document).ready(function() { 92 | var settings = $('#documenter-settings'); 93 | $('#documenter-settings-button').click(function(){ 94 | settings.toggleClass('is-active'); 95 | }); 96 | // Close the dialog if X is clicked 97 | $('#documenter-settings button.delete').click(function(){ 98 | settings.removeClass('is-active'); 99 | }); 100 | // Close dialog if ESC is pressed 101 | $(document).keyup(function(e) { 102 | if (e.keyCode == 27) settings.removeClass('is-active'); 103 | }); 104 | }); 105 | 106 | }) 107 | //////////////////////////////////////////////////////////////////////////////// 108 | require(['jquery'], function($) { 109 | 110 | // Manages the showing and hiding of the sidebar. 111 | $(document).ready(function() { 112 | var sidebar = $("#documenter > .docs-sidebar"); 113 | var sidebar_button = $("#documenter-sidebar-button") 114 | sidebar_button.click(function(ev) { 115 | ev.preventDefault(); 116 | sidebar.toggleClass('visible'); 117 | if (sidebar.hasClass('visible')) { 118 | // Makes sure that the current menu item is visible in the sidebar. 119 | $("#documenter .docs-menu a.is-active").focus(); 120 | } 121 | }); 122 | $("#documenter > .docs-main").bind('click', function(ev) { 123 | if ($(ev.target).is(sidebar_button)) { 124 | return; 125 | } 126 | if (sidebar.hasClass('visible')) { 127 | sidebar.removeClass('visible'); 128 | } 129 | }); 130 | }) 131 | 132 | // Resizes the package name / sitename in the sidebar if it is too wide. 133 | // Inspired by: https://github.com/davatron5000/FitText.js 134 | $(document).ready(function() { 135 | e = $("#documenter .docs-autofit"); 136 | function resize() { 137 | var L = parseInt(e.css('max-width'), 10); 138 | var L0 = e.width(); 139 | if(L0 > L) { 140 | var h0 = parseInt(e.css('font-size'), 10); 141 | e.css('font-size', L * h0 / L0); 142 | // TODO: make sure it survives resizes? 143 | } 144 | } 145 | // call once and then register events 146 | resize(); 147 | $(window).resize(resize); 148 | $(window).on('orientationchange', resize); 149 | }); 150 | 151 | // Scroll the navigation bar to the currently selected menu item 152 | $(document).ready(function() { 153 | var sidebar = $("#documenter .docs-menu").get(0); 154 | var active = $("#documenter .docs-menu .is-active").get(0); 155 | if(typeof active !== 'undefined') { 156 | sidebar.scrollTop = active.offsetTop - sidebar.offsetTop - 15; 157 | } 158 | }) 159 | 160 | }) 161 | //////////////////////////////////////////////////////////////////////////////// 162 | require(['jquery'], function($) { 163 | 164 | function set_theme(theme) { 165 | var active = null; 166 | var disabled = []; 167 | for (var i = 0; i < document.styleSheets.length; i++) { 168 | var ss = document.styleSheets[i]; 169 | var themename = ss.ownerNode.getAttribute("data-theme-name"); 170 | if(themename === null) continue; // ignore non-theme stylesheets 171 | // Find the active theme 172 | if(themename === theme) active = ss; 173 | else disabled.push(ss); 174 | } 175 | if(active !== null) { 176 | active.disabled = false; 177 | if(active.ownerNode.getAttribute("data-theme-primary") === null) { 178 | document.getElementsByTagName('html')[0].className = "theme--" + theme; 179 | } else { 180 | document.getElementsByTagName('html')[0].className = ""; 181 | } 182 | disabled.forEach(function(ss){ 183 | ss.disabled = true; 184 | }); 185 | } 186 | 187 | // Store the theme in localStorage 188 | if(typeof(window.localStorage) !== "undefined") { 189 | window.localStorage.setItem("documenter-theme", theme); 190 | } else { 191 | console.error("Browser does not support window.localStorage"); 192 | } 193 | } 194 | 195 | // Theme picker setup 196 | $(document).ready(function() { 197 | // onchange callback 198 | $('#documenter-themepicker').change(function themepick_callback(ev){ 199 | var themename = $('#documenter-themepicker option:selected').attr('value'); 200 | set_theme(themename); 201 | }); 202 | 203 | // Make sure that the themepicker displays the correct theme when the theme is retrieved 204 | // from localStorage 205 | if(typeof(window.localStorage) !== "undefined") { 206 | var theme = window.localStorage.getItem("documenter-theme"); 207 | if(theme !== null) { 208 | $('#documenter-themepicker option').each(function(i,e) { 209 | e.selected = (e.value === theme); 210 | }) 211 | } else { 212 | $('#documenter-themepicker option').each(function(i,e) { 213 | e.selected = $("html").hasClass(`theme--${e.value}`); 214 | }) 215 | } 216 | } 217 | }) 218 | 219 | }) 220 | //////////////////////////////////////////////////////////////////////////////// 221 | require(['jquery'], function($) { 222 | 223 | // update the version selector with info from the siteinfo.js and ../versions.js files 224 | $(document).ready(function() { 225 | var version_selector = $("#documenter .docs-version-selector"); 226 | var version_selector_select = $("#documenter .docs-version-selector select"); 227 | 228 | version_selector_select.change(function(x) { 229 | target_href = version_selector_select.children("option:selected").get(0).value; 230 | window.location.href = target_href; 231 | }); 232 | 233 | // add the current version to the selector based on siteinfo.js, but only if the selector is empty 234 | if (typeof DOCUMENTER_CURRENT_VERSION !== 'undefined' && $('#version-selector > option').length == 0) { 235 | var option = $(""); 236 | version_selector_select.append(option); 237 | } 238 | 239 | if (typeof DOC_VERSIONS !== 'undefined') { 240 | var existing_versions = version_selector_select.children("option"); 241 | var existing_versions_texts = existing_versions.map(function(i,x){return x.text}); 242 | DOC_VERSIONS.forEach(function(each) { 243 | var version_url = documenterBaseURL + "/../" + each; 244 | var existing_id = $.inArray(each, existing_versions_texts); 245 | // if not already in the version selector, add it as a new option, 246 | // otherwise update the old option with the URL and enable it 247 | if (existing_id == -1) { 248 | var option = $(""); 249 | version_selector_select.append(option); 250 | } else { 251 | var option = existing_versions[existing_id]; 252 | option.value = version_url; 253 | option.disabled = false; 254 | } 255 | }); 256 | } 257 | 258 | // only show the version selector if the selector has been populated 259 | if (version_selector_select.children("option").length > 0) { 260 | version_selector.toggleClass("visible"); 261 | } 262 | }) 263 | 264 | }) 265 | -------------------------------------------------------------------------------- /src/Benchmarking.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Return average average value for an array of cost vectors. 3 | If the length of the input is less than 1, mean is not well defined, 4 | the key values returned are 'nothing'. 5 | """ 6 | function dict_average(dict_list) 7 | avcosts = zero_costvector() 8 | 9 | if length(dict_list) == 0 10 | for cost_type in keys(avcosts) 11 | avcosts[cost_type] = NaN 12 | end 13 | return avcosts 14 | end 15 | 16 | for cost_type in keys(avcosts) 17 | costs = collect(map(x->x[cost_type], dict_list)) 18 | avcosts[cost_type] = mean(costs) 19 | end 20 | return avcosts 21 | end 22 | 23 | """ 24 | Return average standard error for an array of cost vectors. 25 | If the length of the input is less than 2, error is not well defined, 26 | the key values returned are 'nothing'. 27 | """ 28 | function dict_err(dict_list) 29 | averr = zero_costvector() 30 | len = length(dict_list) 31 | 32 | if len < 2 33 | for cost_type in keys(averr) 34 | averr[cost_type] = NaN 35 | end 36 | return averr 37 | end 38 | 39 | for cost_type in keys(averr) 40 | costs = collect(map(x->x[cost_type], dict_list)) 41 | averr[cost_type] = std(costs)/(sqrt(length(costs))) 42 | end 43 | return averr 44 | end 45 | 46 | 47 | """Generate a list of user_pairs for a QNetwork""" 48 | function make_user_pairs(net::QNetwork, num_pairs::Int)::Vector{Tuple{Int64, Int64}} 49 | num_nodes = length(net.nodes) 50 | @assert num_nodes >= num_pairs*2 "Graph space too small for number of pairs" 51 | rand_space = Array(collect(1:num_nodes)) 52 | pairs = Vector{Tuple}() 53 | i = 0 54 | while i < num_pairs 55 | idx = rand(1:length(rand_space)) 56 | u = rand_space[idx] 57 | deleteat!(rand_space, idx) 58 | idx = rand(1:length(rand_space)) 59 | v = rand_space[idx] 60 | deleteat!(rand_space, idx) 61 | chosen_pair = (u, v) 62 | push!(pairs, chosen_pair) 63 | i += 1 64 | end 65 | return pairs 66 | end 67 | 68 | """ 69 | Generate a list of end-users for a TemporalGraph. 70 | src_layer and dst_layer specify the temporal locations of the source and dst nodes 71 | respectively. The default value for these is -1, which indicates the end-users should 72 | be asynchronus. 73 | """ 74 | function make_user_pairs(net::QuNet.TemporalGraph, num_pairs::Int; 75 | src_layer::Int64=-1, dst_layer::Int64=-1)::Vector{Tuple{Int64, Int64}} 76 | 77 | num_nodes = net.nv 78 | @assert num_nodes >= num_pairs*2 "Graph space too small for number of pairs" 79 | @assert dst_layer <= net.steps "dst_layer must be between 1 and $(net.steps) -- or -1 for async nodes" 80 | 81 | rand_space = Array(collect(1:num_nodes)) 82 | pairs = Vector{Tuple}() 83 | i = 0 84 | while i < num_pairs 85 | # Random source 86 | idx = rand(1:length(rand_space)) 87 | u = rand_space[idx] 88 | deleteat!(rand_space, idx) 89 | 90 | # Random dest 91 | idx = rand(1:length(rand_space)) 92 | v = rand_space[idx] 93 | deleteat!(rand_space, idx) 94 | 95 | # Update u and v to point to the specified source and dest layers 96 | # If src_layer == -1, index to async_nodes 97 | if src_layer == -1 98 | u += num_nodes * net.steps 99 | elseif src_layer > 0 100 | u += (src_layer - 1) * num_nodes 101 | else 102 | error("Invalid src_layer. Choose from {1, ..., T.steps} or -1 for asynchronus nodes") 103 | end 104 | 105 | if dst_layer == -1 106 | v += num_nodes * net.steps 107 | elseif dst_layer > 0 108 | v += (dst_layer - 1) * num_nodes 109 | else 110 | error("Invalid dst_layer. Choose from {1, ..., T.steps} or -1 for asynchronus nodes") 111 | end 112 | 113 | chosen_pair = (u, v) 114 | push!(pairs, chosen_pair) 115 | i += 1 116 | end 117 | return pairs 118 | end 119 | 120 | """ 121 | Given a tally for the number of paths used by each end-user in a greedy_protocol: 122 | (i.e. [3,4,2,1] meaning 3 end-users used no paths, 4, end-users used 1 path, etc.) 123 | This function finds the average number of paths used in the protocol. 124 | """ 125 | function ave_paths_used(pathuse_count::Vector{Float64}) 126 | ave_pathuse = 0.0 127 | len = length(pathuse_count) 128 | for i in 1:len 129 | ave_pathuse += (i-1) * pathuse_count[i] 130 | end 131 | ave_pathuse = ave_pathuse / sum(pathuse_count) 132 | return ave_pathuse 133 | end 134 | 135 | 136 | """ 137 | Takes a network as input and return greedy_multi_path! performance statistics for some number of 138 | random user pairs. Ensure graph is refreshed before starting. 139 | """ 140 | function net_performance(network::Union{QNetwork, QuNet.TemporalGraph}, 141 | num_trials::Int64, num_pairs::Int64; max_paths=3, src_layer::Int64=-1, 142 | dst_layer::Int64=-1, edge_perc_rate=0.0) 143 | 144 | # Sample of average routing costs between end-users 145 | ave_cost_data = [] 146 | # Sample of path usage statistics for the algorithm 147 | pathcount_data = [] 148 | 149 | for i in 1:num_trials 150 | net = deepcopy(network) 151 | 152 | # Generate random communication pairs 153 | if typeof(network) == TemporalGraph 154 | user_pairs = make_user_pairs(network, num_pairs, src_layer=src_layer, dst_layer=dst_layer) 155 | # Add asynchronus nodes to the network copy 156 | add_async_nodes!(net, user_pairs) 157 | else 158 | user_pairs = make_user_pairs(network, num_pairs) 159 | end 160 | 161 | # Percolate edges 162 | # WARNING: Edge percolation will not be consistant between temporal layers 163 | # if typeof(net) = QuNet.TemporalGraph 164 | if edge_perc_rate != 0.0 165 | @assert 0 <= edge_perc_rate <= 1.0 "edge_perc_rate out of bounds" 166 | net = QuNet.percolate_edges(net, edge_perc_rate) 167 | refresh_graph!(net) 168 | end 169 | 170 | # Get data from greedy_multi_path 171 | dummy, routing_costs, pathuse_count = QuNet.greedy_multi_path!(net, purify, user_pairs, max_paths) 172 | 173 | # Filter out entries where no paths were found and costs are not well defined 174 | filter!(x->x!=nothing, routing_costs) 175 | 176 | # If the mean is well defined, average the routing costs and push to ave_cost_data 177 | if length(routing_costs) > 0 178 | # Average the data 179 | ave = dict_average(routing_costs) 180 | push!(ave_cost_data, ave) 181 | end 182 | push!(pathcount_data, pathuse_count) 183 | end 184 | 185 | # Find the mean and standard error of ave_cost_data. Call this the performance 186 | performance = dict_average(ave_cost_data) 187 | performance_err = dict_err(ave_cost_data) 188 | 189 | # Find the mean and standard error of the path usage statistics: 190 | 191 | # Usage: 192 | # Each entry in pathcount_data is a vector of Ints of length (max_paths + 1) 193 | # An example entry is [3, 4, 3, 1] 194 | # Where 3 end-users found 0 paths, 4 end-users found 1 path, etc. 195 | # Given a collection of path statistics, ie. [[0, 1, 2, 1], [0, 0, 3, 0], [0, 1, 2, 1]] 196 | # we want to return vectors of average path usage with associated error: 197 | # ie. [0, 0.666, 2.333, 0.666] for means 198 | ave_pathcounts = [0.0 for i in 0:max_paths] 199 | ave_pathcounts_err = [0.0 for i in 0:max_paths] 200 | 201 | for i in 1:max_paths+1 202 | data = [pathcount_data[j][i] for j in 1:num_trials] 203 | ave_pathcounts[i] = mean(data) 204 | ave_pathcounts_err[i] = std(data)/(sqrt(length(data))) 205 | end 206 | 207 | return performance, performance_err, ave_pathcounts, ave_pathcounts_err 208 | end 209 | 210 | 211 | """ 212 | Takes a network as input and returns greedy_multi_path! heatmap data for 213 | some number of user pairs. (i.e. a list of efficiency fidelity coordinates 214 | for all end users over all num_trials) 215 | """ 216 | function heat_data(network::Union{QNetwork, QuNet.TemporalGraph}, 217 | num_trials::Int64, num_pairs::Int64; max_paths=3, src_layer::Int64=-1, 218 | dst_layer::Int64=-1, edge_perc_rate=0.0) 219 | 220 | # e,f coords for all end users over all num_trials 221 | coord_list = [] 222 | 223 | for i in 1:num_trials 224 | println("Collecting data for trial $i") 225 | net = deepcopy(network) 226 | 227 | # Generate random communication pairs 228 | if typeof(network) == TemporalGraph 229 | user_pairs = make_user_pairs(network, num_pairs, src_layer=src_layer, dst_layer=dst_layer) 230 | # Add asynchronus nodes to the network copy 231 | add_async_nodes!(net, user_pairs) 232 | else 233 | user_pairs = make_user_pairs(network, num_pairs) 234 | end 235 | 236 | # Percolate edges 237 | # WARNING: Edge percolation will not be consistant between temporal layers 238 | # if typeof(net) = QuNet.TemporalGraph 239 | if edge_perc_rate != 0.0 240 | @assert 0 <= edge_perc_rate <= 1.0 "edge_perc_rate out of bounds" 241 | net = QuNet.percolate_edges(net, edge_perc_rate) 242 | refresh_graph!(net) 243 | end 244 | 245 | # Get data from greedy_multi_path 246 | dummy, routing_costs, pathuse_count = QuNet.greedy_multi_path!(net, purify, user_pairs, max_paths) 247 | 248 | # Filter out entries where no paths were found and costs are not well defined 249 | filter!(x->x!=nothing, routing_costs) 250 | 251 | # Put end-user costs into tuples (e, f) 252 | for usercost in routing_costs 253 | coord = Vector{Float64}() 254 | push!(coord, usercost["loss"]) 255 | push!(coord, usercost["Z"]) 256 | push!(coord_list, coord) 257 | end 258 | end 259 | return coord_list 260 | end 261 | -------------------------------------------------------------------------------- /src/TemporalGraphs.jl: -------------------------------------------------------------------------------- 1 | mutable struct TemporalGraph <: QObject 2 | #graph::SimpleWeightedDiGraph 3 | graph::Dict 4 | nv::Int64 5 | steps::Int64 6 | # Visual coordinates 7 | locs_x::Vector{Float64} 8 | locs_y::Vector{Float64} 9 | has_async_nodes::Bool 10 | 11 | TemporalGraph() = new(Dict{String,SimpleWeightedDiGraph}(), 0, 1, Vector{Float64}(), Vector{Float64}(), false) 12 | end 13 | 14 | function TemporalGraph(network::QNetwork, steps::Int64; memory_prob::Float64=1.0, 15 | memory_costs::Dict=Dict())::TemporalGraph 16 | temp_graph = TemporalGraph() 17 | temp_graph.nv = length(network.nodes) 18 | temp_graph.steps = steps 19 | temp_graph.has_async_nodes = false 20 | 21 | # Make a copy of the network so we don't change QNetwork structure. 22 | netcopy = deepcopy(network) 23 | 24 | if memory_prob != 1.0 25 | @assert 0 <= memory_prob <= 1 26 | # Iterate through nodes and randomly reassign memories according to memory_prob 27 | # TODO: BAD PRACTICE. Don't manipulate QNetwork data. 28 | for node in netcopy.nodes 29 | if rand(Float64) <= memory_prob 30 | node.has_memory = true 31 | else 32 | node.has_memory = false 33 | end 34 | end 35 | end 36 | 37 | # Keep tabs on which nodes have memories and what the the associated cost are 38 | node_memories = Dict() 39 | node_memory_costs = Dict() 40 | for node in netcopy.nodes 41 | node_memories[node.id] = node.has_memory 42 | node_memory_costs[node.id] = node.memory_costs 43 | end 44 | 45 | for cost_key in keys(zero_costvector()) 46 | temp_graph.graph[cost_key] = SimpleWeightedDiGraph() 47 | 48 | for t in 1:steps 49 | # Vertices 50 | add_vertices!(temp_graph.graph[cost_key], temp_graph.nv) 51 | 52 | # Channels 53 | for channel in netcopy.channels 54 | if channel.active == true 55 | src = findfirst(x -> x == channel.src, netcopy.nodes) + (t-1)*temp_graph.nv 56 | dest = findfirst(x -> x == channel.dest, netcopy.nodes) + (t-1)*temp_graph.nv 57 | weight = channel.costs[cost_key] 58 | add_edge!(temp_graph.graph[cost_key], src, dest, weight) 59 | add_edge!(temp_graph.graph[cost_key], dest, src, weight) 60 | end 61 | end 62 | end 63 | 64 | # Memory channels 65 | for t in 1:(steps-1) 66 | for node in 1:temp_graph.nv 67 | # Add temporal link if memory exists 68 | if node_memories[node] == true 69 | # WARNING: Memory costs aren't dependent on the timestep size. 70 | # If the user specified a memory cost, use that. 71 | if length(memory_costs) != 0 72 | cost = memory_costs[cost_key] 73 | # Otherwise pull it from the node attribute 74 | else 75 | cv = node_memory_costs[node] 76 | cost = cv[cost_key] 77 | end 78 | # If the cost is zero, set it to epsilon so SimpleWeightedGraphs 79 | # add it to the sparse adj. matrix. 80 | if cost == 0 81 | cost = eps(Float64) 82 | end 83 | 84 | src = node + (t-1)*temp_graph.nv 85 | dest = src + temp_graph.nv 86 | add_edge!(temp_graph.graph[cost_key], src, dest, cost) 87 | end 88 | end 89 | end 90 | 91 | # Coords 92 | 93 | # Offset factor for temporal nodes 94 | offsetX = 0.1 95 | offsetY = 0.2 96 | 97 | for t in 1:steps 98 | for node in netcopy.nodes 99 | push!(temp_graph.locs_x, node.location.x + (t-1) * offsetX) 100 | push!(temp_graph.locs_y, node.location.y + (t-1) * offsetY) 101 | end 102 | end 103 | end 104 | 105 | return temp_graph 106 | end 107 | 108 | """ 109 | Add asynchronus nodes to a Temporal Network. 110 | 111 | This function adds tempnet.nv nodes to the graph. If a given node is source, 112 | outgoing asynchronus edges are added to the node that connects it to its temporal 113 | counterparts. This is done with incremental timeweights ϵ so that earlier times 114 | are prioritized. 115 | 116 | Likewise if a node is a dest, incoming asynchronus edges are added with incremental 117 | weights ϵ. 118 | """ 119 | function add_async_nodes!(tempnet::QuNet.TemporalGraph, endusers::Vector{Tuple{Int64, Int64}}; 120 | ϵ=1) 121 | 122 | # No double dipping! 123 | if tempnet.has_async_nodes == true 124 | @warn("Redundent use of add_async_nodes -- T.has_async_nodes == true 125 | Returning an unmodified TemporalGraph") 126 | return tempnet 127 | end 128 | 129 | N = tempnet.nv 130 | steps = tempnet.steps 131 | # index offset for asynchronus nodes 132 | async_offset = N*steps 133 | 134 | # Keep track of asynchronus src and dst nodes 135 | src_nodes = [] 136 | dst_nodes = [] 137 | for userpair in endusers 138 | src = userpair[1] 139 | dst = userpair[2] 140 | if src > async_offset 141 | push!(src_nodes, src) 142 | end 143 | if dst > async_offset 144 | push!(dst_nodes, dst) 145 | end 146 | end 147 | 148 | for costkey in keys(zero_costvector()) 149 | graph = tempnet.graph[costkey] 150 | # Add the asynchronus nodes 151 | add_vertices!(graph, N) 152 | 153 | # Connect async nodes to temporal counterparts: 154 | for async in (1 + async_offset):(N + async_offset) 155 | # If node is a src, make asynchronus channels outgoing 156 | if async in src_nodes 157 | for t in 1:steps 158 | # Connect each temporal node to its async counterpart 159 | # Use time_dependent weight to prioritise top layers when routing 160 | #add_edge(src, dst) 161 | add_edge!(graph, async, async%async_offset + N*(t-1), ϵ*t) 162 | end 163 | # If node is a dst, make asynchronus channels incoming 164 | elseif async in dst_nodes 165 | for t in 1:steps 166 | add_edge!(graph, async%async_offset + N*(t-1), async, ϵ*t) 167 | end 168 | end 169 | end 170 | end 171 | tempnet.has_async_nodes = true 172 | end 173 | 174 | """ 175 | Remove all asynchronus nodes of a TemporalGraph. 176 | 177 | i.e. those put in place by add_async_nodes! 178 | """ 179 | function remove_async_nodes!(tempnet::QuNet.TemporalGraph) 180 | 181 | # No double dipping! 182 | if tempnet.has_async_nodes == false 183 | @warn("Redundent use of remove_async_nodes -- T.has_async_nodes == false 184 | Returning an unmodified TemporalGraph") 185 | return tempnet 186 | end 187 | 188 | offset = tempnet.nv * tempnet.steps 189 | for costkey in keys(zero_costvector()) 190 | graph = tempnet.graph[costkey] 191 | # Assertion is no longer valid since not all async nodes from offset+1 to offset+N are generated 192 | # @assert nv(graph) == N * steps + N 193 | # Remove nodes with decreasing id so iteration works 194 | kill_list = reverse(collect((offset + 1) : (offset + tempnet.nv))) 195 | for i in kill_list 196 | rem_vertex!(graph, i) 197 | end 198 | end 199 | tempnet.has_async_nodes = false 200 | end 201 | 202 | """ 203 | TODO: DOESN'T WORK 204 | 205 | Remove the asynchronus edges of a TemporalGraph, excluding those connecting 206 | specified layers 207 | """ 208 | # function remove_async_edges!(tempnet::QuNet.TemporalGraph, exclude_layers...) 209 | # 210 | # @assert(all(0 < i <= tempnet.steps for i in exclude_layers)) 211 | # # offset index for asynchronus nodes 212 | # offset = tempnet.nv * tempnet.steps 213 | # 214 | # for costkey in keys(zero_costvector()) 215 | # graph = tempnet.graph[costkey] 216 | # for async_node in offset + 1 : offset + tempnet.nv 217 | # nlist = all_neighbors(graph, async_node) 218 | # # remove nodes in nlist that are in the excluded layers 219 | # # BUG, this filters nodes, not edge layers. 220 | # filter!(x -> !((x-1) ÷ tempnet.nv in exclude_layers), nlist) 221 | # for node in nlist 222 | # # Remove in both directions just in case 223 | # rem_edge!(graph, async_node, node) 224 | # rem_edge!(graph, node, async_node) 225 | # end 226 | # end 227 | # end 228 | # end 229 | 230 | 231 | """ 232 | Convert temporal nodes into asynchronus nodes 233 | If the node is already asynchronus, it is returned 234 | Throws an exception if the node is out of bounds 235 | """ 236 | function temp_to_async(nv::Int, steps::Int, node::Int) 237 | offset = nv * steps 238 | if node <= 0 || node > offset + nv 239 | error("Node index out of bounds") 240 | elseif node > offset 241 | return node 242 | end 243 | mod = node % nv 244 | if mod == 0 245 | mod = nv 246 | end 247 | return mod + offset 248 | end 249 | 250 | 251 | """ 252 | Get the temporal depth of a node from its index. If asynchronus, return -1 253 | Throws an exception if the node is out of bounds. 254 | """ 255 | function node_timedepth(nv::Int, steps::Int, node::Int) 256 | offset = nv * steps 257 | if node <= 0 || node > offset + nv 258 | error("Node index out of bounds") 259 | elseif node > offset 260 | return -1 261 | end 262 | if node % nv == 0 263 | return (node-1) ÷ nv + 1 264 | end 265 | return (node ÷ nv) + 1 266 | end 267 | 268 | 269 | """ 270 | Get the relative index of a node from its original index. 271 | ie. for a graph with nv = 10, steps = 3, then node 14 272 | is the same relative to node 4. Another way to think about this 273 | is that it gets the index of the corresponding node in the top layer. 274 | """ 275 | function relative_index(nv::Int, steps::Int, node::Int) 276 | offset = nv * steps 277 | if node <= 0 || node > offset + nv 278 | error("Node index out of bounds") 279 | end 280 | mod = node % nv 281 | if mod == 0 282 | return nv 283 | end 284 | return mod 285 | end 286 | 287 | 288 | """ 289 | For a given node at time T in a TemporalGraph, this function fixes its asynchronus 290 | counterpart with respect to T This is done by removing all edges of the asynchronus node 291 | except for the one connecting at time T. 292 | """ 293 | function fix_async_nodes_in_time!(tempnet::QuNet.TemporalGraph, node_list::Vector{Int}) 294 | 295 | if tempnet.has_async_nodes == false 296 | return tempnet 297 | end 298 | 299 | offset = tempnet.steps * tempnet.nv 300 | # Check that nodes are valid non-temporal indices 301 | @assert(all(0 < i <= offset for i in node_list)) 302 | 303 | for costkey in keys(zero_costvector()) 304 | graph = tempnet.graph[costkey] 305 | for node in node_list 306 | # Relative_index of node 307 | rel = relative_index(tempnet.nv, tempnet.steps, node) 308 | # Get the timedepth of the node 309 | depth = node_timedepth(tempnet.nv, tempnet.steps, node) 310 | # Get the asynchronus counterpart of node 311 | async = temp_to_async(tempnet.nv, tempnet.steps, node) 312 | 313 | for i in 1:tempnet.steps 314 | if i != depth 315 | hard_rem_edge!(graph, rel + (i-1)*tempnet.nv, async) 316 | end 317 | end 318 | end 319 | end 320 | end 321 | 322 | 323 | function gplot(temp_graph::TemporalGraph) 324 | gplot(temp_graph.graph["loss"], temp_graph.locs_x, temp_graph.locs_y, arrowlengthfrac=0.04) 325 | end 326 | -------------------------------------------------------------------------------- /src/Routing.jl: -------------------------------------------------------------------------------- 1 | weights = SimpleWeightedGraphs.weights 2 | 3 | """ 4 | Find the shortest path between two nodes of an AbstractGraph by using the 5 | \'a star\' method. Returns a vector of AbstractEdge. If no path is found, 6 | returns an empty vector. 7 | """ 8 | function shortest_path(graph::AbstractGraph, src::Int64, dst::Int64) 9 | path = a_star(SimpleDiGraph(weights(graph)), src, dst, weights(graph)) 10 | return path 11 | end 12 | # Change SimpleDiGraph to SimpleWeightedDiGraph 13 | 14 | """ 15 | Finds the total weight/length of a path for a given AbstractGraph. 16 | Not to be confused with get_pathcv(), which finds the vector of costs for a 17 | path in a QNetwork. 18 | 19 | !!! warning 20 | For whatever reason, SimpleWeightedDiGraph indexes weights with [dest, src]. 21 | Keep in mind this path_length will likely not work if you pass in anything 22 | but a SimpleWeightedDiGraph. 23 | """ 24 | function path_length(graph::AbstractGraph, path::Vector{Tuple{Int64, Int64}})::Float64 25 | dist = 0.0 26 | for edge in path 27 | # SimpleWeightedDiGraph indexes by [dst, src]. Be careful here! 28 | dist += graph.weights[edge[2], edge[1]] 29 | end 30 | return dist 31 | end 32 | 33 | 34 | function path_length(graph::AbstractGraph, path::Vector{LightGraphs.SimpleGraphs.SimpleEdge{Int64}})::Float64 35 | dist = 0.0 36 | for edge in path 37 | # SimpleWeightedDiGraph indexes by [dst, src]. Be careful here! 38 | dist += graph.weights[edge.dst, edge.src] 39 | end 40 | return dist 41 | end 42 | 43 | """ 44 | Removes the shortest path between two nodes of an abstract graph and returns the 45 | path along with its length. If no path exists, returns nothing for both. 46 | """ 47 | function remove_shortest_path!(graph::AbstractGraph, src::Int64, dst::Int64) 48 | 49 | # Get shortest path. If no path exists, return nothing for path and length 50 | path = shortest_path(graph, src, dst) 51 | if length(path) == 0 52 | return nothing 53 | end 54 | 55 | path_len = path_length(graph, path) 56 | 57 | # Remove path 58 | for edge in path 59 | status = hard_rem_edge!(graph, edge.src, edge.dst) 60 | if status == false 61 | error("Failed to remove edge in path") 62 | end 63 | end 64 | 65 | return path, path_len 66 | end 67 | 68 | 69 | """ 70 | Removes the shortest path between two nodes of a QNetwork *with respect to a 71 | given cost* and returns the path with its corresponding cost vector. 72 | """ 73 | function remove_shortest_path!(network::QNetwork, cost_id::String, src::Int64, dst::Int64) 74 | @assert cost_id in keys(zero_costvector()) "Invalid cost" 75 | 76 | # Find shortest path in terms of the given cost 77 | g = network.graph[cost_id] 78 | path = shortest_path(g, src, dst) 79 | # If no path exists, return nothing for path and path_cv 80 | if length(path) == 0 81 | return nothing, nothing 82 | end 83 | 84 | # Find the costs associated with the path 85 | path_cv = get_pathcv(network, path) 86 | 87 | # Remove the path from the network graphs 88 | for cost_id in keys(zero_costvector()) 89 | graph = network.graph[cost_id] 90 | for edge in path 91 | status = hard_rem_edge!(graph, edge.src, edge.dst) 92 | if status == false 93 | error("Failed to remove edge in path") 94 | end 95 | end 96 | end 97 | return path, path_cv 98 | end 99 | 100 | """ 101 | Remove the shortest_path of a TemporalGraph, making sure not to remove the edge 102 | between the asynchronous node and its temporal counterpart. When specifying 103 | src and dst, specify them as you would if the graph were non-temporal. E.G. if 104 | you have a 1x2 network with 2 timesteps and you wanted to find the best path 105 | between the two nodes, you would specify src:1, dst:2 106 | """ 107 | function remove_shortest_path!(tempnet::QuNet.TemporalGraph, cost_id::String, 108 | src::Int64, dst::Int64) 109 | 110 | @assert cost_id in keys(zero_costvector()) "Invalid cost" 111 | 112 | # Check if src or dst nodes are asynchronus 113 | async_src = false 114 | async_dst = false 115 | 116 | if src > tempnet.nv * tempnet.steps 117 | async_src = true 118 | end 119 | if dst > tempnet.nv * tempnet.steps 120 | async_dst = true 121 | end 122 | 123 | # TODO: Remove this 124 | # # Check that src and dst are nodes within range(1, size-of-static-graph) 125 | # if (src > tempnet.nv && dst > tempnet.nv) 126 | # @assert 1==0 "(src::$src) and (dst::$dst) async_pair = true: nodes must be asynchronous" 127 | # # Reindex src and dst so that they point to their asynchronus counterparts 128 | # src += tempnet.nv * tempnet.steps 129 | # dst += tempnet.nv * tempnet.steps 130 | # end 131 | 132 | # Find shortest path in terms of the given cost 133 | t = tempnet.graph[cost_id] 134 | path = shortest_path(t, src, dst) 135 | 136 | # If length(path) == 0, no path exists. Return nothing 137 | if length(path) == 0 138 | return nothing, nothing 139 | end 140 | 141 | # If either src or dst was asynchronus: 142 | # Pop the asynchronus edge so it doesn't get removed. 143 | if async_src == true 144 | popfirst!(path) 145 | end 146 | if async_dst == true 147 | pop!(path) 148 | end 149 | 150 | # Find the costs associated with the path: 151 | path_cv = get_pathcv(tempnet, path) 152 | 153 | # Remove the shortest path 154 | for cost_id in keys(zero_costvector()) 155 | graph = tempnet.graph[cost_id] 156 | for edge in path 157 | status = hard_rem_edge!(graph, edge.src, edge.dst) 158 | if status == false 159 | error("Failed to remove edge in path") 160 | end 161 | end 162 | end 163 | return path, path_cv 164 | end 165 | 166 | """ 167 | greedy_multi_path! is an entanglement routing strategy for a quantum network 168 | with n end user pairs. 169 | 170 | 1. Each user pair has an associated "bundle" of paths that starts out empty. 171 | For each user pair, find the shortest path between them, and add its cost to the 172 | bundle. If no path exists, add "nothing" to the bundle. 173 | 174 | 2. When all the bundles are filled up to the maximum number "maxpaths", use an 175 | entanglement purification method between the path costs. Add the resulting cost 176 | vector to the array pur_paths. If no paths exist for a given bundle, increment 177 | the collision count by one, and add "nothing" to pur_paths 178 | 179 | 3. Return the array of purified cost vectors and the collision count. 180 | """ 181 | function greedy_multi_path!(network::QNetwork, purification_method, 182 | users, maxpaths::Int64=3) 183 | 184 | # List of paths for each userpair 185 | pathset = [Vector() for i in 1:length(users)] 186 | # List of path costs for each userpair 187 | path_costs = [Vector{Dict{Any,Any}}() for i in 1:length(users)] 188 | 189 | for i in 1:maxpaths 190 | for (userid, user) in enumerate(users) 191 | src = user[1]; dst = user[2] 192 | # Remove the shortest path in terms of Z-dephasing and get its cost vector. 193 | path, path_cv = remove_shortest_path!(network, "Z", src, dst) 194 | # If pathcv is nothing, no path was found. 195 | if path_cv == nothing 196 | break 197 | else 198 | push!(pathset[userid], path) 199 | push!(path_costs[userid], path_cv) 200 | end 201 | end 202 | end 203 | 204 | # A tally of the number of paths each end-user purifies together. 205 | pathuse_count = [0 for i in 0:maxpaths] 206 | # An array of purified cost vectors for each end-user. 207 | pur_paths = [] 208 | 209 | # Purify end-user paths 210 | for userpaths in path_costs 211 | # Increment the tally for the number of paths beting purified 212 | len = length(userpaths) 213 | pathuse_count[len + 1] += 1 214 | # If len == 0, no paths were found between the end-user. 215 | if len == 0 216 | push!(pur_paths, nothing) 217 | # Otherwise, purify the paths 218 | else 219 | purcost::Dict{Any, Any} = purification_method(userpaths) 220 | # Convert purcost from decibels to metric form 221 | purcost = convert_costs(purcost) 222 | push!(pur_paths, purcost) 223 | end 224 | end 225 | return pathset, pur_paths, pathuse_count 226 | end 227 | 228 | 229 | function greedy_multi_path!(network::QuNet.TemporalGraph, purification_method, 230 | users, maxpaths::Int64=3) 231 | 232 | # For TemporalGraph, paths must arrive at identical time_depth 233 | # route_layer_known is false until first path is routed. 234 | route_layer_known = false 235 | 236 | # List of paths for each userpair 237 | pathset = [Vector() for i in 1:length(users)] 238 | # List of path costs for each userpair 239 | path_costs = [Vector{Dict{Any,Any}}() for i in 1:length(users)] 240 | 241 | for i in 1:maxpaths 242 | for (userid, user) in enumerate(users) 243 | src = user[1]; dst = user[2] 244 | # Remove the shortest path in terms of Z-dephasing and get its cost vector. 245 | path, path_cv = remove_shortest_path!(network, "Z", src, dst) 246 | # If pathcv is nothing, no path was found. 247 | if path_cv == nothing 248 | break 249 | else 250 | push!(pathset[userid], path) 251 | push!(path_costs[userid], path_cv) 252 | end 253 | 254 | # If we found a path, and we haven't fixed a routing time: 255 | if path_cv != nothing && route_layer_known == false 256 | route_layer_known = true 257 | last_edge = last(path) 258 | # If last node of the path is asynchronus: 259 | # Remove all async edges except for the one at T = depth 260 | # This means all future paths will have to route to the same time 261 | if last_edge.dst > network.nv * network.steps 262 | node = last_edge.src 263 | depth = QuNet.node_timedepth(T.nv, T.steps, node) 264 | # Remove all asynchronus edges except for that depth 265 | QuNet.fix_async_nodes_in_time(T, [node]) 266 | end 267 | end 268 | 269 | 270 | end 271 | end 272 | 273 | # A tally of the number of paths each end-user purifies together. 274 | pathuse_count = [0 for i in 0:maxpaths] 275 | # An array of purified cost vectors for each end-user. 276 | pur_paths = [] 277 | 278 | # Purify end-user paths 279 | for userpaths in path_costs 280 | # Increment the tally for the number of paths beting purified 281 | len = length(userpaths) 282 | pathuse_count[len + 1] += 1 283 | # If len == 0, no paths were found between the end-user. 284 | if len == 0 285 | push!(pur_paths, nothing) 286 | # Otherwise, purify the paths 287 | else 288 | purcost::Dict{Any, Any} = purification_method(userpaths) 289 | # Convert purcost from decibels to metric form 290 | purcost = convert_costs(purcost) 291 | push!(pur_paths, purcost) 292 | end 293 | end 294 | return pathset, pur_paths, pathuse_count 295 | end 296 | 297 | 298 | """ 299 | Find the maximum timedepth reached by a given pathset 300 | """ 301 | function max_timedepth(pathset, T) 302 | max_depth = 1 303 | for bundle in pathset 304 | for path in bundle 305 | edge = last(path) 306 | node = edge.dst 307 | # Check if node is temporal. If it is, use 2nd last node in path instead 308 | if node > T.nv * T.steps 309 | node = edge.src 310 | end 311 | # use node - 1 here because if node % T.nv == 0, depth is off by one 312 | depth = QuNet.node_timedepth(T.nv, T.steps, node) 313 | if depth > max_depth 314 | max_depth = depth 315 | end 316 | end 317 | end 318 | return max_depth 319 | end 320 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Contents 4 | 5 | * Table of contents 6 | {:toc} 7 | 8 | # About 9 | 10 | The paper _"QuNet: Cost vector analysis & multi-path entanglement routing in quantum networks"_ ([https://arxiv.org/abs/2105.00418](https://arxiv.org/abs/2105.00418)) presents the theory, design and initial results for QuNet. 11 | 12 | Developers: 13 | + Hudson Leone ([leoneht0@gmail.com](mailto:leoneht0@gmail.com)) 14 | + Nathaniel Miller 15 | + Deepesh Singh 16 | + Nathan Langford 17 | + Peter Rohde ([dr.rohde@gmail.com](mailto:dr.rohde@gmail.com), [www.peterrohde.org](https://www.peterrohde.org)) 18 | 19 | # Goal 20 | 21 | QuNet is a highly-scalable, multi-user quantum network simulator and benchmarking tool implemented in Julia. It relies on efficient algorithms for performing multi-user routing and congestion avoidance using quantum memories. QuNet focusses on the specific task of distributing entangled Bell pairs between users ($$A$$ and $$B$$), 22 |

    23 | $$|\Psi^+\rangle = \frac{1}{\sqrt{2}}(|0\rangle_A|0\rangle_B + |1\rangle_A|1\rangle_B).$$ 24 |

    25 | These can subsequently be employed in state teleportation protocols to transmit arbitrary quantum states, or be applied to quantum key distribution, distributed quantum computing, or other entanglement-based quantum protocols. 26 | 27 | QuNet uses a _cost vector_ methodology. Rather than track quantum states themselves, we track their associated _costs_ as they traverse the network. Costs are arbitrary properties that accumulate additively as qubits traverse networks. We can express physical degradation such as loss, dephasing or depolarising processes in this form, and also non-physical costs such as monetary ones. Tracking the accumulation of costs acting on Bell pairs is equivalent to directly tracking the states themselves. 28 | 29 | # Quantum channels 30 | 31 | Quantum channels can be represented in the quantum process formalism using the Kraus (or operator-sum) representation. In many useful instances these can be converted to additive metrics amenable to our cost vector formalism. 32 | 33 | Consider the loss channel, 34 |

    35 | $$\mathcal{E}_\mathrm{loss}(\rho) = p\rho + (1-p)|vac\rangle\langle vac|,$$ 36 |

    37 | where $$p$$ is the probability of a qubit not being lost, otherwise replaced with the vacuum state $$|vac\rangle$$. If multiple such channels are applied in series the $$p$$'s multiply, 38 |

    39 | $$p_\mathrm{total} = \prod_i p_i.$$ 40 |

    41 | However by converting to logarithmic form we can make this additive, 42 |

    43 | $$-\log(p_\mathrm{net}) = -\sum_i \log(p_i).$$ 44 |

    45 | Now $$m_i=-\log(p_i)$$ can be used as an additive edge weight. This is a common approach amongst experimentalists, who typically consider loss over fibre in terms of decibels per unit distance (dB/m). 46 | 47 | We can apply a similar approach to depolarising channels whose quantum process is given by, 48 |

    49 | $$\mathcal{E}_\mathrm{depol}(\rho) = p\rho + (1-p)\frac{I}{2},$$ 50 |

    51 | and $$m=-\log(p)$$ acts additively. With some algebraic manipulation we can apply this to dephasing channels, 52 |

    53 | $$\mathcal{E}_\mathrm{deph}(\rho) = (2p-1)\rho + (1-p)(\rho + Z\rho Z),$$ 54 |

    55 | where $$-\log(2p-1)$$ acts as our additive dephasing metric. Note this representation of the dephasing channel has been algebraically manipulated into an unaffected term ($$\rho$$), and a completely dephased term ($$\rho+Z\rho Z$$) which is the steady-state of the channel. This approach could also be applied to bit-flip ($$X$$) channels, bit-phase-flip ($$Y$$) channels, or amplitude damping channels. 56 | 57 | # Multi-path routing 58 | 59 | Classical networks rely on path-finding algorithms (e.g shortest path à la Dijkstra) for optimal packet routing. This is highly efficient, since Dijkstra's algorithm has worst case $$O(V^2)$$ runtime in the number of graph vertices $$V$$. Quantum networks can employ multi-path routing, whereby multiple independently routed Bell pairs are purified into one of higher fidelity. 60 | 61 |

    62 | 63 | 64 | We implement multi-path routing via multiple application of shortest path routing, where subsequent rounds exclude previously consumed routes from consideration. 65 | 66 | # Entanglement swapping & purification 67 | 68 | Primitive operations in quantum networks include entanglement swapping (for extending entanglement links), and entanglement purification (for boosting fidelity). 69 | 70 |

    71 |

    72 | 73 | 77 | 78 | # Graph reduction 79 | 80 | These primitives provide simple substitution rules for graph reduction. 81 | 82 |

    83 | 84 | 85 | # Network abstraction 86 | 87 | Quantum repeaters, comprising classically-controlled (and sometimes randomised) sequences of swapping and purification operations, can be reduced to a single virtual link capturing their average cost vector, thereby bypassing the need for directly accommodating non-deterministic or classically-controlled operations, maintaining compatability with the QuNet framework. Similarly, SneakerNet channels, whereby a large number of error-corrected qubits are physically transported can be reduced to an equivalent cost vector too. 88 | 89 | For large networks it may not always be necessary to understand the full dynamics across all nodes and channels. Instead we might focus higher-level abstractions of the network, which consider the dynamics between designated subnetworks or regions, reducing computational load. 90 | 91 | # Space-based networks 92 | 93 | QuNet can accommodate both static and dynamic nodes and channels. Here Alice & Bob have the option of communicating via: 94 | + A static ground-based fibre link. 95 | + A LEO satellite passing overhead through atmospheric free-space channels, which dynamically update. 96 | + Exploiting both and purifying them together (multi-path routing). 97 | 98 |

    99 |

    100 | 101 | 105 | 106 | # Code example 107 | 108 | This is the QuNet code in Julia that creates that network. Julia modules can be called from Python or run in Jupyter notebooks too. You can learn more about the Julia language at [www.julialang.org](https://www.julialang.org). 109 | 110 | 111 | 112 | ```julia 113 | using QuNet 114 | 115 | Q = QNetwork() 116 | A = BasicNode("A") 117 | B = BasicNode("B") 118 | S = PlanSatNode("S") 119 | 120 | B.location = Coords(500,0,0) 121 | S.location = Coords(-2000,0,1000) 122 | S.velocity = Velocity(1000,0) 123 | 124 | AB = BasicChannel(A, B, exp_cost=true) 125 | AS = AirChannel(A, S) 126 | SB = AirChannel(S, B) 127 | 128 | for i in [A, S, AB, AS, SB] 129 | add(Q, i) 130 | end 131 | ``` 132 | 133 | # Temporal routing & quantum memories 134 | 135 | We accommodate for quantum memories by treating them as temporal channels between the respective nodes of identical copies of the underlying graph, where each layer represents the network at a particular point in time. This is far more efficient than naïve combinatoric congestion mitigation techniques, relying on the same underlying routing algorithms applied to a graph with a simple linear overhead in size. 136 | 137 |

    138 | 139 | 140 | The incrementally weighted asynchronous nodes guide the routing algorithm to preference earlier times, thereby temporally compressing multi-user routing, and providing a temporal routing queue. 141 | 142 | The compression ratio is the ratio between routing time with and without memories. Here we show the temporal compression ratio of our algorithm against increasing network congestion. 143 | 144 |

    145 | 146 | 147 | Here’s a multi-user network with 3 users (colour coded) and multi-path routing (maximum 3 paths per user). The stacked layers represent time. 148 | 149 |

    150 | 151 | 152 | # Efficient multi-path routing 153 | 154 | Our greedy multi-path routing algorithm allows multi-user routing with congestion mitigation via quantum memories, with algorithmic efficiency, 155 |

    156 | $$O(M^3V^2),$$ 157 |

    158 | for $$M$$ user-pairs on a $$V$$-vertex graph, and is therefore highly scalable and efficient in both users and network size. 159 | 160 | Here we consider a grid network with edge percolations, showing the likelihood of users utilising different path numbers as the network becomes increasingly disconnected. 161 | 162 |

    163 | 164 | 165 | # Application to quantum key distribution 166 | 167 | This heat map shows the fidelity/efficiency trade off for random user pairs on a square lattice network. The distinct heat curves correspond to different numbers of paths utilised. Superimposed contours show achievable per-user E91 QKD secret key rates for the network. 168 | 169 |

    170 | 171 | 172 | # Application to distributed quantum computing 173 | 174 | Our next stage of research is applying QuNet to distributed quantum computing. Entanglement links can be used to fuse together geographically separated graph states, facilitating distributed quantum computation exponentially more powerful than the sum of the parts. 175 | 176 |

    177 | 178 | 179 | Consider a distributed computer with N nodes, each with n bits/qubits, and a scaling function that indicates classical-equivalent compute power (classically this is linear, for quantum computers super-linear). The computational gain achieved by unifying remote devices is, 180 | 181 |

    182 | $$\lambda = \frac{f_\mathrm{sc}(Nn)}{N\cdot f_\mathrm{sc}(n)}.$$ 183 |

    184 | 185 | 186 | Through unification of remote computational assets: 187 | + Classical computers, $$\lambda=1$$. There is no computational enhancement. 188 | + Quantum computers $$\lambda>1$$, in the best case $$\lambda=\mathrm{exp}(N)$$. We achieve exponential computational enhancement. 189 | 190 | # The vision 191 | 192 | In a future world where scalable quantum computers are available and ubiquitous it is clear that networking them together has the potential to provide exponentially more computational power than the sum of the parts. The big question is whether the cost of constructing the global quantum communications infrastructure necessary to facilitate this is justified by the economic return associated with this computational enhancement. 193 | 194 | Our vision for the quantum internet is presented in the upcoming book [“The Quantum Internet”](https://cup.org/2Q7UpM4) published by Cambridge University Press. 195 | 196 | # Acknowledgements 197 | 198 | We thank Darcy Morgan, Alexis Shaw, Marika Kieferova, Zixin Huang, Louis Tessler, Yuval Sanders, Jasminder Sidhu, Simon Devitt & Jon Dowling for conversation (both helpful, unhelpful, meaningless, derogatory, and diatribe). We also thank the developers of [JuliaGraphs](https://juliagraphs.org), which QuNet makes heavy use of. 199 | -------------------------------------------------------------------------------- /data/percolation.txt: -------------------------------------------------------------------------------- 1 | perc_range = (0.0, 0.01, 0.7) 2 | num_trials = 5000 3 | num_pairs = 1 4 | grid_size = 10 5 | perf_data: 6 | Dict("Z" => 0.7608558938183618,"loss" => 0.005704191866075474) Dict("Z" => 0.7575597654504125,"loss" => 0.006576246295914086) Dict("Z" => 0.753426344193894,"loss" => 0.00714247337006783) Dict("Z" => 0.7512425144129743,"loss" => 0.008622349951669505) Dict("Z" => 0.7480758060846325,"loss" => 0.009072930271775984) Dict("Z" => 0.7457931047077145,"loss" => 0.010774840367226987) Dict("Z" => 0.7348843959641059,"loss" => 0.012330699415159022) Dict("Z" => 0.7324486643368421,"loss" => 0.013593139160745714) Dict("Z" => 0.7297288558581212,"loss" => 0.015129890868625087) Dict("Z" => 0.7260932387997536,"loss" => 0.016621218933809658) Dict("Z" => 0.7264123268968632,"loss" => 0.020267519172084972) Dict("Z" => 0.7185467108437522,"loss" => 0.019566775299960183) Dict("Z" => 0.7140981432126126,"loss" => 0.022620830291317152) Dict("Z" => 0.7106829241482625,"loss" => 0.025554896711585742) Dict("Z" => 0.7053063541106481,"loss" => 0.026972437595442846) Dict("Z" => 0.699396010172815,"loss" => 0.028107207537484538) Dict("Z" => 0.6952085870474269,"loss" => 0.031744357014749015) Dict("Z" => 0.6884145966221195,"loss" => 0.033805599539913744) Dict("Z" => 0.6899406428021806,"loss" => 0.036990512383250775) Dict("Z" => 0.6843776197020257,"loss" => 0.039482728065707684) Dict("Z" => 0.6758748411584276,"loss" => 0.04190449703505146) Dict("Z" => 0.6759074376471293,"loss" => 0.043904663817478574) Dict("Z" => 0.6673030759282753,"loss" => 0.046173487321809224) Dict("Z" => 0.6644240103177673,"loss" => 0.05381219249857721) Dict("Z" => 0.6641176103984473,"loss" => 0.05646361433151676) Dict("Z" => 0.6572449335608688,"loss" => 0.057146136966914) Dict("Z" => 0.655482957303805,"loss" => 0.06367746567580167) Dict("Z" => 0.6493194924989466,"loss" => 0.06693889021999949) Dict("Z" => 0.6454943283946477,"loss" => 0.07076488058272085) Dict("Z" => 0.6443367543942062,"loss" => 0.07134280012673125) Dict("Z" => 0.6377853856848942,"loss" => 0.07625707709714973) Dict("Z" => 0.6331465925491606,"loss" => 0.08094711513771023) Dict("Z" => 0.6338397741581353,"loss" => 0.08601460154149972) Dict("Z" => 0.6269374483891372,"loss" => 0.09288868995376111) Dict("Z" => 0.6251156530033692,"loss" => 0.09626911770461256) Dict("Z" => 0.6229513311678594,"loss" => 0.10136151582827384) Dict("Z" => 0.6214394977942002,"loss" => 0.10410811993536206) Dict("Z" => 0.6181560533475159,"loss" => 0.11216375275363763) Dict("Z" => 0.61662818225547,"loss" => 0.11851226273324084) Dict("Z" => 0.6184273117003743,"loss" => 0.12728493864806123) Dict("Z" => 0.6168614092621084,"loss" => 0.12874125502447312) Dict("Z" => 0.6168519399755849,"loss" => 0.13347716954854663) Dict("Z" => 0.6149424863278203,"loss" => 0.14391483633105634) Dict("Z" => 0.6145356773991006,"loss" => 0.15045584745522272) Dict("Z" => 0.6164605655917627,"loss" => 0.15710710824317484) Dict("Z" => 0.6181265592115118,"loss" => 0.1623730876869792) Dict("Z" => 0.6203154531088689,"loss" => 0.16759975563885565) Dict("Z" => 0.6156375622126362,"loss" => 0.1736249218190511) Dict("Z" => 0.629535600158767,"loss" => 0.1932720027885772) Dict("Z" => 0.6236642466718982,"loss" => 0.1892745244673033) Dict("Z" => 0.6278335587272721,"loss" => 0.20202741831213408) Dict("Z" => 0.6378641271252176,"loss" => 0.2181010490297019) Dict("Z" => 0.6410806591853264,"loss" => 0.2291023770003015) Dict("Z" => 0.6408982789231646,"loss" => 0.24043412110333398) Dict("Z" => 0.6494114459133996,"loss" => 0.255043763709274) Dict("Z" => 0.6536711584260959,"loss" => 0.26638884406381586) Dict("Z" => 0.6593109318376609,"loss" => 0.28881627441598934) Dict("Z" => 0.6592755672082343,"loss" => 0.2923814815526248) Dict("Z" => 0.6829274079483045,"loss" => 0.3227058227274433) Dict("Z" => 0.6787524281460737,"loss" => 0.3186958294204471) Dict("Z" => 0.6922214724620387,"loss" => 0.35951764435199496) Dict("Z" => 0.699665829014606,"loss" => 0.37657898462214257) Dict("Z" => 0.706197039209331,"loss" => 0.3842030318641067) Dict("Z" => 0.7171848146098332,"loss" => 0.40536688448475106) Dict("Z" => 0.7322619114727024,"loss" => 0.44431271590406834) Dict("Z" => 0.7405577770807138,"loss" => 0.45640736656159037) Dict("Z" => 0.7437005138241735,"loss" => 0.45981804699911805) Dict("Z" => 0.7507016628390563,"loss" => 0.4784781919444151) Dict("Z" => 0.7662306746780262,"loss" => 0.49938985740710357) Dict("Z" => 0.756388592331351,"loss" => 0.4968353200567489) Dict("Z" => 0.77302900181352,"loss" => 0.5204952833923823) 7 | perf_err: 8 | Dict("Z" => 0.0018651384614712377,"loss" => 0.00029416943438317884) Dict("Z" => 0.0018429704939193358,"loss" => 0.00036711157875265836) Dict("Z" => 0.001873836492687973,"loss" => 0.00036932971586547237) Dict("Z" => 0.0018585988439170993,"loss" => 0.0004536509011219578) Dict("Z" => 0.001861345297363521,"loss" => 0.00041744580346941336) Dict("Z" => 0.0018837349838242811,"loss" => 0.0004882716006063918) Dict("Z" => 0.0018503040493710265,"loss" => 0.0006024862115420797) Dict("Z" => 0.0018522990167088385,"loss" => 0.0005926557168722437) Dict("Z" => 0.0018274784669251242,"loss" => 0.0006574927916691158) Dict("Z" => 0.0018419075071849935,"loss" => 0.0007345822351180096) Dict("Z" => 0.0018648873784454425,"loss" => 0.0008483852152076512) Dict("Z" => 0.0018481150416828603,"loss" => 0.0008229502101331789) Dict("Z" => 0.0018123802153255486,"loss" => 0.0009117610160323852) Dict("Z" => 0.00185345791856799,"loss" => 0.001030614944212079) Dict("Z" => 0.0018182111600820935,"loss" => 0.000981603155664665) Dict("Z" => 0.0018097057216627953,"loss" => 0.0010407754978312355) Dict("Z" => 0.0018238684034146597,"loss" => 0.0011051622911124061) Dict("Z" => 0.0017870927602891547,"loss" => 0.0011711932372496503) Dict("Z" => 0.0018104134636648423,"loss" => 0.0012009979871818305) Dict("Z" => 0.0018166895139708427,"loss" => 0.0012703448632347138) Dict("Z" => 0.0017664524017865333,"loss" => 0.0012857355168351924) Dict("Z" => 0.001787590205609837,"loss" => 0.0013320155077318505) Dict("Z" => 0.0017788257519532429,"loss" => 0.0013581919858393545) Dict("Z" => 0.0017974806921823593,"loss" => 0.0014693059942443853) Dict("Z" => 0.0018029847287494842,"loss" => 0.0015791378734606317) Dict("Z" => 0.001787990700350101,"loss" => 0.001505875594997508) Dict("Z" => 0.0017714679578244598,"loss" => 0.0016001425163023057) Dict("Z" => 0.0017557922434718464,"loss" => 0.0016932841184138727) Dict("Z" => 0.0017336582014447148,"loss" => 0.0017437389640747912) Dict("Z" => 0.0017793622951773108,"loss" => 0.0017234507445658429) Dict("Z" => 0.0017702341221555093,"loss" => 0.0017733776132189546) Dict("Z" => 0.0017371409492530833,"loss" => 0.001871426226475987) Dict("Z" => 0.0017962884640713276,"loss" => 0.0019462949246704408) Dict("Z" => 0.0017333331777375533,"loss" => 0.0020243815220896836) Dict("Z" => 0.0017466750715543662,"loss" => 0.0021121768689223745) Dict("Z" => 0.0017822039076315441,"loss" => 0.00214140029006785) Dict("Z" => 0.0018265903814378456,"loss" => 0.002220911137765155) Dict("Z" => 0.0017741139765496765,"loss" => 0.002307207790064428) Dict("Z" => 0.0018140236295035284,"loss" => 0.0024280549430591727) Dict("Z" => 0.0019173007034690344,"loss" => 0.0026166054294720306) Dict("Z" => 0.001898137413304383,"loss" => 0.0026237882153838325) Dict("Z" => 0.002014855698937582,"loss" => 0.0028812957479166525) Dict("Z" => 0.0019533228909035896,"loss" => 0.002901942705571711) Dict("Z" => 0.0020799738067743922,"loss" => 0.003103554239757068) Dict("Z" => 0.0021454527034499445,"loss" => 0.0032889323541931527) Dict("Z" => 0.0022640537278014396,"loss" => 0.003492403526187664) Dict("Z" => 0.0023600703988615277,"loss" => 0.0036806184172291996) Dict("Z" => 0.002441499102042597,"loss" => 0.003914012201042555) Dict("Z" => 0.0026977933604235066,"loss" => 0.004336272276710602) Dict("Z" => 0.002676873584826682,"loss" => 0.0043615724184452205) Dict("Z" => 0.002895086691308763,"loss" => 0.004843481554862928) Dict("Z" => 0.0032461412136516833,"loss" => 0.005348517564920444) Dict("Z" => 0.00323095607571213,"loss" => 0.005514592740246759) Dict("Z" => 0.0034934226342727757,"loss" => 0.006198584399951891) Dict("Z" => 0.003739515117655556,"loss" => 0.006520827968168393) Dict("Z" => 0.003888081826614706,"loss" => 0.006966614982655091) Dict("Z" => 0.004022710476537404,"loss" => 0.007517031339618218) Dict("Z" => 0.004244406809858353,"loss" => 0.007916202632828412) Dict("Z" => 0.004748843553318537,"loss" => 0.008661358463324963) Dict("Z" => 0.005018964483051573,"loss" => 0.009309962423243603) Dict("Z" => 0.005264200463404773,"loss" => 0.010076530659354018) Dict("Z" => 0.005724310119631225,"loss" => 0.0110602304474982) Dict("Z" => 0.005577294976966923,"loss" => 0.010546740140849015) Dict("Z" => 0.00582229780991454,"loss" => 0.011015953668812835) Dict("Z" => 0.006438726781026524,"loss" => 0.012516197086919803) Dict("Z" => 0.006476081273518954,"loss" => 0.012502286684439907) Dict("Z" => 0.006733155900500328,"loss" => 0.012770771895064256) Dict("Z" => 0.0068217357188688835,"loss" => 0.013668640858770672) Dict("Z" => 0.007132744033698661,"loss" => 0.014112899694148738) Dict("Z" => 0.008076367243203824,"loss" => 0.01590796900233018) Dict("Z" => 0.008293056684446376,"loss" => 0.016270078248066557) 9 | path_data: 10 | [0.0, 0.0, 0.082, 0.524, 0.394] [0.0, 0.0026, 0.0992, 0.5194, 0.3788] [0.0, 0.0056, 0.117, 0.527, 0.3504] [0.0002, 0.0068, 0.14, 0.5324, 0.3206] [0.0002, 0.0086, 0.1652, 0.533, 0.293] [0.0002, 0.0124, 0.1794, 0.5518, 0.2562] [0.0006, 0.021, 0.2056, 0.5298, 0.243] [0.0008, 0.0276, 0.2264, 0.5244, 0.2208] [0.0004, 0.0332, 0.234, 0.532, 0.2004] [0.002, 0.039, 0.2602, 0.523, 0.1758] [0.0012, 0.0438, 0.2952, 0.4972, 0.1626] [0.0022, 0.0506, 0.3044, 0.5004, 0.1424] [0.0038, 0.0668, 0.317, 0.484, 0.1284] [0.0042, 0.0714, 0.3526, 0.4552, 0.1166] [0.007, 0.0844, 0.3664, 0.4396, 0.1026] [0.0084, 0.0952, 0.3844, 0.4254, 0.0866] [0.0086, 0.113, 0.3862, 0.4112, 0.081] [0.01, 0.122, 0.4282, 0.3774, 0.0624] [0.0112, 0.1368, 0.4332, 0.3622, 0.0566] [0.0174, 0.1508, 0.442, 0.3408, 0.049] [0.0188, 0.1728, 0.459, 0.3102, 0.0392] [0.023, 0.1838, 0.457, 0.298, 0.0382] [0.0214, 0.2164, 0.4456, 0.2804, 0.0362] [0.0282, 0.2438, 0.452, 0.2482, 0.0278] [0.0356, 0.2524, 0.4536, 0.2388, 0.0196] [0.046, 0.2696, 0.4702, 0.1982, 0.016] [0.045, 0.297, 0.459, 0.1824, 0.0166] [0.0554, 0.3244, 0.4536, 0.1564, 0.0102] [0.0604, 0.3454, 0.4424, 0.143, 0.0088] [0.074, 0.3616, 0.4262, 0.1308, 0.0074] [0.0872, 0.3924, 0.4016, 0.1134, 0.0054] [0.0892, 0.419, 0.3966, 0.0908, 0.0044] [0.111, 0.4354, 0.3684, 0.0814, 0.0038] [0.1192, 0.4676, 0.341, 0.07, 0.0022] [0.1436, 0.4716, 0.3276, 0.0554, 0.0018] [0.168, 0.4922, 0.2924, 0.0456, 0.0018] [0.196, 0.4904, 0.277, 0.0352, 0.0014] [0.2118, 0.5134, 0.2446, 0.0292, 0.001] [0.2356, 0.5238, 0.216, 0.024, 0.0006] [0.267, 0.5272, 0.1818, 0.0232, 0.0008] [0.2904, 0.5148, 0.1754, 0.0194, 0.0] [0.3276, 0.5018, 0.155, 0.0154, 0.0002] [0.3618, 0.4988, 0.13, 0.009, 0.0004] [0.411, 0.475, 0.1076, 0.0062, 0.0002] [0.4386, 0.4582, 0.0992, 0.004, 0.0] [0.4746, 0.4362, 0.0844, 0.0048, 0.0] [0.5154, 0.406, 0.0748, 0.0038, 0.0] [0.5616, 0.383, 0.0532, 0.0022, 0.0] [0.5846, 0.3598, 0.0532, 0.0024, 0.0] [0.6256, 0.3296, 0.0422, 0.0026, 0.0] [0.665, 0.2996, 0.0334, 0.002, 0.0] [0.6974, 0.2706, 0.0298, 0.0022, 0.0] [0.7136, 0.2566, 0.0294, 0.0004, 0.0] [0.7472, 0.2338, 0.0188, 0.0002, 0.0] [0.78, 0.2036, 0.0162, 0.0002, 0.0] [0.7962, 0.1886, 0.0152, 0.0, 0.0] [0.817, 0.173, 0.0094, 0.0006, 0.0] [0.841, 0.152, 0.007, 0.0, 0.0] [0.8562, 0.1338, 0.0098, 0.0002, 0.0] [0.8712, 0.1208, 0.0076, 0.0004, 0.0] [0.8816, 0.1138, 0.0042, 0.0004, 0.0] [0.9088, 0.0878, 0.0034, 0.0, 0.0] [0.899, 0.0964, 0.0046, 0.0, 0.0] [0.9074, 0.0886, 0.004, 0.0, 0.0] [0.9314, 0.0664, 0.0022, 0.0, 0.0] [0.9356, 0.062, 0.0024, 0.0, 0.0] [0.9386, 0.0588, 0.0026, 0.0, 0.0] [0.942, 0.056, 0.002, 0.0, 0.0] [0.9492, 0.0482, 0.0024, 0.0002, 0.0] [0.9618, 0.0372, 0.001, 0.0, 0.0] [0.959, 0.0394, 0.0016, 0.0, 0.0] 11 | path_err: 12 | [0.0, 0.0, 0.0038804911599343277, 0.007063623641997858, 0.006911031216142357] [0.0, 0.0007202442296541291, 0.004227936504894308, 0.007066449953335026, 0.006860869209164463] [0.0, 0.001055438671907578, 0.004546023896401484, 0.007061456849302902, 0.006747819989813225] [0.00019999999999999996, 0.0011623348073773268, 0.004907628445939307, 0.0070569120726845045, 0.006600897088159581] [0.0001999999999999999, 0.0013059667338113804, 0.005252362274433509, 0.007056355888961829, 0.0064372733123225] [0.00019999999999999996, 0.0015651638724394057, 0.005426694924496927, 0.007033722104926371, 0.0061741344322258996] [0.0003463408586899323, 0.0020279601880966256, 0.005715965574816779, 0.007059203812873967, 0.00606609920063152] [0.00039987995798259165, 0.0023170502248222556, 0.005919089045032572, 0.00706334950156342, 0.005866545429045463] [0.00028281442113046486, 0.002533936890933445, 0.005987985570643841, 0.007057277168441553, 0.005661658668250422] [0.0006318859517121696, 0.002738119694223023, 0.0062053922824166175, 0.007064289124615952, 0.005383741165192356] [0.0004896528892903607, 0.002894475357905039, 0.006451334431200156, 0.007071664138428613, 0.00521897456777745] [0.000662661168294018, 0.0030999758015174883, 0.006508194781954822, 0.007071772761761989, 0.004942603956091702] [0.0008702088557872401, 0.003531295231101734, 0.006581098857518741, 0.007068153348531118, 0.004731507733596832] [0.0009146799045926248, 0.0036418485158120494, 0.006757498113662427, 0.007043331151006639, 0.004539274061102761] [0.0011791853525290768, 0.003931719685320507, 0.006814653028524627, 0.007019987467567795, 0.004291658381830182] [0.0012908219258475787, 0.004151004476946939, 0.0068801740791256, 0.006992620921333425, 0.003977845227070819] [0.0013059667338113801, 0.004477746089497304, 0.006886174255649926, 0.006959353311889254, 0.003858857104847382] [0.0014072654615302125, 0.004628983203371924, 0.006998481817476551, 0.006855891528439606, 0.0034210508206899723] [0.001488406890273692, 0.004860234189344692, 0.007008378947737847, 0.006797902198094565, 0.0032682448324531897] [0.0018493598963980162, 0.0050613234829518456, 0.007024034980940991, 0.006703734780851818, 0.00305314007750489] [0.0019209502882693247, 0.005347312479194575, 0.007047959615871059, 0.0065424576995613895, 0.00274484586739714] [0.0021201648567514652, 0.005478094002733118, 0.007045575067061955, 0.006468969741303692, 0.002711018613802729] [0.0020467647273611674, 0.005824173091387281, 0.0070297945633084075, 0.0063532071151607885, 0.002641837970347977] [0.00234137745989021, 0.006072864887150667, 0.007039112857595368, 0.006109567690391253, 0.0023251910249827926] [0.0026206681553214753, 0.006143809671486897, 0.007041258684693595, 0.0060301065029473066, 0.001960596028598821] [0.002962862756042242, 0.00627622866132535, 0.007059203812873968, 0.005638236421256951, 0.0017746633162313344] [0.0029320162591412713, 0.006462704798353563, 0.00704795961587106, 0.005461869130881895, 0.0018070808250384494] [0.0032354693094403574, 0.006621306241163283, 0.007041258684693595, 0.0051374202297771445, 0.0014211248781102373] [0.0033693676747582084, 0.006725237078553626, 0.007024693392843043, 0.004951272868728215, 0.0013209318575136415] [0.0037023697152366703, 0.006795463462099769, 0.0069943188445494705, 0.004768943338320642, 0.001212164126005087] [0.0039902902755685156, 0.006906083318159395, 0.006933476950313176, 0.0044846527649400475, 0.0010365244216010133] [0.004031367309546093, 0.006978362236793209, 0.006918906145659912, 0.004063788241896346, 0.0009361107096129874] [0.004442943613145447, 0.007012503336227952, 0.006822433372296417, 0.0038675314555814523, 0.0008702088557872399] [0.004582845458282323, 0.0070569120726845045, 0.006704684225085592, 0.0036086845970386737, 0.000662661168294018] [0.004959912110675866, 0.007060358187198881, 0.006638106724976512, 0.0032354693094403574, 0.0005995197117501268] [0.005287796531489429, 0.0070709144787865505, 0.006433407009132023, 0.0029505709911531986, 0.0005995197117501267] [0.0056145440260918925, 0.007070471435007897, 0.006329471734867651, 0.00260644407385657, 0.0005288326132024737] [0.005778826088938239, 0.0070692349527062244, 0.006079601986319697, 0.002381303450050885, 0.0004470346384717842] [0.006002144045808073, 0.007063759009073542, 0.00582027276981268, 0.002164656357815135, 0.00034634085868993247] [0.0062569984821798695, 0.00706130330752618, 0.005454878838308705, 0.002129145051829571, 0.00039987995798259154] [0.006420421509719786, 0.007068676342643469, 0.00537891760030865, 0.0019507662847762064, 0.0] [0.006638106724976512, 0.007071729199479724, 0.00511861700540386, 0.0017416011698798844, 0.00019999999999999996] [0.006796277648682074, 0.007071754657988957, 0.004756524456468295, 0.0013357233363860469, 0.00028281442113046475] [0.006958842223891804, 0.007062929774208369, 0.004382726281429356, 0.0011102065175913622, 0.00019999999999999996] [0.007018251860102655, 0.007047019518691527, 0.004227936504894308, 0.0008927258212230538, 0.0] [0.007062644277382071, 0.007013968288367868, 0.003931719685320507, 0.0009775393171751838, 0.0] [0.007068419944521015, 0.006945678403849257, 0.0037207204614362597, 0.0008702088557872401, 0.0] [0.007017901266592276, 0.006875438482807202, 0.0031742664024052387, 0.000662661168294018, 0.0] [0.006969812311442205, 0.006788078343796596, 0.0031742664024052387, 0.0006920576484352669, 0.0] [0.006845020004336871, 0.0066484290115533555, 0.0028434924107192472, 0.0007202442296541294, 0.0] [0.00667562077881574, 0.006478916828656973, 0.002541294873863948, 0.0006318859517121696, 0.0] [0.0064973141387964055, 0.006283551909940826, 0.002404900977117757, 0.000662661168294018, 0.0] [0.006393995983493218, 0.006177290650759183, 0.002389198537989421, 0.0002828144211304648, 0.0] [0.006147030925401267, 0.005986207391645162, 0.0019209502882693243, 0.00019999999999999996, 0.0] [0.005858913326980918, 0.0056952519855609385, 0.0017855390306364028, 0.00019999999999999996, 0.0] [0.005697333053349102, 0.0055328230069364446, 0.0017304308359988364, 0.0, 0.0] [0.005468837320338509, 0.005349759245498525, 0.0013648078766288028, 0.00034634085868993247, 0.0] [0.005171958026701881, 0.00507782993722459, 0.0011791853525290766, 0.0, 0.0] [0.004962785291302608, 0.004814992090311879, 0.0013932624433491375, 0.00019999999999999996, 0.0] [0.004737784431595011, 0.004609308098210495, 0.0012283117478677755, 0.0002828144211304649, 0.0] [0.004569514645213269, 0.004491541694046473, 0.000914679904592625, 0.00028281442113046475, 0.0] [0.004071833489423788, 0.004002678639018807, 0.0008233004097611894, 0.0, 0.0] [0.004261857891405732, 0.00417431347392547, 0.0009570533894397153, 0.0, 0.0] [0.004099806054185539, 0.004019109185843245, 0.0008927258212230538, 0.0, 0.0] [0.003575103393309706, 0.0035214610799535356, 0.000662661168294018, 0.0, 0.0] [0.0034717342334512785, 0.003410795611780493, 0.0006920576484352669, 0.0, 0.0] [0.003395337047000275, 0.003327270073983932, 0.0007202442296541294, 0.0, 0.0] [0.0033059621711652156, 0.0032519094364690717, 0.0006318859517121696, 0.0, 0.0] [0.003105769012697507, 0.003029387303975425, 0.0006920576484352668, 0.0001999999999999999, 0.0] [0.002711018613802729, 0.0026766891737716935, 0.0004470346384717843, 0.0, 0.0] [0.002804527246188728, 0.0027515526904716356, 0.0005652892271944303, 0.0, 0.0] 13 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Unit tests for QuNet 3 | 4 | Hudson's style convention for testing: 5 | 6 | + Redundency is good. 7 | + Don't aim to be redundant, but don't aim for tight scripting either. 8 | + If you want to manipulate a network from the network-library, (I.E. refreshing, 9 | adding temporal links, percolating etc.) do it on a copy. 10 | + One test set per file. Test every function in a given file thoroughly 11 | + Each test or subset of tests within the testset should begin with a comment 12 | briefly explaining what it's testing. Ideally, the test should work completely independently 13 | from other tests. 14 | + If you find something here that doesn't follow the style guide but it works regardless, 15 | don't change it. 16 | + Redundency is good 17 | 18 | """ 19 | 20 | using QuNet 21 | using Test 22 | using LightGraphs 23 | using SimpleWeightedGraphs 24 | 25 | include_networks = ["barbell", "simple_network", "simple_satnet", 26 | "small_square", "shortest_path_test", "smalltemp", "greedy_test", "bridge"] 27 | 28 | for N in include_networks 29 | include("network-library/$N.jl") 30 | end 31 | 32 | @testset "Network.jl" begin 33 | # Test: QNetwork is correctly initialised 34 | @test(typeof(barbell) == QNetwork) 35 | 36 | # Test: Nodes are correctly added 37 | @test length(barbell.nodes) == 2 38 | 39 | # Test: Channels are correctly added 40 | @test length(barbell.channels) == 1 41 | 42 | # Test: getnode works for id 43 | newnode = QuNet.getnode(barbell, 1) 44 | @test newnode == barbell.nodes[1] 45 | 46 | # Test: getnode works for name 47 | newnode = QuNet.getnode(barbell, "A") 48 | @test newnode == barbell.nodes[1] 49 | 50 | # Test: getchannel works for id 51 | newchannel = QuNet.getchannel(barbell, 1, 2) 52 | @test newchannel == barbell.channels[1] 53 | 54 | # Test: getchannel works for string 55 | newerchannel = QuNet.getchannel(barbell, "A", "B") 56 | @test newerchannel == barbell.channels[1] 57 | 58 | # Test: Update a sat network and check that the costs have changed 59 | AS = QuNet.getchannel(simple_satnet, "A", "S") 60 | old_costs = AS.costs 61 | update(simple_satnet) 62 | new_costs = AS.costs 63 | for key in keys(old_costs) 64 | @test old_costs[key] != new_costs[key] 65 | end 66 | 67 | # Test: Reset the network back to t=0 and check position goes back to init. 68 | S = QuNet.getnode(simple_satnet, "S") 69 | update(simple_satnet, 0.0) 70 | @test S.location.x == 500 71 | 72 | # Test: Check that deepcopy can clone network structure 73 | Q = deepcopy(barbell) 74 | @test all(Q.nodes[i] != barbell.nodes[i] for i in 1:length(Q.nodes)) 75 | @test cmp(string(Q), string(barbell)) == 0 76 | 77 | # Test: refresh_graph! creates SimpleWeightedGraph copies of Network for all costs 78 | Q = deepcopy(barbell) 79 | QuNet.refresh_graph!(Q) 80 | @test length(Q.graph) == length(zero_costvector()) 81 | g = Q.graph["Z"] 82 | @test nv(g) == 2 83 | @test ne(g) == 2 84 | @test g.weights[1, 2] == 0.5 85 | @test g.weights[2, 1] == 0.5 86 | 87 | # Test refresh_graph! on satellite network 88 | Q = deepcopy(simple_satnet) 89 | QuNet.refresh_graph!(Q) 90 | g = Q.graph["loss"] 91 | @test nv(g) == 3 92 | @test ne(g) == 4 93 | 94 | # Test 12: Test that update works on copied graph 95 | Q = deepcopy(barbell) 96 | update(Q) 97 | @test cmp(string(Q), string(barbell)) != 0 98 | 99 | # Test 13 / 14: Test that getchannel fetches the right channel in 100 | # a copied graph 101 | Q = deepcopy(barbell) 102 | AB = QuNet.getchannel(barbell, "A", "B") 103 | CAB = QuNet.getchannel(Q, "A", "B") 104 | @test (AB in barbell.channels) && (CAB in Q.channels) 105 | @test !(CAB in barbell.channels) && !(AB in Q.channels) 106 | end 107 | 108 | 109 | @testset "Channel.jl" begin 110 | #Test Basic Channel is initalised with costs 111 | AB = BasicChannel(A, B) 112 | @test typeof(AB) == BasicChannel 113 | 114 | # Test AirChannel is a subtype of QChannel 115 | AS = AirChannel(A, S) 116 | @test typeof(AS) <: QuNet.QChannel 117 | end 118 | 119 | 120 | @testset "Node.jl" begin 121 | # Test Basic node is initalised 122 | A = BasicNode("A") 123 | @test A.name == "A" 124 | 125 | # Test node properties can be updated 126 | B = BasicNode("B") 127 | B.location = Coords(100, 0, 0) 128 | @test isequal(B.location.x, 100) 129 | 130 | # Test PlanSatNode is initialised 131 | S = PlanSatNode("S") 132 | S.location = Coords(0, 0, 1000) 133 | S.velocity = Velocity(1000, 0, 0) 134 | end 135 | 136 | 137 | @testset "Utilities.jl" begin 138 | # Test maximal coherence 139 | Z = 1.0 140 | dB = Z_to_dB(Z) 141 | @test dB == 0 142 | Zn = dB_to_Z(dB) 143 | @test Zn == 1 144 | 145 | # Test maximal decoherence 146 | Z = 0.5 147 | dB = Z_to_dB(Z) 148 | @test dB == Inf 149 | 150 | Z = dB_to_Z(dB) 151 | @test Z == 0.5 152 | 153 | # Test decoherence < 0.5 154 | Z = 0.3 155 | @test_throws DomainError Z_to_dB(Z) 156 | 157 | # Test Purification 158 | cv1 = Dict() 159 | cv2 = Dict() 160 | cv1["loss"] = 1.0 161 | cv2["loss"] = 2.0 162 | cv1["Z"] = 1.0 163 | cv2["Z"] = 2.0 164 | result = purify([cv1, cv2]) 165 | # TODO: Compare with hand calc 166 | end 167 | 168 | 169 | @testset "CostVector.jl" begin 170 | # Test: convert_costs to decibelic form 171 | cost_vector = Dict("loss"=>0.5, "Z"=>0.5) 172 | dB_cv = convert_costs(cost_vector, false) 173 | @test (cost_vector["loss"] == 3.0102999566398116 && 174 | cost_vector["Z"]== Inf) 175 | 176 | # Test: convert_cost from decibelic form 177 | metric_cv = convert_costs(cost_vector, true) 178 | @test cost_vector == metric_cv 179 | 180 | # Test: get_pathcv for a vector of QChannels 181 | Q = QNetwork() 182 | A = BasicNode("A") 183 | S = PlanSatNode("S") 184 | S.location = Coords(0,0,1000) 185 | C = AirChannel(A, S) 186 | for i in [A, S, C] 187 | add(Q, i) 188 | end 189 | cost_vector = get_pathcv([C]) 190 | @test(cost_vector["loss"] == 0.4970454276591223 && 191 | cost_vector["Z"]== 0.49694603901995044) 192 | 193 | # Test: get_pathcv for a single QChannel 194 | Q = deepcopy(barbell) 195 | channel = Q.channels[1] 196 | cv = get_pathcv(channel) 197 | @test(cv["loss"] == 1 && cv["Z"] == 0.5) 198 | 199 | # Test: get_pathcv for a QNetwork, and a path of Int tuples 200 | Q = deepcopy(simple_network) 201 | path = [(1,2),(2,3)] 202 | pathcv = get_pathcv(Q, path) 203 | @test pathcv["loss"] == 2 && pathcv["Z"] == 2 204 | 205 | # Test: get_pathcv for a TemporalGraph and a path of Int tuples 206 | T = deepcopy(smalltemp) 207 | path = [(1,5)] 208 | pathcv = get_pathcv(T, path) 209 | @test pathcv["loss"] == 1.0 && pathcv["Z"] == 1.0 210 | end 211 | 212 | 213 | @testset "Routing.jl" begin 214 | # Test: shortest_path for a SimpleWeightedDiGraph 215 | g = SimpleWeightedDiGraph(2) 216 | add_edge!(g, 1, 2, 3.14) 217 | short_path = shortest_path(g, 1, 2) 218 | @test(short_path[1] == SimpleWeightedEdge(1, 2, 3.14)) 219 | 220 | # Test: path_length 221 | @test(3.14 == QuNet.path_length(g, short_path)) 222 | 223 | # Test: remove_shortest_path! for a SimpleWeightedDiGraph 224 | removed_path, removed_path_cost = QuNet.remove_shortest_path!(g, 1, 2) 225 | @test(removed_path_cost == 3.14) 226 | @test(length(edges(g)) == 0) 227 | 228 | # Test: remove_shortest_path! for a QNetwork 229 | Q = deepcopy(shortest_path_test) 230 | removed_path, removed_cv = QuNet.remove_shortest_path!(Q, "loss", 1, 2) 231 | # Test removed path is correct 232 | shortestpath = [(1,3),(3,2)] 233 | shortestpath = QuNet.int_to_simpleedge(shortestpath) 234 | @test(shortestpath == removed_path) 235 | 236 | # Test removed path costs are correct 237 | @test(removed_cv["Z"] == 10 && removed_cv["loss"] == 2) 238 | 239 | # Test that the correct path was removed for both weighted graphs of the QNetwork 240 | # And check that edges are removed in both directions 241 | G = Q.graph["loss"] 242 | @test has_edge(G, 1, 3) == false && has_edge(G, 3, 1) == false && 243 | has_edge(G, 3, 2) == false && has_edge(G, 2, 3) == false 244 | 245 | # Check that no nodes and no other edges were removed 246 | @test nv(G) == length(Q.nodes) 247 | @test ne(G) == (length(Q.channels) - 2) * 2 248 | 249 | G = Q.graph["Z"] 250 | @test has_edge(G, 1, 3) == false && has_edge(G, 3, 1) == false && 251 | has_edge(G, 3, 2) == false && has_edge(G, 2, 3) == false 252 | 253 | # Check that no nodes and no other edges were removed 254 | @test nv(G) == length(Q.nodes) 255 | @test ne(G) == (length(Q.channels) - 2) * 2 256 | 257 | # Test: remove_shortest_path! for a TemporalGraph returning cost vector 258 | # and using asynchronus nodes 259 | T = deepcopy(smalltemp) 260 | src = 1+8 261 | dst = 2+8 262 | QuNet.add_async_nodes!(T, [(src, dst)]) 263 | removed_path, removed_cv = QuNet.remove_shortest_path!(T, "loss", src, dst) 264 | # Test removed path is correct 265 | shortestpath = [(1,2)] 266 | shortestpath = QuNet.int_to_simpleedge(shortestpath) 267 | @test(shortestpath == removed_path) 268 | # Test removed path cost vectors is correct. 269 | @test removed_cv["loss"] == 1 && removed_cv["Z"] == 1 270 | 271 | # Remove shortest path again, and test that the path in the next temporal layer was removed 272 | removed_path, removed_cv = QuNet.remove_shortest_path!(T, "loss", src, dst) 273 | shortestpath = [(5,6)] 274 | shortestpath = QuNet.int_to_simpleedge(shortestpath) 275 | @test(shortestpath == removed_path) 276 | 277 | # Remove shortest path one final time: 278 | # test that the path now takes a longer route in first temporal layer 279 | removed_path, removed_cv = QuNet.remove_shortest_path!(T, "loss", src, dst) 280 | shortestpath = [(1,3),(3,4),(4,2)] 281 | shortestpath = QuNet.int_to_simpleedge(shortestpath) 282 | @test(shortestpath == removed_path) 283 | # test that the costs of this path are correct 284 | @test(removed_cv["loss"] == 3.0 && removed_cv["Z"] == 3.0) 285 | 286 | # Test that the correct path was removed for both weighted graphs of the TempNet 287 | # And check that edges are removed in both directions 288 | G = T.graph["loss"] 289 | @test has_edge(G, 1, 3) == false && has_edge(G, 3, 1) == false && 290 | has_edge(G, 3, 4) == false && has_edge(G, 4, 3) == false && 291 | has_edge(G, 4, 2) == false && has_edge(G, 2, 4) == false 292 | 293 | G = T.graph["loss"] 294 | @test has_edge(G, 1, 3) == false && has_edge(G, 3, 1) == false && 295 | has_edge(G, 3, 4) == false && has_edge(G, 4, 3) == false && 296 | has_edge(G, 4, 2) == false && has_edge(G, 2, 4) == false 297 | 298 | # Test: Check that no nodes and no other channels are removed for TempNet 299 | T = deepcopy(smalltemp) 300 | QuNet.add_async_nodes!(T, [(1, 2)]) 301 | removed_path, removed_cv = QuNet.remove_shortest_path!(T, "loss", 1, 2) 302 | QuNet.remove_async_nodes!(T) 303 | 304 | G = T.graph["loss"] 305 | @test nv(G) == 8 306 | @test ne(G) == 18 307 | 308 | G = T.graph["Z"] 309 | @test nv(G) == 8 310 | @test ne(G) == 18 311 | 312 | #Test: greedy_multi_path 313 | Q = deepcopy(greedy_test) 314 | pathset, pur_paths, pathuse_count = QuNet.greedy_multi_path!(Q, purify, [(1, 2)]) 315 | # Test that all three paths were purified together. 316 | @test pathuse_count == [0, 0, 0, 1] 317 | 318 | # Test that the pathset contains all the paths from "A" to "B" 319 | all_paths = [[(1,3),(3,2)], [(1,4), (4,2)], [(1,5), (5,2)]] 320 | new_pathset = [] 321 | for path in pathset[1] 322 | path = QuNet.simpleedge_to_int(path) 323 | push!(new_pathset, path) 324 | end 325 | @test new_pathset == all_paths 326 | 327 | # Test: greedy_multi_path on a TemporalGraph with 2 asynchronus end-users 328 | T = deepcopy(smalltemp) 329 | offset = smalltemp.nv * smalltemp.steps 330 | src1 = 1 + offset 331 | dst1 = 4 + offset 332 | src2 = 2 + offset 333 | dst2 = 3 + offset 334 | QuNet.add_async_nodes!(T, [(src1, dst1), (src2, dst2)]) 335 | pathset, pur_paths, pathuse_count = QuNet.greedy_multi_path!(T, purify, [(src1, dst1), (src2, dst2)]) 336 | # Test that 2 edge-disjoint paths were found for each usepair 337 | @test(pathuse_count[3] == 2) 338 | # Test that the pathsets have the same destinations 339 | for bundle in pathset 340 | dsts = [] 341 | for path in bundle 342 | push!(dsts, last(path).dst) 343 | end 344 | @test(all(i == dsts[1] for i in dsts)) 345 | end 346 | 347 | # # Test: A grid lattice completely saturated with end-users has no purification 348 | # # i.e. no end-user used 2 or more paths 349 | # NOTE: I actually proved myself wrong on this one. Purification of 2 and even 3 350 | # Paths is possible depending on the choice of userpair. Will leave the code here 351 | # As proof: 352 | # Q = GridNetwork(10, 10) 353 | # userpairs = make_user_pairs(Q, 50) 354 | # pathset, purpaths, pathuse_count = QuNet.greedy_multi_path!(Q, purify, userpairs) 355 | # @test pathuse_count[3] == 0 356 | # @test pathuse_count[4] == 0 357 | 358 | # # Test that Greedy_multi_path and other routing algorithms can distinguish between 359 | # # The asynchronous edges of a temporal graph (i.e. prefering short times over long ones) 360 | # Q = deepcopy(bridge) 361 | # T = QuNet.TemporalGraph(Q, 2) 362 | # # Make asynchronus userpairs 363 | # QuNet.add_async_nodes!(T, [(13,17), (14,18)]) 364 | # pathset, pur_paths, pathuse_count = QuNet.greedy_multi_path!(T, purify, [(13,17), (14,18)]) 365 | # # Expected output: [[(1-3),(3-4),(4-5)],(2-8),(8-9),(9-10),(10-12)] 366 | # println(pathset) 367 | end 368 | 369 | 370 | @testset "TemporalGraphs.jl" begin 371 | # Test: TemporalGraph initialises correctly 372 | G = GridNetwork(2, 2) 373 | T = QuNet.TemporalGraph(G, 2) 374 | @test nv(T.graph["loss"]) == 8 375 | @test T.nv == 4 376 | 377 | # Test: TemporalGraph attribute memory_prob works 378 | G = GridNetwork(10, 10) 379 | T = QuNet.TemporalGraph(G, 2, memory_prob=0.0) 380 | @test ne(T.graph["Z"]) == 720 381 | 382 | # Test: TemporalGraph adds 100 more edges when memory_prob = 100% 383 | T = QuNet.TemporalGraph(G, 2, memory_prob=1.0) 384 | @test ne(T.graph["Z"]) == 820 385 | 386 | # Test TemporalGraph with a default memory_cost, check that temporal edges have that cost. 387 | T = QuNet.TemporalGraph(G, 2, memory_prob=1.0, memory_costs=Dict("Z"=>3, "loss"=>4)) 388 | graph = T.graph["Z"] 389 | @test graph.weights[101, 1] == 3 390 | graph = T.graph["loss"] 391 | @test graph.weights[104, 4] == 4 392 | 393 | # Test add_async_nodes! for endusers that are both temporal 394 | G = deepcopy(barbell) 395 | T = QuNet.TemporalGraph(G, 3) 396 | @test T.has_async_nodes == false 397 | async_pairs = [(7,8)] 398 | QuNet.add_async_nodes!(T, async_pairs) 399 | @test nv(T.graph["loss"]) == 8 400 | @test nv(T.graph["Z"]) == 8 401 | @test T.nv == 2 402 | @test T.has_async_nodes == true 403 | 404 | # Test add_async_nodes! won't let you double dip. Uncomment for good warning 405 | # QuNet.add_async_nodes!(T, async_pairs) 406 | 407 | # Test that async edges were added to the correct nodes and in the proper directions 408 | g = T.graph["loss"] 409 | # async_pairs = [(7,8)] 410 | # usage: g.weights[dst, src] 411 | @test g.weights[1,7] != 0 && g.weights[7,1] == 0 412 | @test g.weights[2,8] == 0 && g.weights[8,2] != 0 413 | @test g.weights[3,7] != 0 && g.weights[7,3] == 0 414 | @test g.weights[4,8] == 0 && g.weights[8,4] != 0 415 | @test g.weights[5,7] != 0 && g.weights[7,5] == 0 416 | @test g.weights[6,8] == 0 && g.weights[8,6] != 0 417 | 418 | # Test add_async_nodes! for endusers where src is fixed on the top plane and dst is asynchronus 419 | G = deepcopy(barbell) 420 | T = QuNet.TemporalGraph(G, 2) 421 | async_pairs = [(1, 6)] 422 | QuNet.add_async_nodes!(T, async_pairs) 423 | g = T.graph["Z"] 424 | # Test that no async links were added to src 425 | @test g.weights[1,5] == 0 && g.weights[5,1] == 0 426 | 427 | # # Test that async links were added to dst and in proper direction 428 | # # usage: g.weights[dst, src] 429 | @test g.weights[6, 2] != 0 && g.weights[2, 6] == 0 430 | @test g.weights[6, 4] != 0 && g.weights[4, 6] == 0 431 | 432 | # Test remove_async_nodes! 433 | G = deepcopy(barbell) 434 | T = QuNet.TemporalGraph(G, 2) 435 | async_pairs = make_user_pairs(T, 1) 436 | QuNet.add_async_nodes!(T, async_pairs) 437 | QuNet.remove_async_nodes!(T) 438 | @test nv(T.graph["loss"]) == 4 439 | @test T.has_async_nodes == false 440 | 441 | # Test that remove_async_nodes! won't let you double dip 442 | # uncomment for a useful warning 443 | # QuNet.remove_async_nodes!(T) 444 | 445 | # Test fix async_nodes_in_time 446 | Q = deepcopy(barbell) 447 | depth = 5 448 | T = QuNet.TemporalGraph(Q, depth) 449 | src = 1 + 2*depth 450 | dst = 2 + 2*depth 451 | user_pairs = [(src, dst)] 452 | QuNet.add_async_nodes!(T, user_pairs) 453 | # Fix the third node 454 | QuNet.fix_async_nodes_in_time!(T, [3]) 455 | 456 | g = T.graph["loss"] 457 | users = filter(isodd, 1:depth) 458 | for user in users 459 | if user == 3 460 | @test has_edge(g, src, user) == true 461 | else 462 | @test has_edge(g, src, user) == false 463 | end 464 | end 465 | 466 | # TODO: Not working 467 | # # Test remove_async_edges! for specific time layers 468 | # Q = deepcopy(barbell) 469 | # T = QuNet.TemporalGraph(Q, 5) 470 | # user_pairs = [(11, 12)] 471 | # QuNet.add_async_nodes!(T, user_pairs) 472 | # QuNet.remove_async_edges!(T, 3) 473 | # # Async_edges should have been removed at t = 3 474 | # g = T.graph["loss"] 475 | # src = 11; dst = 12 476 | # #usage g.weights[dst, src] 477 | # @test g.weights[1, src] != 0 478 | # @test g.weights[3, src] != 0 479 | # @test g.weights[5, src] == 0 480 | # @test g.weights[7, src] != 0 481 | 482 | # @test(has_path(g, src, 1) == true) 483 | # @test(has_path(g, src, 3) == true) 484 | # @test(has_path(g, src, 5) == false) 485 | # @test(has_path(g, src, 7) == true) 486 | 487 | end 488 | 489 | 490 | @testset "Benchmarking.jl" begin 491 | # TODO Test dict_average 492 | # TODO Test dict_err 493 | 494 | # Test make_user_pairs for QNetwork. 495 | 496 | # NOTE: One important detail I noticed: 497 | # make_user_pairs ordinarily generates random user pairs. I verified this in 498 | # A seperate test file. Impressively, the @testset seems to fix the seed, such 499 | # that no matter how many times you roll the dice, you get the same outcome. 500 | # Convenient for unit testing, not so much for verifying randomness. 501 | 502 | # Test make_user_pairs for a regular QNetwork 503 | Q = GridNetwork(2,2) 504 | user_pair = make_user_pairs(Q, 2) 505 | @test user_pair == [(4,3),(1,2)] 506 | 507 | # Test make_user_pairs for a TemporalGraph, specifying that pairs should be asynchronus 508 | G = deepcopy(barbell) 509 | T = QuNet.TemporalGraph(G, 2) 510 | async_pairs = QuNet.make_user_pairs(T, 1, src_layer=-1, dst_layer=-1) 511 | @test async_pairs == [(5,6)] 512 | 513 | # Test make_user_pairs for a TemporalGraph, specifying that src should be asynchronus, and dst on layer 1 514 | G = deepcopy(barbell) 515 | T = QuNet.TemporalGraph(G, 3) 516 | lopsided_pair = QuNet.make_user_pairs(T, 1, src_layer=-1, dst_layer=1) 517 | @test lopsided_pair[1][1] > T.nv * T.steps && lopsided_pair[1][2] <= T.nv 518 | 519 | # Test make_user_pairs for a TemporalGraph for many pairs, specifying dst should be asynchronus and src on arbitrary layer, 520 | G = GridNetwork(5,5) 521 | T = QuNet.TemporalGraph(G, 5) 522 | lopsided_pairs = QuNet.make_user_pairs(T, 10, src_layer=3, dst_layer=-1) 523 | @test all(T.nv * 2 < lopsided_pairs[i][1] <= T.nv * 3 for i in 1:10) 524 | @test all(T.nv * T.steps < lopsided_pairs[i][2] for i in 1:10) 525 | 526 | # Test ave_paths_used 527 | pathuse_count = [5,1,2,4] 528 | # Expected answer: 529 | # (0*5 + 1*1 + 2*2 + 3*4) / (5 + 1 + 2 + 4) == 17/12 530 | ave_pathuse = QuNet.ave_paths_used(pathuse_count) 531 | @test(ave_pathuse == 17/12) 532 | 533 | # TODO Test net_performance 534 | # Test for 1 trial of barbell network 535 | # usage: (network::QNetwork, num_trials::Int64, num_pairs::Int64; max_paths=3) 536 | Q = deepcopy(barbell) 537 | performance, performance_err, ave_pathcounts, ave_pathcounts_err = net_performance(barbell, 1, 1) 538 | # Check ave_pathcounts is correct 539 | @test ave_pathcounts == [0, 1, 0, 0] 540 | # Only one sample taken, so no variance in ave_pathcounts: 541 | @test all(isnan(i) == true for i in ave_pathcounts_err) 542 | 543 | # Test net_performance for many trials of barbell network 544 | Q = deepcopy(barbell) 545 | performance, performance_err, ave_pathcounts, ave_pathcounts_err = net_performance(barbell, 100, 1) 546 | @test ave_pathcounts == [0, 1, 0, 0] 547 | # Only one path possible, so no error 548 | @test ave_pathcounts_err == [0, 0, 0, 0] 549 | 550 | # Test net_performance for TemporalGraph 551 | T = deepcopy(smalltemp) 552 | performance, performance_err, ave_pathcounts, ave_pathcounts_err = net_performance(T, 100, 2) 553 | # Check that the average number of paths used is 2 554 | @test ave_pathcounts[3] == 2.0 555 | @test ave_pathcounts_err == [0.0, 0.0, 0.0, 0.0] 556 | end 557 | -------------------------------------------------------------------------------- /QuNet-Paper/MultiPathDemo.jl: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains all the scripts used to produce the plots for the QuNet paper. 3 | (Except for the Satellite plot which is in SatPlotDemo.jl) 4 | """ 5 | 6 | using QuNet 7 | using LightGraphs, SimpleWeightedGraphs, GraphPlot, MetaGraphs, Plots, Colors, Statistics 8 | using LaTeXStrings 9 | using DelimitedFiles 10 | 11 | """ 12 | Plot the performance statistics of greedy-multi-path vs the number of end-user pairs 13 | """ 14 | function plot_with_userpairs(max_pairs::Int64, 15 | num_trials::Int64) 16 | 17 | # The average routing costs between end-users sampled over num_trials for different numbers of end-users 18 | perf_data = [] 19 | # The associated errors of the costs sampled over num_trials 20 | perf_err = [] 21 | # Average numbers of paths used, sampled over num_trials for different numbers of end-users 22 | # e.g. [3,4,5]: 3 end-users found no path on average, 4 end-users found 1 path on average etc. 23 | path_data = [] 24 | # Associated errors of path_data 25 | path_err = [] 26 | 27 | grid_size = 10 28 | 29 | for i in 1:max_pairs 30 | println("Collecting for pairsize: $i") 31 | 32 | net = GridNetwork(grid_size, grid_size) 33 | 34 | # Collect performance statistics 35 | p, p_e, pat, pat_e = net_performance(net, num_trials, i, max_paths=4) 36 | push!(perf_data, p) 37 | push!(perf_err, p_e) 38 | push!(path_data, pat) 39 | push!(path_err, pat_e) 40 | 41 | end 42 | 43 | # Collect data for conditional probability of purification: (N2+N3)/∑N_i 44 | cpp = [] 45 | cpp_err = [] 46 | for i in 1:max_pairs 47 | tableau = path_data[i] 48 | errors = path_err[i] 49 | off = 1 50 | 51 | # Numerator and denominator 52 | num = tableau[2 + off] + tableau[3 + off] + tableau[4 + off] 53 | denom = tableau[1 + off] + tableau[2 + off] + tableau[3 + off] + tableau[4 + off] 54 | 55 | # Percentage errors (Ignore factor 100. Not needed) 56 | num_perr = (errors[2 + off] + errors[3 + off] + errors[4 + off])/num 57 | denom_perr = (errors[1 + off] + errors[2 + off] + errors[3 + off] + errors[4 + off])/denom 58 | 59 | data = num/denom 60 | data_err = (num_perr + denom_perr) * data 61 | push!(cpp, data) 62 | push!(cpp_err, data_err) 63 | i += 1 64 | end 65 | 66 | # Collect data for average number of paths used 67 | avepath = [] 68 | for i in 1:max_pairs 69 | tableau = convert(Vector{Float64}, path_data[i]) 70 | data = QuNet.ave_paths_used(tableau) 71 | 72 | # TODO: Include errors 73 | push!(avepath, data) 74 | i += 1 75 | end 76 | 77 | # Get values for x axis 78 | x = collect(1:max_pairs) 79 | 80 | # Extract data from performance 81 | loss = collect(map(x->x["loss"], perf_data)) 82 | z = collect(map(x->x["Z"], perf_data)) 83 | loss_err = collect(map(x->x["loss"], perf_err)) 84 | z_err = collect(map(x->x["Z"], perf_err)) 85 | 86 | # Extract data from path: PX is the rate of using X paths 87 | P0 = [path_data[i][1]/i for i in 1:max_pairs] 88 | P1 = [path_data[i][2]/i for i in 1:max_pairs] 89 | P2 = [path_data[i][3]/i for i in 1:max_pairs] 90 | P3 = [path_data[i][4]/i for i in 1:max_pairs] 91 | P4 = [path_data[i][5]/i for i in 1:max_pairs] 92 | 93 | P0e = [path_err[i][1]/i for i in 1:max_pairs] 94 | P1e = [path_err[i][2]/i for i in 1:max_pairs] 95 | P2e = [path_err[i][3]/i for i in 1:max_pairs] 96 | P3e = [path_err[i][4]/i for i in 1:max_pairs] 97 | P4e = [path_err[i][5]/i for i in 1:max_pairs] 98 | 99 | # Save data to txt 100 | open("data/userpairs.txt", "w") do io 101 | writedlm(io, ["max_pairs = $max_pairs", 102 | "num_trials = $num_trials", 103 | "grid_size = $grid_size", 104 | "perf_data", perf_data, 105 | "perf_err", perf_err, 106 | "path_data", path_data, 107 | "path_err", path_err, 108 | "cpp", cpp, 109 | "cpp_err", cpp_err, 110 | "avepath", avepath]) 111 | end 112 | 113 | # Plot 114 | plot(x, loss, ylims=(0,1), seriestype = :scatter, yerror = loss_err, label=L"$\eta$", 115 | legend=:bottomright) 116 | plot!(x, z, seriestype = :scatter, yerror = z_err, label=L"$F$") 117 | xaxis!(L"$\textrm{Number of End User Pairs}$") 118 | savefig("plots/cost_userpair.png") 119 | savefig("plots/cost_userpair.pdf") 120 | 121 | plot(x, P0, ylims=(0,1), linewidth=2, yerr = P0e, label=L"$P_0$", legend= :right) 122 | plot!(x, P1, linewidth=2, yerr = P1e, label=L"$P_1$") 123 | plot!(x, P2, linewidth=2, yerr = P2e, label=L"$P_2$") 124 | plot!(x, P3, linewidth=2, yerr = P3e, label=L"$P_3$") 125 | plot!(x, P4, linewidth=2, yerr = P4e, label=L"$P_4$") 126 | plot!(x, cpp, linewidth=2, yerr = cpp_err, label=L"$P_{P}$") 127 | xaxis!(L"$\textrm{Number of End User Pairs}$") 128 | savefig("plots/path_userpair.png") 129 | savefig("plots/path_userpair.pdf") 130 | 131 | plot(x, avepath, linewidth=2, legend = false) 132 | xaxis!(L"$\textrm{Number of End User Pairs}$") 133 | yaxis!(L"$\textrm{Average Number of Paths Used}$") 134 | savefig("plots/avepath_userpair.png") 135 | savefig("plots/avepath_userpair.pdf") 136 | end 137 | 138 | 139 | """ 140 | Plot the performance statistics of greedy-multi-path with respect to edge percolation 141 | rate (The probability that a given edge is removed) 142 | """ 143 | function plot_with_percolations(perc_range::Tuple{Float64, Float64, Float64}, num_trials::Int64) 144 | # number of end-user pairs 145 | num_pairs = 1 146 | 147 | # Network to be percolated. 148 | grid_size = 10 149 | net = GridNetwork(grid_size, grid_size) 150 | 151 | perf_data = [] 152 | perf_err = [] 153 | path_data = [] 154 | path_err = [] 155 | 156 | for p in perc_range[1]:perc_range[2]:perc_range[3] 157 | println("Collecting for percolation rate: $p") 158 | 159 | # Percolate the network 160 | # perc_net = QuNet.percolate_edges(net, p) 161 | # refresh_graph!(perc_net) 162 | 163 | # Collect performance data with error, percolating the network edges 164 | p, p_e, pat, pat_e = net_performance(net, num_trials, num_pairs, max_paths=4, 165 | edge_perc_rate = p) 166 | push!(perf_data, p) 167 | push!(perf_err, p_e) 168 | push!(path_data, pat) 169 | push!(path_err, pat_e) 170 | end 171 | 172 | # Get values for x axis 173 | x = collect(perc_range[1]:perc_range[2]:perc_range[3]) 174 | 175 | # Extract performance data 176 | loss = collect(map(x->x["loss"], perf_data)) 177 | z = collect(map(x->x["Z"], perf_data)) 178 | loss_err = collect(map(x->x["loss"], perf_err)) 179 | z_err = collect(map(x->x["Z"], perf_err)) 180 | 181 | # Save data to txt 182 | open("data/percolation.txt", "w") do io 183 | writedlm(io, ["perc_range = $perc_range", 184 | "num_trials = $num_trials", 185 | "num_pairs = $num_pairs", 186 | "grid_size = $grid_size", 187 | "perf_data:", perf_data, 188 | "perf_err:", perf_err, 189 | "path_data:", path_data, 190 | "path_err:", path_err]) 191 | end 192 | 193 | # Extract data from path: PX is the rate of using X paths 194 | P0 = [path_data[i][1]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 195 | P1 = [path_data[i][2]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 196 | P2 = [path_data[i][3]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 197 | P3 = [path_data[i][4]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 198 | P4 = [path_data[i][5]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 199 | 200 | # Extract errors from path: 201 | P0e = [path_err[i][1]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 202 | P1e = [path_err[i][2]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 203 | P2e = [path_err[i][3]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 204 | P3e = [path_err[i][4]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 205 | P4e = [path_err[i][5]/num_pairs for i in 1:length(perc_range[1]:perc_range[2]:perc_range[3])] 206 | 207 | # Plot 208 | plot(x, loss, ylims=(0,1), seriestype = :scatter, yerror = loss_err, label=L"$\eta$", 209 | legend=:bottomright) 210 | plot!(x, z, seriestype = :scatter, yerror = z_err, label=L"$F$") 211 | xaxis!(L"$\textrm{Probability of Edge Removal}$") 212 | savefig("plots/cost_percolation.pdf") 213 | savefig("plots/cost_percolation.png") 214 | 215 | plot(x, P0, ylims=(0,1), linewidth=2, yerr = P0e, label=L"$P_0$", legend= :right) 216 | plot!(x, P1, linewidth=2, yerr = P1e, label=L"$P_1$") 217 | plot!(x, P2, linewidth=2, yerr = P2e, label=L"$P_2$") 218 | plot!(x, P3, linewidth=2, yerr = P3e, label=L"$P_3$") 219 | plot!(x, P4, linewidth=2, yerr = P3e, label=L"$P_4$") 220 | xaxis!(L"$\textrm{Probability of Edge Removal}$") 221 | savefig("plots/path_percolation.pdf") 222 | savefig("plots/path_percolation.png") 223 | end 224 | 225 | 226 | """ 227 | Plot the performance statistics of greedy-multi-path with respect to the timedepth 228 | of the graph 229 | """ 230 | function plot_with_timedepth(num_trials::Int64, max_depth::Int64) 231 | 232 | """ 233 | For a given grid size, this function runs the greedy_multi_path routing algorithm 234 | on randomly placed end-user pairs. 235 | """ 236 | function asymptotic_costs(gridsize::Int64) 237 | N = 10000 238 | G = GridNetwork(gridsize, gridsize) 239 | T = QuNet.TemporalGraph(G, 5, memory_costs = unit_costvector()) 240 | p, dum1, dum2, dum3 = net_performance(T, N, 1, max_paths=4) 241 | return p["loss"], p["Z"] 242 | end 243 | 244 | # BEGIN MAIN 245 | 246 | num_pairs = 40 247 | grid_size = 10 248 | 249 | perf_data = [] 250 | perf_err = [] 251 | path_data = [] 252 | path_err = [] 253 | 254 | for i in 1:max_depth 255 | println("Collecting for time depth $i") 256 | G = GridNetwork(grid_size, grid_size) 257 | # Create a Temporal Graph from G with timedepth i 258 | T = QuNet.TemporalGraph(G, i, memory_costs = unit_costvector()) 259 | # Get random pairs of asynchronus nodes 260 | user_pairs = make_user_pairs(T, num_pairs) 261 | # Get data 262 | p, p_e, pat, pat_e = net_performance(T, num_trials, num_pairs, max_paths=4) 263 | push!(perf_data, p) 264 | push!(perf_err, p_e) 265 | push!(path_data, pat) 266 | push!(path_err, pat_e) 267 | end 268 | 269 | # Collect data for horizontal lines: 270 | println("Collecting data for asymptote") 271 | as = asymptotic_costs(grid_size) 272 | e_as = ones(length(1:max_depth)) * as[1] 273 | f_as = ones(length(1:max_depth)) * as[2] 274 | 275 | # Get values for x axis 276 | x = collect(1:max_depth) 277 | 278 | # Extract from performance data 279 | loss = collect(map(x->x["loss"], perf_data)) 280 | z = collect(map(x->x["Z"], perf_data)) 281 | loss_err = collect(map(x->x["loss"], perf_err)) 282 | z_err = collect(map(x->x["Z"], perf_err)) 283 | 284 | # Save data to txt 285 | open("data/temporal.txt", "w") do io 286 | writedlm(io, ["max_depth = $max_depth", 287 | "num_trials = $num_trials", 288 | "num_pairs = $num_pairs", 289 | "grid_size = $grid_size", 290 | "perf_data:", perf_data, 291 | "perf_err:", perf_err, 292 | "path_data:", path_data, 293 | "path_err:", path_err, 294 | "e_as", e_as, 295 | "f_as", f_as]) 296 | end 297 | 298 | # Extract from path data 299 | P0 = [path_data[i][1]/num_pairs for i in 1:max_depth] 300 | P1 = [path_data[i][2]/num_pairs for i in 1:max_depth] 301 | P2 = [path_data[i][3]/num_pairs for i in 1:max_depth] 302 | P3 = [path_data[i][4]/num_pairs for i in 1:max_depth] 303 | P4 = [path_data[i][5]/num_pairs for i in 1:max_depth] 304 | 305 | P0e = [path_err[i][1]/num_pairs for i in 1:max_depth] 306 | P1e = [path_err[i][2]/num_pairs for i in 1:max_depth] 307 | P2e = [path_err[i][3]/num_pairs for i in 1:max_depth] 308 | P3e = [path_err[i][4]/num_pairs for i in 1:max_depth] 309 | P4e = [path_err[i][5]/num_pairs for i in 1:max_depth] 310 | 311 | # Plot 312 | # after seriestype: marker = (5) 313 | plot(x, loss, ylims=(0,1), seriestype = :scatter, yerror = loss_err, label=L"$\eta$", 314 | legend=:right) 315 | plot!(x, z, seriestype = :scatter, yerror = z_err, label=L"$F$") 316 | 317 | # Plot asymptote 318 | plot!(x, e_as, linestyle=:dot, color=:red, linewidth=2, label=L"$\textrm{Asymptotic } \eta$") 319 | plot!(x, f_as, linestyle=:dot, color=:green, linewidth=2, label=L"$\textrm{Asymptotic } F$") 320 | xaxis!(L"$\textrm{Time Depth of Tempral Meta-Graph}$") 321 | 322 | savefig("plots/cost_temporal.png") 323 | savefig("plots/cost_temporal.pdf") 324 | 325 | plot(x, P0, ylims=(0,1), linewidth=2, yerr = P0e, label=L"$P_0$", legend= :right) 326 | plot!(x, P1, linewidth=2, yerr = P1e, label=L"$P_1$") 327 | plot!(x, P2, linewidth=2, yerr = P2e, label=L"$P_2$") 328 | plot!(x, P3, linewidth=2, yerr = P3e, label=L"$P_3$") 329 | plot!(x, P4, linewidth=2, yerr = P4e, label=L"$P_4$") 330 | xaxis!(L"$\textrm{Time Depth of Temporal Meta-Graph}$") 331 | 332 | savefig("plots/path_temporal.png") 333 | savefig("plots/path_temporal.pdf") 334 | end 335 | 336 | 337 | """ 338 | Plot performance statistics of greedy-multi-path with respect to grid size of the network 339 | """ 340 | function plot_with_gridsize(num_trials::Int64, num_pairs::Int64, min_size::Int64, max_size::Int64) 341 | @assert min_size < max_size 342 | @assert num_pairs*2 <= min_size^2 "Graph size too small for num_pairs" 343 | 344 | perf_data = [] 345 | perf_err = [] 346 | path_data = [] 347 | path_err = [] 348 | 349 | size_list = collect(min_size:1:max_size) 350 | for i in size_list 351 | println("Collecting for gridsize: $i") 352 | # Generate ixi graph: 353 | net = GridNetwork(i, i) 354 | 355 | # Collect performance statistics 356 | p, p_e, pat, pat_e = net_performance(net, num_trials, num_pairs, max_paths=4) 357 | push!(perf_data, p) 358 | push!(perf_err, p_e) 359 | push!(path_data, pat) 360 | push!(path_err, pat_e) 361 | end 362 | 363 | # Get values for x axis 364 | x = collect(min_size:1:max_size) 365 | 366 | # Extract data from performance 367 | loss = collect(map(x->x["loss"], perf_data)) 368 | z = collect(map(x->x["Z"], perf_data)) 369 | loss_err = collect(map(x->x["loss"], perf_err)) 370 | z_err = collect(map(x->x["Z"], perf_err)) 371 | 372 | # Save data to txt 373 | open("data/gridsize.txt", "w") do io 374 | writedlm(io, ["min_size = $min_size", 375 | "max_size = $max_size", 376 | "num_trials = $num_trials", 377 | "num_pairs = $num_pairs", 378 | "perf_data:", perf_data, 379 | "perf_err:", perf_err, 380 | "path_data:", path_data, 381 | "path_err:", path_err]) 382 | end 383 | 384 | # Extract from path data 385 | P0 = [path_data[i][1]/num_pairs for i in 1:(max_size-min_size)+1] 386 | P1 = [path_data[i][2]/num_pairs for i in 1:(max_size-min_size)+1] 387 | P2 = [path_data[i][3]/num_pairs for i in 1:(max_size-min_size)+1] 388 | P3 = [path_data[i][4]/num_pairs for i in 1:(max_size-min_size)+1] 389 | P4 = [path_data[i][5]/num_pairs for i in 1:(max_size-min_size)+1] 390 | 391 | P0e = [path_err[i][1]/num_pairs for i in 1:(max_size-min_size)+1] 392 | P1e = [path_err[i][2]/num_pairs for i in 1:(max_size-min_size)+1] 393 | P2e = [path_err[i][3]/num_pairs for i in 1:(max_size-min_size)+1] 394 | P3e = [path_err[i][4]/num_pairs for i in 1:(max_size-min_size)+1] 395 | P4e = [path_err[i][5]/num_pairs for i in 1:(max_size-min_size)+1] 396 | 397 | # Plot 398 | plot(x, loss, ylims=(0,1), seriestype = :scatter, yerror = loss_err, label=L"$\eta$", 399 | legend=:bottomright) 400 | plot!(x, z, seriestype = :scatter, yerror = z_err, label=L"$F$") 401 | xaxis!(L"$\textrm{Grid Size}$") 402 | savefig("plots/cost_gridsize.png") 403 | savefig("plots/cost_gridsize.pdf") 404 | 405 | plot(x, P0, ylims=(0,1), linewidth=2, yerr = P0e, label=L"$P_0$", legend= :right) 406 | plot!(x, P1, linewidth=2, yerr = P1e, label=L"$P_1$") 407 | plot!(x, P2, linewidth=2, yerr = P2e, label=L"$P_2$") 408 | plot!(x, P3, linewidth=2, yerr = P3e, label=L"$P_3$") 409 | plot!(x, P4, linewidth=2, yerr = P4e, label=L"$P_4$") 410 | xaxis!(L"$\textrm{Grid Size}$") 411 | savefig("plots/path_gridsize.png") 412 | savefig("plots/path_gridsize.pdf") 413 | end 414 | 415 | 416 | """ 417 | Plot the performance statistics of greedy_multi_path for 1 random end-user pair 418 | over a range of graph sizes and for different numbers of max_path (1,2,3) (ie. the 419 | maximum number of paths that can be purified by a given end-user pair.) 420 | """ 421 | function plot_maxpaths_with_gridsize(num_trials::Int64, min_size::Int64, max_size::Int64) 422 | @assert min_size < max_size 423 | 424 | perf_data1 = [] 425 | perf_data2 = [] 426 | perf_data3 = [] 427 | perf_data4 = [] 428 | 429 | size_list = collect(min_size:1:max_size) 430 | for (j, data_array) in enumerate([perf_data1, perf_data2, perf_data3, perf_data4]) 431 | println("Collecting for max_paths: $j") 432 | for i in size_list 433 | println("Collecting for gridsize: $i") 434 | # Generate ixi graph: 435 | net = GridNetwork(i, i) 436 | 437 | # Collect performance statistics 438 | performance, dummy, dummy, dummy = net_performance(net, num_trials, 1, max_paths=j) 439 | push!(data_array, performance) 440 | end 441 | end 442 | 443 | # Get values for x axis 444 | x = collect(min_size:1:max_size) 445 | 446 | # Extract data from performance data 447 | loss_arr1 = collect(map(x->x["loss"], perf_data1)) 448 | z_arr1 = collect(map(x->x["Z"], perf_data1)) 449 | loss_arr2 = collect(map(x->x["loss"], perf_data2)) 450 | z_arr2 = collect(map(x->x["Z"], perf_data2)) 451 | loss_arr3 = collect(map(x->x["loss"], perf_data3)) 452 | z_arr3 = collect(map(x->x["Z"], perf_data3)) 453 | loss_arr4 = collect(map(x->x["loss"], perf_data4)) 454 | z_arr4 = collect(map(x->x["Z"], perf_data4)) 455 | 456 | # Save data to txt 457 | open("data/maxpaths.txt", "w") do io 458 | writedlm(io, ["min_size = $min_size", 459 | "max_size = $max_size", 460 | "num_trials = $num_trials", 461 | "num_pairs = 1", 462 | "perf_data1:", perf_data1, 463 | "perf_data2:", perf_data2, 464 | "perf_data3", perf_data3, 465 | "perf_data4", perf_data4]) 466 | end 467 | 468 | # Plot 469 | plot(x, loss_arr1, linewidth=2, label=L"$\eta_1$", xlims=(0, max_size), color = :red, legend=:left) 470 | plot!(x, z_arr1, linewidth=2, label=L"$F_1$", linestyle=:dash, color =:red) 471 | plot!(x, loss_arr2, linewidth=2, label=L"$\eta_2$", color =:blue) 472 | plot!(x, z_arr2, linewidth=2, label=L"$F_2$", linestyle=:dash, color =:blue) 473 | plot!(x, loss_arr3, linewidth=2, label=L"$\eta_3$", color =:green) 474 | plot!(x, z_arr3, linewidth=2, label=L"$F_3$", linestyle=:dash, color =:green) 475 | plot!(x, loss_arr4, linewidth=2, label=L"$\eta_3$", color =:purple) 476 | plot!(x, z_arr4, linewidth=2, label=L"$F_3$", linestyle=:dash, color =:purple) 477 | xaxis!(L"$\textrm{Grid Size}$") 478 | 479 | savefig("plots/cost_maxpaths.png") 480 | savefig("plots/cost_maxpaths.pdf") 481 | end 482 | 483 | """ 484 | Draw a network with timedepth 1 and the greedy-paths chosen between 3 end user pairs. 485 | """ 486 | function draw_network_routing() 487 | timedepth = 3 488 | grid_size = 10 489 | 490 | # Index offset for asynchronus nodes 491 | off = grid_size^2 * timedepth 492 | # Choose asynchronus endusers 493 | userpairs = [(1 + off, 100 + off), (50 + off, 81 + off), (87 + off, 22 + off)] 494 | 495 | net = GridNetwork(grid_size, grid_size) 496 | T = QuNet.TemporalGraph(net, timedepth, memory_prob=1.0) 497 | QuNet.add_async_nodes!(T, userpairs) 498 | T_copy = deepcopy(T) 499 | user_paths, dum1, dum2 = QuNet.greedy_multi_path!(T_copy, QuNet.purify, userpairs) 500 | QuNet.plot_network(T.graph["Z"], user_paths, T.locs_x, T.locs_y) 501 | end 502 | 503 | 504 | """ 505 | Two different temporal plots, one with memory and one without. While varying the 506 | number of end-users, we compare the ratio of the depths of the graphs 507 | """ 508 | function plot_bandwidth_ratio_with_userpairs(num_trials::Int64, max_pairs::Int64) 509 | 510 | grid_size = 10 511 | time_depth = 50 512 | asynchronus_weight = 100 513 | 514 | # Generate ixi graph and extend it in time 515 | G = GridNetwork(grid_size, grid_size) 516 | # Extend in time with memory links: 517 | T_mem = QuNet.TemporalGraph(G, time_depth, memory_costs = unit_costvector()) 518 | # Extend in time without memory 519 | T = QuNet.TemporalGraph(G, time_depth, memory_prob=0.0) 520 | 521 | plot_data = [] 522 | error_data = [] 523 | for i in 1:max_pairs 524 | println("Collecting for pairs : $i") 525 | raw_data = [] 526 | for j in 1:num_trials 527 | # Get i random userpairs. Ensure src nodes are fixed on T=1, dst nodes are asynchronus. 528 | mem_user_pairs = make_user_pairs(T, i, src_layer=1, dst_layer=-1) 529 | user_pairs = make_user_pairs(T, i, src_layer=-1, dst_layer=-1) 530 | 531 | # Make copies of the network 532 | T_mem_copy = deepcopy(T_mem) 533 | T_copy = deepcopy(T) 534 | 535 | # Add async nodes 536 | QuNet.add_async_nodes!(T_mem_copy, mem_user_pairs, ϵ=asynchronus_weight) 537 | QuNet.add_async_nodes!(T_copy, user_pairs, ϵ=asynchronus_weight) 538 | 539 | # Get pathset data 540 | pathset_mem, dum1, dum2 = QuNet.greedy_multi_path!(T_mem_copy, QuNet.purify, mem_user_pairs, 4) 541 | pathset, dum1, dum2 = QuNet.greedy_multi_path!(T_copy, QuNet.purify, user_pairs, 4) 542 | # Pathset is an array of vectors containing edges describing paths between end-user pairs 543 | # Objective: find the largest timedepth used in the pathsets 544 | 545 | max_depth_mem = QuNet.max_timedepth(pathset_mem, T) 546 | max_depth = QuNet.max_timedepth(pathset, T) 547 | 548 | # Get the ratio of these two quantities. Add it to data array 549 | push!(raw_data, max_depth / max_depth_mem ) 550 | end 551 | # Average the raw data, add it to plot data: 552 | push!(plot_data, mean(raw_data)) 553 | # Get standard error 554 | push!(error_data, std(raw_data)/sqrt(num_trials - 1)) 555 | end 556 | 557 | # Save data to txt 558 | open("data/bandwidth_with_userpairs.txt", "w") do io 559 | writedlm(io, ["grid_size = $grid_size", 560 | "num_trials = $num_trials", 561 | "time_depth = $time_depth", 562 | "max_pairs = $max_pairs", 563 | "asynchronus_weight = $asynchronus_weight", 564 | "bandwidth_ratios = $plot_data", 565 | "bandwidth_error = $error_data"]) 566 | end 567 | 568 | # Plot 569 | x = collect(1:max_pairs) 570 | plot(x, plot_data, yerr = error_data, legend = false) 571 | xaxis!(L"$\textrm{Number of End User Pairs}$") 572 | 573 | savefig("plots/bandwidth_with_userpairs.png") 574 | savefig("plots/bandwidth_with_userpairs.pdf") 575 | end 576 | 577 | 578 | # For this one, let's just consider bandwidth. No ratio needed! 579 | function plot_bandwidth_ratio_with_memory_rate(num_trials::Int64, perc_range::Tuple{Float64, Float64, Float64}) 580 | 581 | grid_size = 10 582 | time_depth = 8 583 | num_pairs = 50 584 | asynchronus_weight = 100 585 | 586 | # Generate ixi graph and extend it in time 587 | G = GridNetwork(grid_size, grid_size) 588 | 589 | # Extend graph in time without memory 590 | T = QuNet.TemporalGraph(G, time_depth, memory_prob=0.0) 591 | 592 | plot_data = [] 593 | error_data = [] 594 | for i in perc_range[1]:perc_range[2]:perc_range[3] 595 | println("Collecting for memory percolation rate : $i") 596 | raw_data = [] 597 | for j in 1:num_trials 598 | 599 | # Make a network with some probability of memory 600 | T_mem = QuNet.TemporalGraph(G, time_depth, memory_prob=i, memory_costs = unit_costvector()) 601 | # Make a copy of the network without memory 602 | T_copy = deepcopy(T) 603 | 604 | # Get i random userpairs with asynchronus src and dst nodes. 605 | mem_user_pairs = make_user_pairs(T_mem, num_pairs, src_layer=-1, dst_layer=-1) 606 | user_pairs = make_user_pairs(T, num_pairs, src_layer=-1, dst_layer=-1) 607 | 608 | # Add async nodes 609 | QuNet.add_async_nodes!(T_mem, mem_user_pairs, ϵ=asynchronus_weight) 610 | QuNet.add_async_nodes!(T_copy, user_pairs, ϵ=asynchronus_weight) 611 | 612 | # Get pathset data 613 | pathset_mem, dum1, dum2 = QuNet.greedy_multi_path!(T_mem, QuNet.purify, mem_user_pairs, 1) 614 | pathset, dum1, dum2 = QuNet.greedy_multi_path!(T_copy, QuNet.purify, user_pairs, 1) 615 | # Pathset is an array of vectors containing edges describing paths between end-user pairs 616 | # Objective: find the largest timedepth used in the pathsets 617 | 618 | max_depth_mem = QuNet.max_timedepth(pathset_mem, T_mem) 619 | max_depth = QuNet.max_timedepth(pathset, T_copy) 620 | 621 | # Get the bandwidth of this quantity 622 | push!(raw_data, max_depth / max_depth_mem) 623 | end 624 | 625 | # Average the raw data, add it to plot data: 626 | push!(plot_data, mean(raw_data)) 627 | # Get standard error 628 | push!(error_data, std(raw_data)/sqrt(num_trials - 1)) 629 | end 630 | 631 | open("data/bandwidth_with_memory_rate.txt", "w") do io 632 | writedlm(io, ["grid_size = $grid_size", 633 | "num_trials = $num_trials", 634 | "time_depth = $time_depth", 635 | "num_pairs = $num_pairs", 636 | "perc_range = $perc_range", 637 | "asynchronus_weight = $asynchronus_weight", 638 | "bandwidth_ratios = $plot_data", 639 | "bandwidth_error = $error_data"]) 640 | end 641 | 642 | # Plot 643 | x = collect(perc_range[1]:perc_range[2]:perc_range[3]) 644 | plot(x, plot_data, yerr = error_data, legend = false) 645 | xaxis!(L"$\textrm{Proportion of Nodes with Quantum Memory}$") 646 | yaxis!(L"$\textrm{R}$") 647 | 648 | savefig("plots/bandwidth_with_memory_rate.png") 649 | savefig("plots/bandwidth_with_memory_rate.pdf") 650 | end 651 | 652 | 653 | 654 | # MAIN 655 | """ 656 | Uncomment functions to reproduce plots from the paper / create your own 657 | """ 658 | # Usage : (max_pairs::Int64, num_trials::Int64) 659 | # plot_with_userpairs(50, 5000) 660 | 661 | # Usage : (perc_range::Tuple{Float64, Float64, Float64}, num_trials::Int64) 662 | # plot_with_percolations((0.0, 0.01, 0.7), 5000) 663 | 664 | # Usage : (num_trials::Int64, max_depth::Int64) 665 | # plot_with_timedepth(1000, 15) 666 | 667 | # Usage : (num_trials::Int64, num_pairs::Int64, min_size::Int64, max_size::Int64) 668 | # plot_with_gridsize(100, 40, 10, 150) 669 | 670 | # Usage : (num_trials::Int64, num_pairs::Int64, min_size::Int64, max_size::Int64) 671 | # plot_maxpaths_with_gridsize(5000, 10, 50) 672 | 673 | # Usage : (num_trials::Int64, max_pairs::Int64) 674 | # plot_bandwidth_ratio_with_userpairs(1000, 50) 675 | 676 | # Usage : num_trials::Int64, perc_range::Tuple{Float64, Float64, Float64} 677 | # plot_bandwidth_ratio_with_memory_rate(5000, (0.0, 0.05, 1.0)) 678 | 679 | # Usage : None 680 | # draw_network_routing() 681 | 682 | # Usage: (num_trials::Int64, max_depth::Int64) 683 | # plot_nomultipath_with_timedepth(10, 10) 684 | --------------------------------------------------------------------------------