Tagging and Querying

The query and tag! interface lets you manage "classical state" metadata in your simulations. In particular, this interface enables the creation of modular interoperable control protocols. Each protocol can operate independently of others without knowledge of each others' internals. This is done by using various "tags" to communicate metadata between the network nodes running the protocols, and by the protocols querying for the presence of such tags, leading to greater flexibility when setting up different simulations.

The components of the query interface which make this possible are described below.

The Tag type

QuantumSavory.TagType

Tags are used to represent classical metadata describing the state (or even history) of nodes and their registers. The library allows the construction of custom tags using the Tag constructor. Currently tags are implemented as instances of a sum type and have fairly constrained structure. Most of them are constrained to contain only Symbol instances and integers.

Here is an example of such a generic tag:

julia> Tag(:sometagdescriptor, 1, 2, -3)
SymbolIntIntInt(:sometagdescriptor, 1, 2, -3)::Tag

A tag can have a custom DataType as first argument, in which case additional customizability in printing is available. E.g. consider the [EntanglementHistory] tag used to track how pairs were entangled before a swap happened.

julia> using QuantumSavory.ProtocolZoo: EntanglementHistory

julia> Tag(EntanglementHistory, 1, 2, 3, 4, 5)
Was entangled to 1.2, but swapped with .5 which was entangled to 3.4

See also: tag!, query

source

And here are all currently supported tag signatures:

[tuple(m.sig.types[2:end]...) for m in methods(Tag) if m.sig.types[2] ∈ (Symbol, DataType)]
17-element Vector{Tuple{DataType, Vararg{DataType}}}:
 (Symbol, Int64, Int64, Int64)
 (Symbol, Int64, Int64)
 (Symbol, Int64)
 (Symbol, Int64, Int64, Int64, Int64, Int64, Int64)
 (Symbol, Int64, Int64, Int64, Int64, Int64)
 (Symbol, Int64, Int64, Int64, Int64)
 (Symbol,)
 (DataType,)
 (DataType, Int64)
 (DataType, Int64, Int64)
 (DataType, Int64, Int64, Int64)
 (DataType, Int64, Int64, Int64, Int64)
 (DataType, Int64, Int64, Int64, Int64, Int64)
 (DataType, Int64, Int64, Int64, Int64, Int64, Int64)
 (DataType, Symbol)
 (DataType, Symbol, Int64)
 (DataType, Symbol, Int64, Int64)

Assigning and removing tags

Querying for the presence of a tag

The query function allows the user to query for Tags in three different cases:

  • on a particular qubit slot (RegRef) in a Register node;
  • on a Register to query for any slot that contains the passed Tag;
  • on a messagebuffer to query for a particular Tag received from another node in a network.

The Tag description passed to query can include predicate functions (of the form x -> pass::Bool) and wildcards (the variable), for situations where we have freedom in what tag we are exactly searching for.

The queries can search in FIFO or FILO order (FILO by default). E.g., for the default FILO, a query on a RegRef returns the Tag which is at the end of the vector of tags stored the given slot (as new tags are appended at the end). On a Register it returns the slot with the "youngest" age.

One can also query by "lock" and "assignment" status of a given slot, by using the locked and assigned boolean keywords. By default these keywords are set to nothing and these properties are not checked.

Following is a detailed description of each query method

QuantumSavory.queryFunction
query(
    reg::Union{RegRef, Register},
    queryargs::Union{QuantumSavory.Wildcard, Int64, DataType, Function, Symbol}...;
    locked,
    assigned,
    filo
) -> Any

A query function searching for the first slot in a register that has a given tag.

Wildcards are supported (instances of Wildcard also available as the constants W or the emoji which can be entered as \:question: in the REPL). Predicate functions are also supported (they have to be IntBool functions). The order of query lookup can be specified in terms of FIFO or FILO and defaults to FILO if not specified. The keyword arguments locked and assigned can be used to check, respectively, whether the given slot is locked or whether it contains a quantum state. The keyword argument filo can be used to specify whether the search should be done in a FIFO or FILO order, defaulting to filo=true (i.e. a stack-like behavior).

julia> r = Register(10);
       tag!(r[1], :symbol, 2, 3);
       tag!(r[2], :symbol, 4, 5);


julia> query(r, :symbol, 4, 5)
(slot = Slot 2, id = 4, tag = SymbolIntInt(:symbol, 4, 5)::Tag)

julia> lock(r[1]);

julia> query(r, :symbol, 4, 5; locked=false) |> isnothing
false

julia> query(r, :symbol, ❓, 3)
(slot = Slot 1, id = 3, tag = SymbolIntInt(:symbol, 2, 3)::Tag)

julia> query(r, :symbol, ❓, 3; assigned=true) |> isnothing
true

julia> query(r, :othersym, ❓, ❓) |> isnothing
true

julia> tag!(r[5], Int, 4, 5);

julia> query(r, Float64, 4, 5) |> isnothing
true

julia> query(r, Int, 4, >(7)) |> isnothing
true

julia> query(r, Int, 4, <(7))
(slot = Slot 5, id = 5, tag = TypeIntInt(Int64, 4, 5)::Tag)

A query can be on on a single slot of a register:

julia> r = Register(5);

julia> tag!(r[2], :symbol, 2, 3);

julia> query(r[2], :symbol, 2, 3)
(slot = Slot 2, id = 6, tag = SymbolIntInt(:symbol, 2, 3)::Tag)

julia> query(r[3], :symbol, 2, 3) === nothing
true

julia> queryall(r[2], :symbol, 2, 3)
1-element Vector{@NamedTuple{slot::RegRef, id::Int128, tag::Tag}}:
 (slot = Slot 2, id = 6, tag = SymbolIntInt(:symbol, 2, 3)::Tag)

See also: queryall, tag!, W,

source
query(
    mb::MessageBuffer,
    queryargs::Union{QuantumSavory.Wildcard, Int64, DataType, Function, Symbol}...
) -> Union{Nothing, NamedTuple{(:depth, :src, :tag), <:Tuple{Int64, Any, Any}}}

You are advised to actually use querydelete!, not query when working with classical message buffers.

source

Wildcards

QuantumSavory.❓Constant

A wildcard instance for use with the tag querying functionality.

This emoji can be inputted with the \:question: emoji shortcut, or you can simply use the ASCII alternative W.

See also: query, tag!, W

source

querydelete!

A method on top of query, which allows to query for tag in a RegRef or a messagebuffer, returning the tag that satisfies the passed predicates and wildcars, and deleting it from the list at the same time. It otherwise has the same signature as query.

QuantumSavory.querydelete!Function
querydelete!(
    mb::MessageBuffer,
    args...
) -> Union{Nothing, @NamedTuple{src::Union{Nothing, Int64}, tag::T} where T}

A query for classical message buffers that also deletes the message out of the buffer.

julia> net = RegisterNet([Register(3), Register(2)])
A network of 2 registers in a graph of 1 edges

julia> put!(channel(net, 1=>2), Tag(:my_tag));

julia> put!(channel(net, 1=>2), Tag(:another_tag, 123, 456));

julia> query(messagebuffer(net, 2), :my_tag)

julia> run(get_time_tracker(net))

julia> query(messagebuffer(net, 2), :my_tag)
(depth = 1, src = 1, tag = Symbol(:my_tag)::Tag)

julia> querydelete!(messagebuffer(net, 2), :my_tag)
@NamedTuple{src::Union{Nothing, Int64}, tag::Tag}((1, Symbol(:my_tag)::Tag))

julia> querydelete!(messagebuffer(net, 2), :my_tag) === nothing
true

julia> querydelete!(messagebuffer(net, 2), :another_tag, ❓, ❓)
@NamedTuple{src::Union{Nothing, Int64}, tag::Tag}((1, SymbolIntInt(:another_tag, 123, 456)::Tag))

julia> querydelete!(messagebuffer(net, 2), :another_tag, ❓, ❓) === nothing
true

You can also wait on a message buffer for a message to arrive before running a query:

julia> using ResumableFunctions; using ConcurrentSim;

julia> net = RegisterNet([Register(3), Register(2), Register(3)])
A network of 3 registers in a graph of 2 edges

julia> env = get_time_tracker(net);

julia> @resumable function receive_tags(env)
           while true
               mb = messagebuffer(net, 2)
               @yield wait(mb)
               msg = querydelete!(mb, :second_tag, ❓, ❓)
               print("t=$(now(env)): query returns ")
               if isnothing(msg)
                   println("nothing")
               else
                   println("$(msg.tag) received from node $(msg.src)")
               end
           end
       end
receive_tags (generic function with 1 method)

julia> @resumable function send_tags(env)
           @yield timeout(env, 1.0)
           put!(channel(net, 1=>2), Tag(:my_tag))
           @yield timeout(env, 2.0)
           put!(channel(net, 3=>2), Tag(:second_tag, 123, 456))
       end
send_tags (generic function with 1 method)

julia> @process send_tags(env);

julia> @process receive_tags(env);

julia> run(env, 10)
t=1.0: query returns nothing
t=3.0: query returns SymbolIntInt(:second_tag, 123, 456)::Tag received from node 3
source
querydelete!(
    reg::Union{RegRef, Register},
    args...;
    kwa...
) -> Any

A query for Register or a register slot (i.e. a RegRef) that also deletes the tag.

julia> reg = Register(3)
       tag!(reg[1], :tagA, 1, 2, 3)
       tag!(reg[2], :tagA, 10, 20, 30)
       tag!(reg[2], :tagB, 6, 7, 8);

julia> queryall(reg, :tagA, ❓, ❓, ❓)
2-element Vector{@NamedTuple{slot::RegRef, id::Int128, tag::Tag}}:
 (slot = Slot 2, id = 4, tag = SymbolIntIntInt(:tagA, 10, 20, 30)::Tag)
 (slot = Slot 1, id = 3, tag = SymbolIntIntInt(:tagA, 1, 2, 3)::Tag)

julia> querydelete!(reg, :tagA, ❓, ❓, ❓)
(slot = Slot 2, id = 4, tag = SymbolIntIntInt(:tagA, 10, 20, 30)::Tag)

julia> queryall(reg, :tagA, ❓, ❓, ❓)
1-element Vector{@NamedTuple{slot::RegRef, id::Int128, tag::Tag}}:
 (slot = Slot 1, id = 3, tag = SymbolIntIntInt(:tagA, 1, 2, 3)::Tag)
source

queryall

A method defined on top of query which allows to query for all tags in a RegRef or a Register that match the query.

QuantumSavory.queryallFunction
queryall(
    reg::Union{RegRef, Register},
    queryargs::Union{QuantumSavory.Wildcard, Int64, DataType, Function, Symbol}...;
    filo,
    kwargs...
) -> Any

A query function that returns all slots of a register that have a given tag, with support for predicates and wildcards.

julia> r = Register(10);
       tag!(r[1], :symbol, 2, 3);
       tag!(r[2], :symbol, 4, 5);

julia> queryall(r, :symbol, ❓, ❓)
2-element Vector{@NamedTuple{slot::RegRef, id::Int128, tag::Tag}}:
 (slot = Slot 2, id = 2, tag = SymbolIntInt(:symbol, 4, 5)::Tag)
 (slot = Slot 1, id = 1, tag = SymbolIntInt(:symbol, 2, 3)::Tag)

julia> queryall(r, :symbol, ❓, >(4))
1-element Vector{@NamedTuple{slot::RegRef, id::Int128, tag::Tag}}:
 (slot = Slot 2, id = 2, tag = SymbolIntInt(:symbol, 4, 5)::Tag)

julia> queryall(r, :symbol, ❓, >(5))
@NamedTuple{slot::RegRef, id::Int128, tag::Tag}[]
source