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!Function

Set 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.

source

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

flowchart TB A["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!Function

Apply 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.

source

apply!(refs::Vector{RegRef}, operation; time)

Applying an operation to the qubits referred to by the sequence of RegRefs 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.

Limitations of symbolic-to-explicit conversion

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 Registers containing the state.

Interface Overview

flowchart TB A["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 implementation
in an independent library]) A --> B --> TOP --> C --> D D --Yes--> D1 D --No--> D2 D1 --> D2
Limitations of symbolic-to-explicit conversion

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).

Short-circuiting the `express` dispatch

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.observableFunction

Calculate 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).

source

observable(refs::Tuple{Vararg{RegRef, N}}, obs; something=nothing, time=nothing)

Calculate the value of an observable on the state in the sequence of RegRefs 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 instate`.

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.

Limitations of symbolic-to-explicit conversion

Similar to the limitations faced by apply!

Interface Overview

flowchart TB A["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 implementation
in an independent library]) A --> B --> TOP --> C --> D D --Yes--> D1 D --No--> D2 D1 --> D2
Short-circuiting the `express` dispatch

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!Function

Perform 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).

source

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

flowchart TB A["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 implementation
in an independent library]) A --> B --> TOP TOP --> E1 --> F1 --> G TOP --> E2 --> F2 --> G

traceout!

QuantumInterface.traceout!Function

Delete 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.

source

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

flowchart TB A["traceout!(r::RegRef)"] B["traceout!(r::Register, i::Int)"] C["traceout!(r::StateRef, i::Int)"] D([Dispatch on state to low level implementation
in an independent library]) A --> B --> C --> D

uptotime!

QuantumSavory.uptotime!Function

Evolve 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
source

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 RegRefs 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

flowchart TB A["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 implementation
in an independent library]) E --> F

swap!

swap!(r1::RegRef, r2::RegRef)

Swap the state of the given RegRefs

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

flowchart TB A["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

flowchart TB A["overwritetime!(refs::Base.AbstractVecOrTuple{RegRef}, now)"] B["overwritetime!(registers, indices, now)"] A --> B