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.Tag
— TypeTags 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
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
QuantumSavory.tag!
— FunctionQuantumSavory.untag!
— Functionuntag!(
ref::Union{RegRef, Register},
id::Integer
) -> @NamedTuple{tag::Tag, slot::Int64, time::Float64}
Remove the tag with the given id from a RegRef
or a Register
.
To remove a tag based on a query, use querydelete!
instead.
See also: querydelete!
, query
, tag!
Querying for the presence of a tag
The query
function allows the user to query for Tag
s in three different cases:
- on a particular qubit slot (
RegRef
) in aRegister
node; - on a
Register
to query for any slot that contains the passedTag
; - on a
messagebuffer
to query for a particularTag
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.query
— Functionquery(
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 Int
↦Bool
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)
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.
Wildcards
QuantumSavory.W
— ConstantQuantumSavory.❓
— ConstantA 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
.
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!
— Functionquerydelete!(
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
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)
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.queryall
— Functionqueryall(
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}[]