Extending Blocks
Blocks are defined as a sub-type system inside Julia, you could extend it by defining new Julia types by subtyping abstract types we provide. But we also provide some handy tools to help you create your own blocks.
Define Custom Constant Blocks
Constant blocks are used quite often and in numerical simulation we would expect it to be a real constant in the program, which means it won't allocate new memory when we try to get its matrix for several times, and it won't change with parameters.
In Yao, you can simply define a constant block with @const_gate
, with the corresponding matrix:
julia> @const_gate Rand = rand(ComplexF64, 4, 4)
This will automatically create a type RandGate{T}
and a constant binding Rand
to the instance of RandGate{ComplexF64}
, and it will also bind a Julia constant for the given matrix, so when you call mat(Rand)
, no allocation will happen.
julia> @allocated mat(Rand)
ERROR: UndefVarError: mat not defined
If you want to use other data type like ComplexF32
, you could directly call Rand(ComplexF32)
, which will create a new instance with data type ComplexF32
.
julia> Rand(ComplexF32)
ERROR: UndefVarError: Rand not defined
But remember this won't bind the matrix, it only binds the matrix you give
julia> @allocated mat(Rand(ComplexF32))
ERROR: UndefVarError: Rand not defined
so if you want to make the matrix call mat
for ComplexF32
to have zero allocation as well, you need to do it explicitly.
julia> @const_gate Rand::ComplexF32
ERROR: LoadError: UndefVarError: @const_gate not defined
in expression starting at none:1
Define Custom Blocks
Primitive blocks are the most basic block to build a quantum circuit, if a primitive block has a certain structure, like containing tweakable parameters, it cannot be defined as a constant, thus create a new type by subtyping PrimitiveBlock
is necessary
using YaoBlocks
mutable struct PhaseGate{T <: Real} <: PrimitiveBlock{1}
theta::T
end
If your insterested block is a composition of other blocks, you should define a CompositeBlock
, e.g
struct ChainBlock{N} <: CompositeBlock{N}
blocks::Vector{AbstractBlock{N}}
end
Besides types, there are several interfaces you could define for a block, but don't worry, they should just error if it doesn't work.
Define the matrix
The matrix form of a block is the minimal requirement to make a custom block functional, defining it is super simple, e.g for phase gate:
mat(::Type{T}, gate::PhaseGate) where T = exp(T(im * gate.theta)) * Matrix{Complex{T}}(I, 2, 2)
Or for composite blocks, you could just calculate the matrix by call mat
on its subblocks.
mat(::Type{T}, c::ChainBlock) where T = prod(x->mat(T, x), reverse(c.blocks))
The rest will just work, but might be slow since you didn't define any specification for this certain block.
Define how blocks are applied to registers
Although, having its matrix is already enough for applying a block to register, we could improve the performance or dispatch to other actions by overloading apply!
interface, e.g we can use specialized instruction to make X gate (a builtin gate defined @const_gate
) faster:
function apply!(r::ArrayReg, x::XGate)
nactive(r) == 1 || throw(QubitMismatchError("register size $(nactive(r)) mismatch with block size $N"))
instruct!(matvec(r.state), Val(:X), (1, ))
return r
end
In Yao, this interface allows us to provide more aggressive specialization on different patterns of quantum circuits to accelerate the simulation etc.
Define Parameters
If you want to use some member of the block to be parameters, you need to declare them explicitly
niparams(::Type{<:PhaseGate}) = 1
getiparams(x::PhaseGate) = x.theta
setiparams!(r::PhaseGate, param::Real) = (r.theta = param; r)
Define Adjoint
Since blocks are actually quantum operators, it makes sense to call their adjoint
as well. We provide Daggered
for general purpose, but some blocks may have more specific transformation rules for adjoints, e.g
Base.adjoint(x::PhaseGate) = PhaseGate(-x.theta)
Define Cache Keys
To enable cache, you should define cache_key
, e.g for phase gate, we only cares about its phase, instead of the whole instance
cache_key(gate::PhaseGate) = gate.theta