Register Interface
A rather diverse set of simulation libraries is used under the hood. Long term the Julia Quantum Science community might be able to converge to a common interface that would slightly simplify work between the libraries, but in the interim the Julia multimethod paradigm is sufficient. Below we describe the interface that enables us to operate with many distinct underlying simulators.
initialize!
Initialize the state of a register to a known state.
QuantumSavory.initialize!
— FunctionSet the state of a given set of registers.
initialize!([regA,regB], [slot1,slot2], state)
would set the state of the given slots in the given registers to state
. state
can be any supported state representation, e.g., kets or density matrices from QuantumOptics.jl
or tableaux from QuantumClifford.jl
.
initialize!(refs::Vector{RegRef}, state; time)
Store a state
in the given register slots.
refs
can also be Tuple{Vararg{RegRef, N}}
or a single RegRef
.
initialize!(r::Vector{Register}, i::Vector{Int64}, state; time)
r
can also be a single Register
.
The accesstimes
attributes of the slots are reset to the given time
.
If state<:Symbolic
, then consistent_representation
is used to choose an appropriate representation based on the AbstractRepresentation
properties of the register slots. Then an express
call is made to transform the symbolic object into the appropriate representation.
initialize!(r::RegRef; time)
and initialize!(reg::Register, i::Int64; time)
When a state
is not provided, a default one is calculated from newstate
, depending on the register slot's QuantumStateTrait
(e.g. qubit vs qumode) and AbstractRepresentation
(e.g. ket vs tableaux).
Interface Overview
initialize!(refs::Vector{RegRef}, state; time)
"]
B["initialize!(r::Vector{Register}, i, state; time)
"]
subgraph TOP [lower from registers to states]
direction LR
end
D{{"state<:Symbolic
"}}
subgraph D1 [express state]
direction LR
d11["consistent_representation(r,i,state)
"]
d12["express(state,repr)
"]
d11 --> d12
end
D2([Store a state reference\nin the register slots])
A --> B --> TOP --> D
D --Yes--> D1
D --No--> D2
D1 --> D2
Ap["initialize!(::RegRef; time)
"]
Bp["initialize!(::Register, i; time)
"]
Cp["newstate(::QuantumStateTrait, ::AbstractRepresentation)
"]
subgraph TOPp [lower from registers to states]
direction LR
end
Ap --> Bp --> TOPp --> Cp ---> D2
apply!
Apply a quantum operation to a register.
QuantumInterface.apply!
— FunctionApply a given operation on the given set of register slots.
apply!([regA, regB], [slot1, slot2], Gates.CNOT)
would apply a CNOT gate on the content of the given registers at the given slots. The appropriate representation of the gate is used, depending on the formalism under which a quantum state is stored in the given registers. The Hilbert spaces of the registers are automatically joined if necessary.
apply!(refs::Vector{RegRef}, operation; time)
Applying an operation
to the qubits referred to by the sequence of RegRef
s at a specified time
.
refs
can also be Tuple{Vararg{RegRef, N}}
or a single RegRef
.
apply!(regs::Vector{Register}, indices, operation; time)
indices
refers to the slots inside of the given regs
.
Calls uptotime!
in order to update any AbstractBackground
properties.
Calls subsystemcompose
in order to make one big state. Then goes to apply!(state, subsystem_indices, operation; time)
.
apply!(state, subsystem_indices, operation; time)
subsystem_indices
refers to subsystems in state
.
If operation<:Symbolic
, then express(operation, repr, ::UseAsOperation)
is used to convert the symbolic operation
into something workable for the given state type. repr
is chosen by dispatch on state
.
Currently, the decision of how to convert a symbolic operation is based only on the state
on which the operation would act. It can not be modified by the AbstractRepresentation
properties of the Register
s containing the state.
Interface Overview
apply!(refs::Vector{RegRef}, operation; time)
"]
B["apply!(regs::Vector{Register}, indices, operation; time)
"]
subgraph TOP [lower from registers to states]
direction LR
B1["uptotime!
"]
B2["subsystemcompose
"]
B1 --> B2
end
C["apply!(state, subsystem_indices, operation; time)
"]
D{{"operation<:Symbolic
"}}
D1["express(operation, repr, ::UseAsOperation)
"]
D2([Dispatch on state to low level implementationin an independent library]) A --> B --> TOP --> C --> D D --Yes--> D1 D --No--> D2 D1 --> D2
As mentioned above, converting from symbolic to explicit representation for the operation
is dependent only on the type of state
, i.e. by the time the conversion is done, no knowledge of the register and its properties are kept (in particular its preferred representation is not considered).
You can add a custom dispatch that skips the express
functionality by defining a method apply!(state::YourStateType, indices, operation<:Symbolic{AbstractOperator})
. This would preemt the default apply!(state, indices, operation<:Symbolic{AbstractOperator})
containing the express
logic. The drawback is that this would also skip the memoization employed by express
.
observable
Measure a quantum observable. The dispatch down the call three is very similar to the one for apply!
.
QuantumSavory.observable
— FunctionCalculate the expectation value of a quantum observable on the given register and slot.
observable([regA, regB], [slot1, slot2], obs)
would calculate the expectation value of the obs
observable (using the appropriate formalism, depending on the state representation in the given registers).
observable(refs::Tuple{Vararg{RegRef, N}}, obs; something=nothing, time=nothing)
Calculate the value of an observable on the state in the sequence of RegRef
s at a specified time
. If these registers are not instantiated, return something
.
refs
can also be Tuple{Vararg{RegRef, N}}
or a single RegRef
.
observable(regs::Vector{Register}, indices, obs; something=nothing, time=nothing)
indices
refers to the slots inside of the given regs
.
Calls uptotime!
in order to update any AbstractBackground
properties.
Calls subsystemcompose
in order to make one big state. Then goes to observable(state, subsystem_indices, obs; time)
.
observable(state, subsystem_indices, obs; time)
subsystem_indicesrefers to subsystems in
state`.
If operation<:Symbolic
, then an express(obs, repr, ::UseAsObservable)
call is used to convert the symbolic obs
into something workable for the given state type. repr
is chosen by dispatch on state
.
Similar to the limitations faced by apply!
Interface Overview
observable(refs::Vector{RegRef}, obs; something=nothing, time)
"]
B["observable(regs::Vector{Register}, indices, obs; something=nothing, time)
"]
subgraph TOP [lower from registers to states]
direction LR
B1["uptotime!
"]
B2["subsystemcompose
"]
B1 --> B2
end
C["observable(state, subsystem_indices, obs; time)
"]
D{{"obs<:Symbolic
"}}
D1["express(obs, repr, ::UseAsObservable)
"]
D2([Dispatch on state to low level implementationin an independent library]) A --> B --> TOP --> C --> D D --Yes--> D1 D --No--> D2 D1 --> D2
Similarly to the case with apply!
, you can skips the express
functionality by defining a method observable(state::YourStateType, indices, obs<:Symbolic{AbstractOperator})
.
project_traceout!
QuantumSavory.project_traceout!
— FunctionPerform a projective measurement on the given slot of the given register.
project_traceout!(reg, slot, [stateA, stateB])
performs a projective measurement, projecting on either stateA
or stateB
, returning the index of the subspace on which the projection happened. It assumes the list of possible states forms a basis for the Hilbert space. The Hilbert space of the register is automatically shrunk.
A basis object can be specified on its own as well, e.g. project_traceout!(reg, slot, basis)
.
project_traceout!(r::RegRef, basis; time)
Project the state in RegRef
on basis
at a specified time
. basis
can be a Vector
or Tuple
of basis states, or it can be a Matrix
like Z
or X
.
project_traceout(reg::Register, i::Int, basis; time)
Project the state in the slot in index i
of Register
on basis
at a specified time
. basis
can be a Vector
or Tuple
of basis states, or it can be a Matrix
like Z
or X
.
project_traceout!(f, r::RegRef, basis; time)
Project the state in RegRef
on basis
at a specified time
and apply function f
on the projected basis state. basis
can be a Vector
or Tuple
of basis states, or it can be a Matrix
like Z
or X
.
project_traceout!(f, reg::Register, i::Int, basis; time)
Project the state in the slot in index i
of Register
on basis
at a specified time
and apply function f
on the projected basis state. basis
can be a Vector
or Tuple
of basis states, or it can be a Matrix
like Z
or X
. Lowers the representation from registers to states.
project_traceout!(state, stateindex, basis::Symbolic{AbstractOperator})
and basis::AbstractVecOrTuple{<:Symbolic{AbstractKet}}
Backend implementations. If basis
is an operator, call eigvecs
to convert it into a matrix whose columns are the eigenvectors of the operator. If basis
is a Vector
or Tuple
of Symbolic
basis states, call express
to convert it to the necessary representation.
Interface Overview
project_traceout!(r::RegRef, basis; time)
"]
B["project_traceout!(reg::Register, i::Int, basis; time)
"]
subgraph TOP [lower from registers to states]
direction LR
D1["reg.staterefs[i].state[]
"]
D2["reg.stateindices[i]
"]
end
E1["basis::Symbolic{AbstractOperator}
"]
F1["eigvecs(basis)
"]
E2["basis::Base.AbstractVecOrTuple{<:Symbolic{AbstractKet}}
"]
F2["express.(basis)
"]
G([Dispatch on state to low level implementationin an independent library]) A --> B --> TOP TOP --> E1 --> F1 --> G TOP --> E2 --> F2 --> G
traceout!
QuantumInterface.traceout!
— FunctionDelete the given slot of the given register.
traceout!(reg, slot)
would reset (perform a partial trace) over the given subsystem. The Hilbert space of the register gets automatically shrunk.
Perform a partial trace over a part of the system (i.e. discard a part of the system).
traceout!(r::RegRef)
Partial trace over a particular register reference.
traceout!(r::Register, i::Int)
Partial trace over slot i
of register r
. Calls down to the state reference stored in that particular register.
traceout!(s::StateRef, i::Int)
Partial trace over subsystem i
of state referenced by s
.
Interface Overview
traceout!(r::RegRef)
"]
B["traceout!(r::Register, i::Int)
"]
C["traceout!(r::StateRef, i::Int)
"]
D([Dispatch on state to low level implementationin an independent library]) A --> B --> C --> D
uptotime!
QuantumSavory.uptotime!
— FunctionEvolve all the states in a register to a given time, according to the various backgrounds that they might have.
julia> reg = Register(2, T1Decay(1.0))
Register with 2 slots: [ Qubit | Qubit ]
Slots:
nothing
nothing
julia> initialize!(reg[1], X₁)
observable(reg[1], σᶻ)
0.0 + 0.0im
julia> uptotime!(reg[1], 10)
observable(reg[1], Z)
0.9999546000702374 + 0.0im
uptotime!(ref::RegRef, now)
Evolve the state in a RegRef
upto a given time now
uptotime!(refs::Base.AbstractVecOrTuple{RegRef}, now)
Evolve the state represented by the given RegRef
s upto a time now
uptotime!(registers, indices::Base.AbstractVecOrTuple{Int}, now)
Evolve the state of all the given registers
at the slots represented by indices
upto a time now
uptotime!(stateref::StateRef, idx::Int, background, Δt)
Evolve a StateRef
at index idx
with given background
and Δt
uptotime!(state, indices::Base.AbstractVecOrTuple{Int}, backgrounds, Δt)
Evolve state
at indices
given backgrounds
and Δt
uptotime!(state::QuantumClifford.MixedDestabilizer, idx::Int, background, Δt)
Low level implementation to compute the result of uptotime!
for states using Clifford representation
uptotime!(state::StateVector, idx::Int, background, Δt)
Low level implementation to compute the result of uptotime!
for states using Ket representation. The state
in ket representation is converted to a density matrix before calling the uptotime!
for final computation.
uptotime!(state::Operator, idx::Int, background, Δt)
Low level implementation to compute the result of uptotime!
for Operator
Interface Overview
uptotime!(ref::RegRef, now)
"]
B["uptotime!(refs::Base.AbstractVecOrTuple{RegRef}, now)
"]
C["uptotime!(registers, indices::Base.AbstractVecOrTuple{Int}, now)
"]
C1["lower from registers to states"]
D["uptotime!(stateref::StateRef, idx::Int, background, Δt)
"]
E["uptotime!(state, indices::Base.AbstractVecOrTuple{Int}, backgrounds, Δt)
"]
A --> C
B --> C
C --> C1
C1 --> E
D --> E
F([Dispatch on state to low level implementationin an independent library]) E --> F
swap!
swap!(r1::RegRef, r2::RegRef)
Swap the state of the given RegRef
s
swap!(reg1::Register, reg2::Register, i1::Int, i2::Int)
Swap the state stored in the two Registers
at slots i1
and i2
respectively
Interface Overview
swap!(r1::RegRef, r2::RegRef)
"]
B["swap!(reg1::Register, reg2::Register, i1::Int, i2::Int)
"]
A --> B
overwritetime!
overwritetime!(refs::Base.AbstractVecOrTuple{RegRef}, now)
Overwrite the time of the simulation for the given references to now
overwritetime!(registers, indices, now)
Overwrite the time of the simulation for the given registers
at indices
to now
Interface Overview
overwritetime!(refs::Base.AbstractVecOrTuple{RegRef}, now)
"]
B["overwritetime!(registers, indices, now)
"]
A --> B