diff --git a/.github/workflows/benchmark-comment.yml b/.github/workflows/benchmark-comment.yml index 66a25078..e873dbfa 100644 --- a/.github/workflows/benchmark-comment.yml +++ b/.github/workflows/benchmark-comment.yml @@ -18,6 +18,7 @@ on: jobs: comment: runs-on: ubuntu-latest + #runs-on: self-hosted if: > ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} @@ -41,7 +42,7 @@ jobs: # check if the previous comment exists - name: find comment - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@v3 id: fc with: issue-number: ${{ steps.output-pull-request-number.outputs.body }} @@ -51,13 +52,13 @@ jobs: # create/update comment - name: create comment if: ${{ steps.fc.outputs.comment-id == 0 }} - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ steps.output-pull-request-number.outputs.body }} body: ${{ steps.output-result-markdown.outputs.body }} - name: update comment if: ${{ steps.fc.outputs.comment-id != 0 }} - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.fc.outputs.comment-id }} body: ${{ steps.output-result-markdown.outputs.body }} diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index aa1040df..34c65be0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,11 +1,14 @@ name: Performance tracking - on: pull_request: +env: + PYTHON: ~ + jobs: performance-tracking: runs-on: ubuntu-latest + #runs-on: self-hosted steps: # setup - uses: actions/checkout@v4 diff --git a/.github/workflows/changelog-enforcer.yml b/.github/workflows/changelog-enforcer.yml new file mode 100644 index 00000000..b7a2038f --- /dev/null +++ b/.github/workflows/changelog-enforcer.yml @@ -0,0 +1,15 @@ +name: "Changelog Enforcer" +on: + pull_request: + # The specific activity types are listed here to include "labeled" and "unlabeled" + # (which are not included by default for the "pull_request" trigger). + # This is needed to allow skipping enforcement of the changelog in PRs with specific labels, + # as defined in the (optional) "skipLabels" property. + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] + +jobs: + # Enforces the update of a changelog file on every pull request + changelog: + runs-on: ubuntu-latest + steps: + - uses: dangoslen/changelog-enforcer@v3 \ No newline at end of file diff --git a/.github/workflows/ci-julia-nightly.yml b/.github/workflows/ci-julia-nightly.yml index 69109386..9182797a 100644 --- a/.github/workflows/ci-julia-nightly.yml +++ b/.github/workflows/ci-julia-nightly.yml @@ -4,6 +4,8 @@ on: branches: [master, main] tags: ["*"] pull_request: +env: + PYTHON: ~ jobs: test: name: Julia ${{ matrix.version }} - t=${{ matrix.threads }} - jet=${{ matrix.jet }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} @@ -28,22 +30,14 @@ jobs: with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v4 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 env: JULIA_NUM_THREADS: ${{ matrix.threads }} JET_TEST: ${{ matrix.jet }} - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: - file: lcov.info \ No newline at end of file + file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2714bd18..99380a5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ on: branches: [master, main] tags: ["*"] pull_request: +env: + PYTHON: ~ jobs: test: name: Julia ${{ matrix.version }} - t=${{ matrix.threads }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} @@ -26,24 +28,16 @@ jobs: with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v4 - env: - cache-name: cache-artifacts - with: - path: ~/.julia/artifacts - key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} - restore-keys: | - ${{ runner.os }}-test-${{ env.cache-name }}- - ${{ runner.os }}-test- - ${{ runner.os }}- + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 env: JULIA_NUM_THREADS: ${{ matrix.threads }} - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} docs: name: Documentation runs-on: ubuntu-latest @@ -52,6 +46,7 @@ jobs: - uses: julia-actions/setup-julia@v1 with: version: '1' + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-docdeploy@v1 env: diff --git a/.github/workflows/downgrade.yml b/.github/workflows/downgrade.yml new file mode 100644 index 00000000..b12c8448 --- /dev/null +++ b/.github/workflows/downgrade.yml @@ -0,0 +1,29 @@ +name: Downgrade +on: + pull_request: + branches: [master, main] + paths-ignore: + - 'docs/**' + push: + branches: [master, main] + paths-ignore: + - 'docs/**' +env: + PYTHON: ~ +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + version: ['1.9'] + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + - uses: cjdoris/julia-downgrade-compat-action@v1 + with: + skip: Pkg,TOML,InteractiveUtils,Random,LinearAlgebra + - uses: julia-actions/cache@v1 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 \ No newline at end of file diff --git a/.github/workflows/invalidations.yml b/.github/workflows/invalidations.yml index 7e0aefe0..e6aade0e 100644 --- a/.github/workflows/invalidations.yml +++ b/.github/workflows/invalidations.yml @@ -20,6 +20,7 @@ jobs: with: version: '1' - uses: actions/checkout@v4 + - uses: julia-actions/cache@v1 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-invalidations@v1 id: invs_pr diff --git a/Project.toml b/Project.toml index 1bc3fe52..05e0e0e4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumSavory" uuid = "2de2e421-972c-4cb5-a0c3-999c85908079" authors = ["Stefan Krastanov "] -version = "0.3.2" +version = "0.3.3" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" @@ -51,7 +51,7 @@ QuantumOpticsBase = "0.4.17" QuantumSymbolics = "0.2.5" Random = "1" Reexport = "1.2.2" -ResumableFunctions = "0.6.1 - 0.6.6" +ResumableFunctions = "0.6" Statistics = "1" SumTypes = "0.5.1" julia = "1.9" diff --git a/benchmark/Project.toml b/benchmark/Project.toml new file mode 100644 index 00000000..c4625ed0 --- /dev/null +++ b/benchmark/Project.toml @@ -0,0 +1,7 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" +QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c" +QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" +QuantumSavory = "2de2e421-972c-4cb5-a0c3-999c85908079" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl new file mode 100644 index 00000000..363b1f55 --- /dev/null +++ b/benchmark/benchmarks.jl @@ -0,0 +1,125 @@ +using BenchmarkTools +using Pkg +using StableRNGs +using QuantumSavory +using QuantumSavory: tag_types +using QuantumOpticsBase: Ket, Operator +using QuantumClifford: MixedDestabilizer + +const SUITE = BenchmarkGroup() + +rng = StableRNG(42) + +M = Pkg.Operations.Context().env.manifest +V = M[findfirst(v -> v.name == "QuantumSavory", M)].version + +SUITE["register"] = BenchmarkGroup(["register"]) +SUITE["register"]["creation_and_initialization"] = BenchmarkGroup(["creation_and_initialization"]) +function register_creation_and_initialization() + traits = [Qubit(), Qubit(), Qubit()] + reg1 = Register(traits) + qc_repr = [QuantumOpticsRepr(), CliffordRepr(), CliffordRepr()] + reg2 = Register(traits, qc_repr) + qmc_repr = [QuantumOpticsRepr(), QuantumMCRepr(), QuantumMCRepr()] + reg3 = Register(traits, qmc_repr) + net = RegisterNet([reg1, reg2, reg3]) + + i = 1 + initialize!(net[i,2]) + @assert nsubsystems(net[i].staterefs[2]) == 1 + initialize!(net[i,3],X1) + @assert nsubsystems(net[i].staterefs[2]) == 1 + apply!([net[i,2], net[i,3]], CNOT) + @assert net[i].staterefs[2].state[] isa Ket + @assert nsubsystems(net[i].staterefs[2]) == 2 + + i = 2 + initialize!(net[i,2]) + @assert nsubsystems(net[i].staterefs[2]) == 1 + initialize!(net[i,3],X1) + @assert nsubsystems(net[i].staterefs[2]) == 1 + apply!([net[i,2], net[i,3]], CNOT) + @assert net[i].staterefs[2].state[] isa MixedDestabilizer + @assert nsubsystems(net[i].staterefs[2]) == 2 + + i = 3 + initialize!(net[i,2]) + @assert nsubsystems(net[i].staterefs[2]) == 1 + initialize!(net[i,3],X1) + @assert nsubsystems(net[i].staterefs[2]) == 1 + apply!([net[i,2], net[i,3]], CNOT) + @assert net[i].staterefs[2].state[] isa Ket + @assert nsubsystems(net[i].staterefs[2]) == 2 + + ## + # with backgrounds + traits = [Qubit(), Qubit(), Qubit()] + backgrounds = [T2Dephasing(1.0),T2Dephasing(1.0),T2Dephasing(1.0)] + reg1 = Register(traits, backgrounds) + qc_repr = [QuantumOpticsRepr(), CliffordRepr(), CliffordRepr()] + reg2 = Register(traits, qc_repr, backgrounds) + qmc_repr = [QuantumOpticsRepr(), QuantumMCRepr(), QuantumMCRepr()] + reg3 = Register(traits, qmc_repr, backgrounds) + net = RegisterNet([reg1, reg2, reg3]) + + i = 1 + initialize!(net[i,2], time=1.0) + @assert nsubsystems(net[i].staterefs[2]) == 1 + initialize!(net[i,3],X1, time=2.0) + @assert nsubsystems(net[i].staterefs[2]) == 1 + apply!([net[i,2], net[i,3]], CNOT, time=3.0) + @assert net[i].staterefs[2].state[] isa Operator + @assert nsubsystems(net[i].staterefs[2]) == 2 + + i = 2 + initialize!(net[i,2], time=1.0) + @assert nsubsystems(net[i].staterefs[2]) == 1 + initialize!(net[i,3],X1, time=2.0) + @assert nsubsystems(net[i].staterefs[2]) == 1 + apply!([net[i,2], net[i,3]], CNOT, time=3.0) + @assert net[i].staterefs[2].state[] isa MixedDestabilizer + @assert nsubsystems(net[i].staterefs[2]) == 2 + + i = 3 + initialize!(net[i,2], time=1.0) + @assert nsubsystems(net[i].staterefs[2]) == 1 + initialize!(net[i,3],X1, time=2.0) + @assert nsubsystems(net[i].staterefs[2]) == 1 + apply!([net[i,2], net[i,3]], CNOT, time=3.0) + @assert nsubsystems(net[i].staterefs[2]) == 2 +end +SUITE["register"]["creation_and_initialization"]["from_tests"] = @benchmarkable register_creation_and_initialization() + +SUITE["tagquery"] = BenchmarkGroup(["tagquery"]) +SUITE["tagquery"]["misc"] = BenchmarkGroup(["misc"]) +function tagquery_interfacetest() + r = Register(10) + tag!(r[1], :symbol1, 2, 3) + tag!(r[2], :symbol1, 4, 5) + tag!(r[5], Int, 4, 5) + + @assert Tag(:symbol1, 2, 3) == tag_types.SymbolIntInt(:symbol1, 2, 3) + @assert query(r, :symbol1, 4, ❓) == (slot=r[2], tag=tag_types.SymbolIntInt(:symbol1, 4, 5)) + @assert query(r, :symbol1, 4, 5) == (slot=r[2], tag=tag_types.SymbolIntInt(:symbol1, 4, 5)) + @assert query(r, :symbol1, ❓, ❓) == (slot=r[1], tag=tag_types.SymbolIntInt(:symbol1, 2, 3)) + @assert query(r, :symbol2, ❓, ❓) == nothing + @assert query(r, Int, 4, 5) == (slot=r[5], tag=tag_types.TypeIntInt(Int, 4, 5)) + @assert query(r, Float32, 4, 5) == nothing + @assert query(r, Int, 4, >(5)) == nothing + @assert query(r, Int, 4, <(6)) == (slot=r[5], tag=tag_types.TypeIntInt(Int, 4, 5)) + + @assert queryall(r, :symbol1, ❓, ❓) == [(slot=r[1], tag=tag_types.SymbolIntInt(:symbol1, 2, 3)), (slot=r[2], tag=tag_types.SymbolIntInt(:symbol1, 4, 5))] + @assert isempty(queryall(r, :symbol2, ❓, ❓)) + + @assert query(r[2], Tag(:symbol1, 4, 5)) == (depth=1, tag=Tag(:symbol1, 4, 5)) + @assert queryall(r[2], Tag(:symbol1, 4, 5)) == [(depth=1, tag=Tag(:symbol1, 4, 5))] + @assert query(r[2], :symbol1, 4, 5) == (depth=1, tag=Tag(:symbol1, 4, 5)) + @assert queryall(r[2], :symbol1, 4, 5) == [(depth=1, tag=Tag(:symbol1, 4, 5))] + + @assert query(r[2], :symbol1, 4, ❓) == (depth=1, tag=Tag(:symbol1, 4, 5)) + @assert queryall(r[2], :symbol1, 4, ❓) == [(depth=1, tag=Tag(:symbol1, 4, 5))] + + @assert querydelete!(r[2], :symbol1, 4, ❓) == Tag(:symbol1, 4, 5) + @assert querydelete!(r[2], :symbol1, 4, ❓) === nothing +end +SUITE["tagquery"]["misc"]["from_tests"] = @benchmarkable tagquery_interfacetest() diff --git a/docs/src/tutorial/message_queues.md b/docs/src/tutorial/message_queues.md index 2f841a87..50b00320 100644 --- a/docs/src/tutorial/message_queues.md +++ b/docs/src/tutorial/message_queues.md @@ -7,6 +7,14 @@ DocTestSetup = quote end ``` +!!! warning + + This section is rather low-level, created before a lot of user-friendly tools were added. + The approach described here still functions well, however we now provide a more convenient interface and pre-build message passing channels. + In particular, the tagging & querying system (based on `tag!` and `query`), the [`messagebuffer`](@ref), and the available [`channel`](@ref), [`qchannel`](@ref) and [`QuantumChannel`](@ref) + probably cover all your needs. + You might still be interested in reading this section in order to learn some of the low-level tooling on which the more recent developments were built. + In network simulations, a convenient synchronization primitive is the passing of messages between nodes. The `ResumableFunctions` and `ConcurrentSim` libraries provide such primitives, convenient to use with `QuatumSavory`. diff --git a/docs/src/visualizations.md b/docs/src/visualizations.md index 72c56c3d..1668bcfa 100644 --- a/docs/src/visualizations.md +++ b/docs/src/visualizations.md @@ -21,13 +21,7 @@ using CairoMakie # or GLMakie for interactive plots using QuantumSavory # create a network of qubit registers -sizes = [2,3,2,5] -registers = Register[] -for s in sizes - traits = [Qubit() for _ in 1:s] - push!(registers, Register(traits)) -end -network = RegisterNet(registers) +network = RegisterNet([Register(2),Register(3),Register(2),Register(5)]) # add some states, entangle a few slots, perform some gates initialize!(network[1,1]) @@ -41,6 +35,16 @@ _, _, plt, obs = registernetplot_axis(fig[1,1],network) fig ``` +The tall rectangles are registers, the gray squares are the slots of these registers, and the (connected) black diamonds denote when a slot is occupied by some subsystem (of a potentially larger) quantum state. + +The visualization is capable of showing tooltips when hovering over different components of the plot, particularly valuable for debugging. Quantum observables can be directly calculated and plotted as well (through the `observables` keyword). + +Other configuration options are available as well (the ones ending on `plot` let you access the subplot objects used to create the visualization and the ones ending on `backref` provide convenient inverse mapping from graphical elements to the registers or states being visualized): + +```@example vis +propertynames(plt) +``` + ## The state of locks and various metadata in the network The [`resourceplot_axis`](@ref) function can be used to draw all locks and resources stored in a meta-graph governing a discrete event simulation. Metadata stored at the vertices is plotted as colored or grayed out dots depending on their state. Metadata stored at the edges is shown as lines. diff --git a/examples/firstgenrepeater_v2/2_swapper_example.jl b/examples/firstgenrepeater_v2/2_swapper_example.jl index 4425f53f..0301c31b 100644 --- a/examples/firstgenrepeater_v2/2_swapper_example.jl +++ b/examples/firstgenrepeater_v2/2_swapper_example.jl @@ -20,7 +20,7 @@ for (;src, dst) in edges(network) @process eprot() end for node in vertices(network) - sprot = SwapperProt(sim, network, node; nodeL = <(node), nodeR = >(node)) + sprot = SwapperProt(sim, network, node; nodeL = <(node), nodeH = >(node), chooseL = argmin, chooseH = argmax) @process sprot() end diff --git a/ext/QuantumSavoryMakie/QuantumSavoryMakie.jl b/ext/QuantumSavoryMakie/QuantumSavoryMakie.jl index d6b2a9d5..5f8caefd 100644 --- a/ext/QuantumSavoryMakie/QuantumSavoryMakie.jl +++ b/ext/QuantumSavoryMakie/QuantumSavoryMakie.jl @@ -5,126 +5,275 @@ using Graphs using NetworkLayout import ConcurrentSim import Makie -import Makie: Theme, Figure, Axis, @recipe -import QuantumSavory: registernetplot, registernetplot_axis, resourceplot_axis, showonplot +import Makie: Theme, Figure, Axis, + @recipe, lift, Observable, + Point2, Point2f, Rect2f, + scatter!, poly!, linesegments!, + DataInspector +import QuantumSavory: registernetplot, registernetplot!, registernetplot_axis, resourceplot_axis, showonplot + +## @recipe(RegisterNetPlot, regnet) do scene - Theme() + Theme( + colormap = :Spectral, + colorrange = (-1., 1.), + register_color = :gray90, + slotcolor = :gray60, + slotmarker = :rect, + slotsize = 0.8, + observables_marker = :circle, + observables_markersize = 0.55, + observables_linewidth = 5, + state_markersize = 0.4, + state_marker = :diamond, + state_markercolor = :black, + state_linecolor = :gray90, + lock_marker = '⚿', # TODO plot the state of the locks + # The registercoords and observables arguments are not considered "theme" configuration options + ) end -function Makie.plot!(rn::RegisterNetPlot{<:Tuple{RegisterNet}}) # TODO plot the state of the locks +function Makie.plot!(rn::RegisterNetPlot{<:Tuple{RegisterNet}}) networkobs = rn[1] registers = networkobs[].registers - register_rectangles = Makie.Observable(Makie.Rect2{Float64}[]) - register_slots_coords = Makie.Observable(Makie.Point2{Float64}[]) - register_slots_coords_backref = Makie.Observable([]) - state_coords = Makie.Observable(Makie.Point2{Float64}[]) - state_coords_backref = Makie.Observable([]) - state_links = Makie.Observable(Makie.Point2{Float64}[]) - twoqubitstate_links = Makie.Observable(Makie.Point2{Float64}[]) - clrs = Makie.Observable(Float32[]) - regs = Makie.Observable([]) - twoqubitobservable = rn[:twoqubitobservable][] + + register_rectangles = Observable(Rect2f[]) # Makie rectangles that will be plotted for each register + register_slots_coords = Observable(Point2f[]) # Makie marker locations that will be plotted for each register slot + state_coords = Observable(Point2f[]) # Makie marker locations for each slot that contains a state subsystem + state_links = Observable(Point2f[]) # The lines connecting the state subsystem markers corresponding to the same composite system + observables_coords = Observable(Point2f[]) # Makie marker locations that will be plotted for each subsystem on which an observable is evaluated + observables_links = Observable(Point2f[]) # The links between observed subsystems + observables_vals = Observable(Float64[]) # Values of the observables (stored per marker) + observables_linkvals = Observable(Float64[]) # Values of the observables (stored per link) + register_backref = Observable(Any[]) # A backreference to the register object for register square + register_slots_coords_backref = Observable(Tuple{Any,Int,Int}[]) # A backreference to the register object and reference indices for each register slot marker + state_coords_backref = Observable(Tuple{Any,Any,Int,Int,Int}[]) # A backreference to the state object and register object and reference indices for each state marker + observables_backref = Observable(Tuple{Any,Float64}[]) # A backreference to the observable (and its value) for each colored dot visualizing an observable + observables_links_backref = Observable(Tuple{Any,Float64}[]) # same as above but for the links + rn[:register_backref] = register_backref + rn[:register_slots_coords_backref] = register_slots_coords_backref + rn[:state_coords_backref] = state_coords_backref + rn[:observables_backref] = observables_backref + rn[:observables_links_backref] = observables_links_backref + + # Optional arguments + ## registercoords -- updates handled explicitly by an `onany` call below if haskey(rn, :registercoords) && !isnothing(rn[:registercoords][]) - registercoords = rn[:registercoords][] + registercoordsobs = rn[:registercoords] + registercoords = registercoordsobs[] + registercoords isa Vector{<:Point2} || throw(ArgumentError("While plotting a network layout an incorrect argument was given: `registercoords` has to be of type `Vector{<:Point2}`. You can leave it empty to autogenerate a register layout. You can generate it manually or with packages like `NetworkLayout`.")) + rn[:registercoords] = registercoordsobs else adj_matrix = adjacency_matrix(networkobs[].graph) registercoords = spring(adj_matrix, iterations=40, C=2*maximum(nsubsystems.(registers))) + rn[:registercoords] = Observable(registercoords) + end + ## slotcolor -- updates handled implicitly (used only in a single `scatter` call) + if haskey(rn, :slotcolor) && !isnothing(rn[:slotcolor][]) + slotcolorobs = rn[:slotcolor] + if slotcolorobs[] isa Vector{<:Vector} # A vector of vector of colors (i.e. a vector of colors per register) + rn[:slotcolor] = lift(x->reduce(vcat, x), slotcolorobs) # Turn it into a vector of colors + end end - rn[:registercoords] = registercoords # TODO make sure it is an observable + ## observables -- updates handled explicitly by an `onany` call below + if haskey(rn, :observables) && !isnothing(rn[:observables][]) + observablesobs = rn[:observables] + if observablesobs[] isa Vector{<:Tuple{Any,Tuple{Vararg{Tuple{Int,Int}}}}} + observables = Tuple{Any, Tuple{Vararg{Tuple{Int,Int}}}, Vector{Tuple{Int,Int}}}[] + for (O, rsidx) in observablesobs[] + links = Tuple{Int,Int}[] + if length(rsidx)>1 + for (i, (iʳᵉᵍ, iˢˡᵒᵗ)) in enumerate(rsidx) + push!(links, (iʳᵉᵍ, iˢˡᵒᵗ)) + i == 1 || i == length(rsidx) || push!(links, (iʳᵉᵍ, iˢˡᵒᵗ)) + end + end + push!(observables, (O, rsidx, links)) + end + rn[:observables] = Observable(observables) + elseif observablesobs[] isa Vector{<:Tuple{Any,Tuple{Vararg{Tuple{Int,Int}}},Vector{Tuple{Int,Int}}}} + # the expected most general format + else + throw(ArgumentError("While plotting a network layout an incorrect argument was given: `observables` has to be of type `Vector{<:Tuple{Any, Tuple{...}}}`, i.e. it has to be a vector in which each element is similar to `(X⊗X, ((1,1), (1,2)))`, giving the observable operator and the `(register, slot)` indices for each observed subsystem. There is also a support for adding a third tuple element, a vector specifying the exact links to be drawn.")) + end + else + rn[:observables] = nothing + end + + # this handles the majority of conversions from input data to graphics coordinates/metadata function update_plot(network) registers = network.registers + registercoords = rn[:registercoords][] all_nodes = [ # TODO it is rather wasteful to replot everything... do it smarter - register_rectangles, register_slots_coords, register_slots_coords_backref, - state_coords, state_coords_backref, state_links, twoqubitstate_links, - clrs, regs + register_rectangles, register_slots_coords, + state_coords, state_links, + register_slots_coords_backref, state_coords_backref, + observables_coords, observables_links, observables_vals, observables_linkvals ] - for a in all_nodes + for a in all_nodes # using a naive `lift` would allocate, so instead we just empty each array and refill it; can still be done more elegantly with lift and preallocation empty!(a[]) end - for (i,r) in enumerate(registers) # TODO this should use the layout/connectivity system - push!(register_rectangles[], Makie.Rect2(registercoords[i][1]-0.3,registercoords[i][2]+0.7-1,0.6,nsubsystems(r)-0.4)) - for j in 1:nsubsystems(r) - push!(register_slots_coords[], Makie.Point2{Float64}(registercoords[i][1],registercoords[i][2]+j-1)) - push!(register_slots_coords_backref[], (r,j)) + + # the location of the registers and the slots inside of the registers + for (iʳᵉᵍ,reg) in enumerate(registers) + xʳᵉᵍ = registercoords[iʳᵉᵍ][1]-0.3 + yʳᵉᵍ = registercoords[iʳᵉᵍ][2]+0.7-1 + Δxʳᵉᵍ = 0.6 + Δyʳᵉᵍ = nsubsystems(reg)-0.4 + push!(register_rectangles[], Rect2f(xʳᵉᵍ, yʳᵉᵍ, Δxʳᵉᵍ, Δyʳᵉᵍ)) + for iˢˡᵒᵗ in 1:nsubsystems(reg) + xˢˡᵒᵗ = registercoords[iʳᵉᵍ][1] + yˢˡᵒᵗ = registercoords[iʳᵉᵍ][2]+iˢˡᵒᵗ-1 + push!(register_slots_coords[], Point2f(xˢˡᵒᵗ,yˢˡᵒᵗ)) + push!(register_slots_coords_backref[], (reg,iʳᵉᵍ,iˢˡᵒᵗ)) end end - states = unique(vcat([[s for s in r.staterefs if !isnothing(s)] for r in registers]...)) + + # the locations of the state subsystem markers and the lines connecting them (denoting belonging to the same system) + states = unique(Iterators.flatten(((s for s in r.staterefs if !isnothing(s)) for r in registers))) for s in states - juststarted = true - for (si,(r,i)) in enumerate(zip(s.registers,s.registerindices)) - isnothing(r) && continue - whichreg = findfirst(o->===(r,o),registers) - #isnothing(whichreg) && continue - push!(state_coords[], Makie.Point2{Float64}(registercoords[whichreg][1], registercoords[whichreg][2]+i-1)) - push!(state_coords_backref[],(s,si)) - if nsubsystems(s)==1 - break - end - push!(state_links[], Makie.Point2{Float64}(registercoords[whichreg][1], registercoords[whichreg][2]+i-1)) - if nsubsystems(s)==2 && !isnothing(twoqubitobservable) - push!(regs[], (whichreg, i, s)) - push!(twoqubitstate_links[], Makie.Point2{Float64}(registercoords[whichreg][1], registercoords[whichreg][2]+i-1)) - end - if !juststarted && si===(reg,o),registers) # TODO -- some form of caching or a backref would be valuable to significantly optimize this (skip the need for a O(n) search) + isnothing(whichreg) && continue # TODO -- the state does not belong to a register in the network... maybe it is in a temporary message buffer register? + xˢ = registercoords[whichreg][1] + yˢ = registercoords[whichreg][2]+iˢˡᵒᵗ-1 + pˢ = Point2f(xˢ, yˢ) + push!(state_coords[], pˢ) + push!(state_coords_backref[], (s, network[whichreg], whichreg, iˢˡᵒᵗ, iˢ)) + nsubsystems(s) == 1 || push!(state_links[], pˢ) + iˢ == 1 || iˢ == nsubsystems(s) || push!(state_links[], pˢ) end end - if !isnothing(twoqubitobservable) - for i in 1:2:length(regs[]) - s = regs[][i][3] - fid = real(observable([registers[regs[][i][1]][regs[][i][2]], registers[regs[][i+1][1]][regs[][i+1][2]]], twoqubitobservable)) - push!(clrs[], fid) + + ## the colors and locations for various observables + if !isnothing(rn[:observables][]) + for (O, rsidx, links) in rn[:observables][] + val = real(observable(tuple((network[rs...] for rs in rsidx)...), O, NaN)) + # TODO issue a warning if val has (percentage-wise) significant imaginary component (here, for plotting, when we implicitly are taking the real part) + for (iʳᵉᵍ, iˢˡᵒᵗ) in rsidx + xˢ = registercoords[iʳᵉᵍ][1] + yˢ = registercoords[iʳᵉᵍ][2]+iˢˡᵒᵗ-1 + pˢ = Point2f(xˢ, yˢ) + push!(observables_coords[], pˢ) + push!(observables_vals[], val) + push!(observables_backref[], (O, val)) + end + for (iʳᵉᵍ, iˢˡᵒᵗ) in links + xˢ = registercoords[iʳᵉᵍ][1] + yˢ = registercoords[iʳᵉᵍ][2]+iˢˡᵒᵗ-1 + pˢ = Point2f(xˢ, yˢ) + push!(observables_links[], pˢ) + push!(observables_linkvals[], val) + push!(observables_links_backref[], (O, val)) end end + end for a in all_nodes notify(a) end end + + # populate all graphical coordinates / metadata for the first time + update_plot(networkobs[]) + + # set up event modification notifications Makie.Observables.onany(update_plot, networkobs) - update_plot(rn[1][]) - register_polyplot = Makie.poly!(rn,register_rectangles,color=:gray90) - register_polyplot.inspectable[] = false - register_slots_scatterplot = Makie.scatter!(rn,register_slots_coords,marker=:rect,color=:gray60,markersize=0.6,markerspace=:data) - state_scatterplot = Makie.scatter!(rn,state_coords,marker=:diamond,color=:black,markersize=0.4,markerspace=:data) - state_lineplot = Makie.linesegments!(rn,state_links,color=:gray90) - twoqubitstate_lineplot = Makie.linesegments!(rn,twoqubitstate_links,color= !isnothing(twoqubitobservable) ? clrs : (:gray90), colormap=:Spectral, colorrange = (0,1)) + Makie.Observables.onany([rn[:registercoords], rn[:observables]]) do _ + update_plot(networkobs) + end + + # generate the actual graphics + register_polyplot = poly!(rn, register_rectangles, color=rn[:register_color], + inspector_label = (self, i, p) -> "a register") + register_polyplot.inspectable[] = false # TODO this `Poly` plot does not seem to be properly inspectable + register_slots_scatterplot = scatter!( + rn, register_slots_coords, + marker=rn[:slotmarker], markersize=rn[:slotsize], color=rn[:slotcolor], + markerspace=:data, + inspector_label = (self, i, p) -> get_slots_vis_string(register_slots_coords_backref[],i)) + observables_scatterplot = scatter!( + rn, observables_coords, + marker=rn[:observables_marker], markersize=rn[:observables_markersize], markerspace=:data, + color=observables_vals, colormap=rn[:colormap], colorrange=rn[:colorrange], + inspector_label = (self, i, p) -> get_observables_vis_string(observables_backref[],i)) + observables_linesegments = linesegments!( + rn, observables_links, + linewidth=rn[:observables_linewidth], markerspace=:data, + color=observables_linkvals, colormap=rn[:colormap], colorrange=rn[:colorrange], + inspector_label = (self, i, p) -> get_observables_vis_string(observables_links_backref[],i)) + state_scatterplot = scatter!( + rn, state_coords, + marker=rn[:state_marker], markersize=rn[:state_markersize], color=rn[:state_markercolor], + markerspace=:data, + inspector_label = (self, i, p) -> get_state_vis_string(state_coords_backref[],i)) + state_linesegmentsplot = linesegments!(rn, state_links, color=rn[:state_linecolor]) + state_linesegmentsplot.inspectable[] = false + + # TODO all of these should be wrapped into their own types in order to simplify DataInspector and process_interaction rn[:register_polyplot] = register_polyplot rn[:register_slots_scatterplot] = register_slots_scatterplot - rn[:register_slots_coords_backref] = register_slots_coords_backref + rn[:observables_scatterplot] = observables_scatterplot + rn[:observables_linesegmentsplot] = observables_linesegments rn[:state_scatterplot] = state_scatterplot - rn[:state_coords_backref] = state_coords_backref - rn[:state_lineplot] = state_lineplot - rn[:twoqubitstate_lineplot] = twoqubitstate_lineplot - rn[:twoqubitobservable] = twoqubitobservable - rn[:fids] = clrs + rn[:state_linesegmentsplot] = state_linesegmentsplot rn end +function get_observables_vis_string(backrefs, i) + o, val = backrefs[i] + return "Observable ⟨$(o)⟩ = $(val)" +end + +function get_slots_vis_string(backrefs, i) + register, registeridx, slot = backrefs[i] + tags = register.tags[slot] + tags_str = if isempty(tags) + "not tagged" + else + "tagged with:\n"*join((" • $(t)" for t in tags), "\n") + end + return "Register $(registeridx) | Slot $(slot)\n $(tags_str)" +end + +function get_state_vis_string(backrefs, i) + state, register, registeridx, slot, subsystem = backrefs[i] + tags = register.tags[slot] + tags_str = if isempty(tags) + "not tagged" + else + "tagged with:\n"*join((" • $(t)" for t in tags), "\n") + end + return "Subsystem $(subsystem) of a state of $(nsubsystems(state)) subsystems, stored in\nRegister $(registeridx) | Slot $(slot)\n $(tags_str)" +end + abstract type RegisterNetGraphHandler end struct RNHandler <: RegisterNetGraphHandler rn end function Makie.process_interaction(handler::RNHandler, event::Makie.MouseEvent, axis) - plot, index = Makie.mouse_selection(axis.scene) + plot, index = Makie.pick(axis.scene) rn = handler.rn + #if plot===rn[:register_polyplot][] # TODO this does not work because poly seems to be much too basic for `pick` to provide a useful reference + # register = rn[:register_backref][][index] + # run(`clear`) + # println("$(register)") + #else if plot===rn[:register_slots_scatterplot][] - register, slot = rn[:register_slots_coords_backref][][index] + register, registeridx, slot = rn[:register_slots_coords_backref][][index] run(`clear`) - println("Slot $(slot) of $(register)") + println("Register $registeridx | Slot $(slot)\n Details: $(register)") elseif plot===rn[:state_scatterplot][] - state, subsystem = rn[:state_coords_backref][][index] + state, reg, registeridx, slot, subsystem = rn[:state_coords_backref][][index] run(`clear`) - println("Subsystem $(subsystem) of $(state)") + println("Subsystem stored in Register $(registeridx) | Slot $(slot)\n Subsystem $(subsystem) of $(state)") + elseif plot===rn[:observables_scatterplot][] + o, val = rn[:observables_backref][][index] + run(`clear`) + println("Observable $(o) has value $(val)") end false end @@ -136,19 +285,20 @@ end It returns a tuple of (subfigure, axis, plot, observable). The observable can be used to issue a `notify` call that updates the plot with the current state of the network.""" -function registernetplot_axis(subfig, registersobservable; registercoords=nothing, interactions=false, twoqubitobservable=nothing) +function registernetplot_axis(subfig, registersobservable; infocli=true, datainspector=true, kwargs...) ax = Makie.Axis(subfig) - p = registernetplot!(ax, registersobservable, - registercoords=registercoords, twoqubitobservable=twoqubitobservable - ) + p = registernetplot!(ax, registersobservable; kwargs...) ax.aspect = Makie.DataAspect() Makie.hidedecorations!(ax) Makie.hidespines!(ax) Makie.deregister_interaction!(ax, :rectanglezoom) - if interactions + if infocli rnh = RNHandler(p) Makie.register_interaction!(ax, :registernet, rnh) end + if datainspector + DataInspector(subfig) + end Makie.autolimits!(ax) subfig, ax, p, p[1] end diff --git a/src/ProtocolZoo/ProtocolZoo.jl b/src/ProtocolZoo/ProtocolZoo.jl index 21452eb0..95c1aa96 100644 --- a/src/ProtocolZoo/ProtocolZoo.jl +++ b/src/ProtocolZoo/ProtocolZoo.jl @@ -173,6 +173,9 @@ end end end +function random_index(arr) + return rand(keys(arr)) +end """ $TYPEDEF @@ -181,17 +184,21 @@ A protocol, running at a given node, that finds swappable entangled pairs and pe $FIELDS """ -@kwdef struct SwapperProt{L,R,LT} <: AbstractProtocol where {L<:Union{Int,<:Function,Wildcard}, R<:Union{Int,<:Function,Wildcard}, LT<:Union{Float64,Nothing}} +@kwdef struct SwapperProt{NL,NH,CL,CH,LT} <: AbstractProtocol where {NL<:Union{Int,<:Function,Wildcard}, NH<:Union{Int,<:Function,Wildcard}, CL<:Function, CH<:Function, LT<:Union{Float64,Nothing}} """time-and-schedule-tracking instance from `ConcurrentSim`""" sim::Simulation """a network graph of registers""" net::RegisterNet """the vertex of the node where swapping is happening""" node::Int - """the vertex of one of the remote nodes (or a predicate function or a wildcard)""" - nodeL::L = ❓ - """the vertex of the other remote node (or a predicate function or a wildcard)""" - nodeR::R = ❓ + """the vertex of one of the remote nodes for the swap, arbitrarily referred to as the "low" node (or a predicate function or a wildcard); if you are working on a repeater chain, a good choice is `<(current_node)`, i.e. any node to the "left" of the current node""" + nodeL::NL = ❓ + """the vertex of the other remote node for the swap, the "high" counterpart of `nodeL`; if you are working on a repeater chain, a good choice is `>(current_node)`, i.e. any node to the "right" of the current node""" + nodeH::NH = ❓ + """the `nodeL` predicate can return many positive candidates; `chooseL` picks one of them (by index into the array of filtered `nodeL` results), defaults to a random pick `arr->rand(keys(arr))`; if you are working on a repeater chain a good choice is `argmin`, i.e. the node furthest to the "left" """ + chooseL::CL = random_index + """the `nodeH` counterpart for `chooseH`; if you are working on a repeater chain a good choice is `argmax`, i.e. the node furthest to the "right" """ + chooseH::CH = random_index """fixed "busy time" duration immediately before starting entanglement generation attempts""" local_busy_time::Float64 = 0.0 # TODO the gates should have that busy time built in """how long to wait before retrying to lock qubits if no qubits are available (`nothing` for queuing up and waiting)""" @@ -210,7 +217,7 @@ end rounds = prot.rounds while rounds != 0 reg = prot.net[prot.node] - qubit_pair = findswapablequbits(prot.net,prot.node) + qubit_pair = findswapablequbits(prot.net, prot.node, prot.nodeL, prot.nodeH, prot.chooseL, prot.chooseH) if isnothing(qubit_pair) isnothing(prot.retry_lock_time) && error("We do not yet support waiting on register to make qubits available") # TODO @yield timeout(prot.sim, prot.retry_lock_time) @@ -247,16 +254,16 @@ end end end -function findswapablequbits(net,node) # TODO parameterize the query predicates and the findmin/findmax +function findswapablequbits(net, node, pred_low, pred_high, choose_low, choose_high) reg = net[node] - leftnodes = queryall(reg, EntanglementCounterpart, <(node), ❓; locked=false, assigned=true) - rightnodes = queryall(reg, EntanglementCounterpart, >(node), ❓; locked=false, assigned=true) + low_nodes = queryall(reg, EntanglementCounterpart, pred_low, ❓; locked=false, assigned=true) + high_nodes = queryall(reg, EntanglementCounterpart, pred_high, ❓; locked=false, assigned=true) - (isempty(leftnodes) || isempty(rightnodes)) && return nothing - _, il = findmin(n->n.tag[2], leftnodes) # TODO make [2] into a nice named property - _, ir = findmax(n->n.tag[2], rightnodes) - return leftnodes[il], rightnodes[ir] + (isempty(low_nodes) || isempty(high_nodes)) && return nothing + il = choose_low((n.tag[2] for n in low_nodes)) # TODO make [2] into a nice named property + ih = choose_high((n.tag[2] for n in high_nodes)) + return low_nodes[il], high_nodes[ih] end diff --git a/src/QuantumSavory.jl b/src/QuantumSavory.jl index d6848082..b6928b1b 100644 --- a/src/QuantumSavory.jl +++ b/src/QuantumSavory.jl @@ -59,7 +59,7 @@ export # noninstant.jl AbstractNoninstantOperation, NonInstantGate, ConstantHamiltonianEvolution, # plots.jl - registernetplot, registernetplot_axis, resourceplot_axis + registernetplot, registernetplot!, registernetplot_axis, resourceplot_axis #TODO you can not assume you can always in-place modify a state. Have all these functions work on stateref, not stateref[] diff --git a/src/plots.jl b/src/plots.jl index 554a33c8..960ce563 100644 --- a/src/plots.jl +++ b/src/plots.jl @@ -1,9 +1,14 @@ -"""Draw the given registers on a given Makie axis. +"""Draw the given register network. Requires a Makie backend be already imported.""" function registernetplot end -"""Draw the given registers on a given Makie axis. +"""Draw the given register network on a given Makie axis. + +Requires a Makie backend be already imported.""" +function registernetplot! end + +"""Draw the given register network on a given Makie subfigure and modify the axis with numerous visualization enhancements. Requires a Makie backend be already imported.""" function registernetplot_axis end diff --git a/test/runtests.jl b/test/runtests.jl index 7fc4b624..be159025 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -32,6 +32,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "messagebuffer" @doset "tags_and_queries" @doset "entanglement_tracker" +@doset "entanglement_tracker_grid" @doset "circuitzoo_api" @doset "circuitzoo_ent_swap" diff --git a/test/test_entanglement_tracker.jl b/test/test_entanglement_tracker.jl index 3698fd06..b75ac758 100644 --- a/test/test_entanglement_tracker.jl +++ b/test/test_entanglement_tracker.jl @@ -46,8 +46,8 @@ for i in 1:10 @test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false - swapper2 = SwapperProt(sim, net, 2; rounds=1) - swapper3 = SwapperProt(sim, net, 3; rounds=1) + swapper2 = SwapperProt(sim, net, 2; nodeL = <(2), nodeH = >(2), chooseL = argmin, chooseH = argmax, rounds = 1) + swapper3 = SwapperProt(sim, net, 3; nodeL = <(3), nodeH = >(3), chooseL = argmin, chooseH = argmax, rounds = 1) @process swapper2() @process swapper3() run(sim, 80) @@ -102,7 +102,7 @@ for i in 1:30, n in 2:30 @process eprot() end for j in 2:n-1 - swapper = SwapperProt(sim, net, j; rounds=1) + swapper = SwapperProt(sim, net, j; nodeL = <(j), nodeH = >(j), chooseL = argmin, chooseH = argmax, rounds = 1) @process swapper() end run(sim, 200) diff --git a/test/test_entanglement_tracker_grid.jl b/test/test_entanglement_tracker_grid.jl new file mode 100644 index 00000000..0dc74860 --- /dev/null +++ b/test/test_entanglement_tracker_grid.jl @@ -0,0 +1,151 @@ +using Revise +using QuantumSavory +using ResumableFunctions +using ConcurrentSim +using QuantumSavory.ProtocolZoo +using QuantumSavory.ProtocolZoo: EntanglementCounterpart, EntanglementHistory, EntanglementUpdateX, EntanglementUpdateZ +using Graphs +using Test + +if isinteractive() + using Logging + logger = ConsoleLogger(Logging.Debug; meta_formatter=(args...)->(:black,"","")) + global_logger(logger) + println("Logger set to debug") +end + +## +# Here we test entanglement tracker and swapper protocols on an arbitrary hardcoded path of long-range connection inside of what is otherwise a 2D grid +# We do NOT test anything related to automatic routing on such a grid -- only the hardcoded path is tested +## + +## Custom Predicates + +function top_left(net, node, x) + n = sqrt(size(net.graph)[1]) # grid size + a = (node ÷ n) + 1 # row number + for i in 1:a-1 + if x == (i-1)*n + i + return true + end + end + return false +end + +function bottom_right(net, node, x) + n = sqrt(size(net.graph)[1]) # grid size + a = (node ÷ n) + 1 # row number + for i in a+1:n + if x == (i-1)*n + i + return true + end + end + return false +end + +## Simulation + +## without entanglement tracker - this is almost the same test as the one in test_entanglement_tracker.jl which tests a simple chain -- the only difference is that we have picked a few hardcoded arbitrary nodes through a grid (creating an ad-hoc chain) +for i in 1:10 + graph = grid([4, 4]) + add_edge!(graph, 1, 6) + add_edge!(graph, 6, 11) + add_edge!(graph, 11, 16) + + net = RegisterNet(graph, [Register(3) for i in 1:16]) + sim = get_time_tracker(net) + + + entangler1 = EntanglerProt(sim, net, 1, 6; rounds=1) + @process entangler1() + run(sim, 20) + + @test net[1].tags == [[Tag(EntanglementCounterpart, 6, 1)],[],[]] + + + entangler2 = EntanglerProt(sim, net, 6, 11; rounds=1) + @process entangler2() + run(sim, 40) + entangler3 = EntanglerProt(sim, net, 11, 16; rounds=1) + @process entangler3() + run(sim, 60) + + @test net[1].tags == [[Tag(EntanglementCounterpart, 6, 1)],[],[]] + @test net[6].tags == [[Tag(EntanglementCounterpart, 1, 1)],[Tag(EntanglementCounterpart, 11, 1)],[]] + @test net[11].tags == [[Tag(EntanglementCounterpart, 6, 2)],[Tag(EntanglementCounterpart, 16, 1)], []] + @test net[16].tags == [[Tag(EntanglementCounterpart, 11, 2)],[],[]] + + @test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false + + l1(x) = top_left(net, 6, x) + h1(x) = bottom_right(net, 6, x) + swapper2 = SwapperProt(sim, net, 6; nodeL=l1, nodeH=h1, rounds=1) + l2(x) = top_left(net, 11, x) + h2(x) = bottom_right(net, 11, x) + swapper3 = SwapperProt(sim, net, 11; nodeL=l2, nodeH=h2, rounds=1) + @process swapper2() + @process swapper3() + run(sim, 80) + + # In the absence of an entanglement tracker the tags will not all be updated + @test net[1].tags == [[Tag(EntanglementCounterpart, 6, 1)],[],[]] + @test net[6].tags == [[Tag(EntanglementHistory, 1, 1, 11, 1, 2)],[Tag(EntanglementHistory, 11, 1, 1, 1, 1)],[]] + @test net[11].tags == [[Tag(EntanglementHistory, 6, 2, 16, 1, 2)],[Tag(EntanglementHistory, 16, 1, 6, 2, 1)], []] + @test net[16].tags == [[Tag(EntanglementCounterpart, 11, 2)],[],[]] + + @test isassigned(net[1][1]) && isassigned(net[16][1]) + @test !isassigned(net[6][1]) && !isassigned(net[11][1]) + @test !isassigned(net[6][2]) && !isassigned(net[11][2]) + + @test [islocked(ref) for i in vertices(net) for ref in net[i]] |> any == false + +end + +## with entanglement tracker -- here we hardcode the diagonal of the grid as the path on which we are making connections +for n in 4:10 + graph = grid([n,n]) + + diag_pairs = [] + diag_nodes = [] + reg_num = 1 # starting register + for i in 1:n-1 # a grid with n nodes has n-1 pairs of diagonal nodes + push!(diag_pairs, (reg_num, reg_num+n+1)) + push!(diag_nodes, reg_num) + reg_num += n + 1 + end + push!(diag_nodes, n^2) + + for (src, dst) in diag_pairs # need edges down the diagonal to establish cchannels and qchannels between the diagonal nodes + add_edge!(graph, src, dst) + end + + net = RegisterNet(graph, [Register(8) for i in 1:n^2]) + + sim = get_time_tracker(net) + + for (src, dst) in diag_pairs + eprot = EntanglerProt(sim, net, src, dst; rounds=1, randomize=true) + @process eprot() + end + + for i in 2:n-1 + l(x) = top_left(net, diag_nodes[i], x) + h(x) = bottom_right(net, diag_nodes[i], x) + swapper = SwapperProt(sim, net, diag_nodes[i]; nodeL = l, nodeH = h, rounds = 1) + @process swapper() + end + + for v in diag_nodes + tracker = EntanglementTracker(sim, net, v) + @process tracker() + end + + run(sim, 200) + + q1 = query(net[1], EntanglementCounterpart, diag_nodes[n], ❓) + q2 = query(net[diag_nodes[n]], EntanglementCounterpart, 1, ❓) + @test q1.tag[2] == diag_nodes[n] + @test q2.tag[2] == 1 + @test observable((q1.slot, q2.slot), Z⊗Z) ≈ 1 + @test observable((q1.slot, q2.slot), X⊗X) ≈ 1 +end diff --git a/test/test_examples.jl b/test/test_examples.jl index 4ad1e2cf..6a78395b 100644 --- a/test/test_examples.jl +++ b/test/test_examples.jl @@ -17,6 +17,13 @@ end end end +@safetestset "firstgenrepeater_v2" begin + if get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" + include("../examples/firstgenrepeater_v2/1_entangler_example.jl") + include("../examples/firstgenrepeater_v2/2_swapper_example.jl") + end +end + @safetestset "colorcentermodularcluster" begin include("../examples/colorcentermodularcluster/1_time_to_connected.jl") if get(ENV,"QUANTUMSAVORY_PLOT_TEST","")=="true" diff --git a/test/test_plotting.jl b/test/test_plotting_1_regcoords.jl similarity index 100% rename from test/test_plotting.jl rename to test/test_plotting_1_regcoords.jl diff --git a/test/test_plotting_2_tags_observables.jl b/test/test_plotting_2_tags_observables.jl new file mode 100644 index 00000000..659e9507 --- /dev/null +++ b/test/test_plotting_2_tags_observables.jl @@ -0,0 +1,97 @@ +using QuantumSavory +#using CairoMakie +#using GLMakie +using Graphs +using FileIO + +## + +regnet = RegisterNet([Register(2), Register(3)]) +fig = Figure() +ax = Makie.Axis(fig[1, 1]) +p = registernetplot!(ax, regnet) +ax.aspect = Makie.DataAspect() +Makie.hidedecorations!(ax) +Makie.hidespines!(ax) +fig + +## + +regnet = RegisterNet([Register(2), Register(3)]) + +## + +fig = Figure() +ax = Axis(fig[1,1]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), registercoords=rand(2,1)) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), registercoords=rand(1,2)) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), registercoords=[1.1,1.1]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), registercoords=[[1.1,1.1]]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), registercoords=[(1.1,1.1)]) +registernetplot!(ax, RegisterNet([Register(2)]), registercoords=[Point2f(1,1)]) +display(fig) + +## + +fig = Figure() +ax = Axis(fig[1,1]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), observables=[("pretend I am an operator",)]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), observables=[(X, 1)]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), observables=[(X, (1,1))]) +registernetplot!(ax, RegisterNet([Register(2)]), observables=[(X, ((1,1),))]) +net = RegisterNet([Register(2)]) +initialize!(net[1,1], X1) +registernetplot!(ax, net, observables=[(X, ((1,1),), [(1,1),(1,1)])]) +@test_throws ArgumentError registernetplot!(ax, RegisterNet([Register(2)]), observables=[(X, ((1,1),), ((1,1),))]) # TODO consider permitting this +display(fig) + +## + +fig = Figure() +ax = Axis(fig[1,1]) +net = RegisterNet([Register(2),Register(2),Register(2), Register(1)]) +initialize!((net[1,1], net[2,2], net[3,2]), X1⊗X1⊗X1) +initialize!((net[1,2], net[3,1]), X1⊗X1) +initialize!(net[2,1], X1) +p = registernetplot!(ax, net, observables=[(X, ((1,2),)), (X⊗X⊗X, ((1,1),(2,2),(3,2)))]) +display(fig) + +## + +fig = Figure() +registernetplot_axis(fig[1,1], RegisterNet([Register(1), Register(2)]), + slotcolor=:red) +display(fig) + +## + +fig = Figure() +registernetplot_axis(fig[1,1], RegisterNet([Register(1), Register(2)]), + slotcolor=(:red,0.1)) +display(fig) + +## + +fig = Figure() +registernetplot_axis(fig[1,1], RegisterNet([Register(1), Register(2)]), + slotcolor=[(:red,0.1), :blue, :gray10]) +display(fig) + +## + +fig = Figure() +registernetplot_axis(fig[1,1], RegisterNet([Register(1), Register(2)]), + slotcolor=[[(:red,0.1)], [:blue, :gray10]]) +display(fig) + +## + +fig = Figure() +net = RegisterNet([Register(2),Register(2),Register(2), Register(1)]) +initialize!((net[1,1], net[2,2], net[3,2]), X1⊗X1⊗X1) +initialize!((net[1,2], net[3,1]), X1⊗X1) +tag!(net[4,1], Tag(:mytag, 1, 2)) +tag!(net[3,1], Tag(:sometag, 10, 20)) +initialize!(net[2,1], X1) +p = registernetplot_axis(fig[1,1], net, observables=[(X, ((1,2),)), (X⊗X⊗X, ((1,1),(2,2),(3,2)))], infocli=false) +display(fig) diff --git a/test/test_plotting_cairo.jl b/test/test_plotting_cairo.jl index fceb4a1b..cb038064 100644 --- a/test/test_plotting_cairo.jl +++ b/test/test_plotting_cairo.jl @@ -1,3 +1,10 @@ +using Test using CairoMakie CairoMakie.activate!() -include("test_plotting.jl") + +@testset "register coordinates" begin + include("test_plotting_1_regcoords.jl") +end +@testset "arguments and observables and tags" begin + include("test_plotting_2_tags_observables.jl") +end diff --git a/test/test_plotting_gl.jl b/test/test_plotting_gl.jl index 9957982c..c9acda48 100644 --- a/test/test_plotting_gl.jl +++ b/test/test_plotting_gl.jl @@ -1,3 +1,10 @@ +using Test using GLMakie GLMakie.activate!() -include("test_plotting.jl") + +@testset "register coordinates" begin + include("test_plotting_1_regcoords.jl") +end +@testset "arguments and observables and tags" begin + include("test_plotting_2_tags_observables.jl") +end