## Figures

## Abstract

Numerical investigations are an important research tool in quantum information theory. There already exists a wide range of computational tools for quantum information theory implemented in various programming languages. However, there is little effort in implementing this kind of tools in the Julia language. Julia is a modern programming language designed for numerical computation with excellent support for vector and matrix algebra, extended type system that allows for implementation of elegant application interfaces and support for parallel and distributed computing. QuantumInformation.jl is a new quantum information theory library implemented in Julia that provides functions for creating and analyzing quantum states, and for creating quantum operations in various representations. An additional feature of the library is a collection of functions for sampling random quantum states and operations such as unitary operations and generic quantum channels.

**Citation: **Gawron P, Kurzyk D, Pawela Ł (2018) QuantumInformation.jl—A Julia package for numerical computation in quantum information theory. PLoS ONE 13(12):
e0209358.
https://doi.org/10.1371/journal.pone.0209358

**Editor: **Nicholas Chancellor,
Durham University, UNITED KINGDOM

**Received: **June 29, 2018; **Accepted: **December 4, 2018; **Published: ** December 26, 2018

**Copyright: ** © 2018 Gawron et al. This is an open access article distributed under the terms of the Creative Commons Attribution License, which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.

**Data Availability: **The QuantumInformation.jl package code is available on GitHub at the URL https://github.com/ZKSI/QuantumInformation.jl.

**Funding: **PG was supported by Polish National Science Centre grant number 2014/15/B/ST6/05204. DK was supported by Polish National Science Centre grant number 2016/22/E/ST6/00062. ŁP was supported by Polish National Science Centre grant number 2015/17/B/ST6/01872. The website of the Polish National Science Centre is www.ncn.gov.pl.

**Competing interests: ** The authors have declared that no competing interests exist.

## Introduction

Numerical investigations are prevalent in quantum information theory. Numerical experiments can be used to find counter examples for theorems, to test hypotheses or to gain insight about quantum objects and operations.

The variety of software that supports investigations in quantum information theory is very large. Yet there are niches that are not well covered. The purpose of QuantumInformation.jl library is to provide functions to create quantum states, manipulate them with quantum channels, calculate functionals on these objects and sample them randomly from various distributions. QuantumInformation.jl package is available on-line at https://github.com/ZKSI/QuantumInformation.jl and stored at Zenodo repository [1]. It is published under GNU General Public License v3.0.

### Related work

A comprehensive collection of software related to quantum mechanics, computation and information can be found at Quantiki [2]—an on-line resource for quantum information research community. There exist several notable libraries aimed at numerical and symbolic computation for quantum information theory. Two Mathemetica libraries—QI [3] and TRQS [4]—were an inspiration for creation of QuantumInformation.jl. Additionally the QUANTUM [5] library was implemented in Mathematica. A library called FEYNMAN implemented in Maple, described in a series of papers [6–10], provides a wide variety of functions. The above-mentioned libraries rely on non-free software and therefore their use can be very limited as use of this software requires acquiring expensive licenses and its source code cannot be studied by researchers. Therefore any results obtained using this software rely on trust to the companies that produced it. Hence non-free software creates barriers for reproducibility of scientific results [11].

A widely celebrated and used framework QuTiP [12, 13] was written in Python. Python posses many scientific computation libraries. It is free software and is widely used for scientific computation. Nevertheless, as a general purpose programming language it has its limits. In Python, implementations of multidimensional arrays and linear algebra routines are provided by NumPy [14] and SciPy [15] respectively. Unfortunately, due to low efficiency of Python, many of the underling functions are implemented in C or Fortran programming languages. Therefore, study and development of these routines is difficult and requires familiarity with these low-level languages.

Julia [16], being a high-level just-in-time compiled language, is very efficient and therefore extremely useful for scientific computing. There are several libraries related to quantum mechanics and quantum information written in Julia. Those are: QuantumInfo.jl [17], Quantum.jl [18] and a collection of packages developed as a part of JuliaQuantum project [19]. Unfortunately these development efforts stalled a couple of years ago. JuliaQuantum project is very ambitious, but its scope seems to be too large to be implemented fully in a relatively short amount of time. The only package whose development was successful is QuantumOptics.jl—a Julia framework for simulating open quantum systems [20]. Yet the applicability scope of this package is different than the one of QuantumInformation.jl.

### Design principles

Our goal while designing QuantumInformation.jl library was to follow the principles presented in the book “Geometry of Quantum States” [21]. We work with column vectors representing kets and row vectors representing bras. We fix our basis to the computational one. Density matrices and quantum channels are represented as two-dimensional arrays in the same fixed basis. This approach allows us to obtain a low level of complexity of our code, high flexibility and excellent computational efficiency. The design choices were highly motivated by the properties of the language in which our library was implemented, namely Julia [22].

Julia is a novel scientific programming language mainly influenced by Python, Matlab and Lisp programming languages. One of the main concepts widely used in Julia is multiple dispatch *i.e.* an ability to dispatch function calls to different methods depending on the types of all function arguments. The multiple dispatch mechanism together with a simple yet flexible type system allows to build clean and easy to use programming interfaces. Julia is just-in-time compiled to machine code using LLVM [23] therefore, despite being a high-level programming language, it can reach computation efficiency similar to C or Fortran. Julia natively supports parallel and distributed computing techniques. Therefore it is easy to write programs for Monte-Carlo sampling in Julia.

In Julia arrays are first class objects [24], and linear algebra operations are integrated into the language standard library. The array system in Julia is designed in a way that minimizes the amount of memory copying operations during transformations of arrays. Julia supports various representations of vectors and matrices. For these reasons a design decision was made not to create library specific types but to rely on built-in standard library abstract array types.

The QuantumInformation.jl library was initially developed in Julia 0.6 but then subsequently it was ported to Julia version 1.0. Part of the functionality of the library, namely the function that calculates the diamond norm of a quantum channel relies on Convex.jl library [25]. Partial traces are implemented using TensorOperations.jl library [26] that provides basic tensor contractions primitives.

### Testing

The QuantumInformation.jl library was tested using standard Julia framework. Tests where performed using three distinct approaches. In case of most of the functions the basic properties, such as e.g. dimensions, norms, hermititicty, positivity, trace are tested, where it was appropriate. Additionally some test cases where manually computed and used to verify the obtained results. In the case of methods generating random objects such as random matrices statistical properties of results are tested. For example in case of random unitary matrices sampling we test phases distribution [27] of obtained matrices in order to ensure that the unitary matrices are drawn according to the Haar measure.

### Organization of the paper

In the section **Linear algebra in Julia**, we describe briefly how the linear algebra routines are implemented in Julia. Next, in the section **States and channels**, we introduce the notions of *quantum states* and *quantum channels* and we discuss how we implement these concepts in Julia. Subsequently, the section **Functionals** focuses on functionals related with quantum information processing, *i.e.* *trace norm, diamond norm, entropy, fidelity* or the *PPT criterion*. Afterward, we show the usage of QuantumInformation.jl for modeling and application of the *quantum measurements*. The section **Random quantum objects** introduces probabilistic measures on quatum states and channels and their implementation in Julia. Additionally, we introduce some common random matrix ensembles. In section **Benchmarks** we provide a comparison, in terms of code clarity and execution speed, of our library with the latest version of QuTiP [12, 13]. Finally, in the section **Conclusions and future work** we present the final remarks and outline possible future work.

## Linear algebra in Julia

A basic construction of vector in Julia creates a full one-index array containing elements of a number type as presented below.

A transposition of a column vector returns an object of type LinearAlgebra.Transpose as shown below

julia> xt = transpose(x)

1×2 LinearAlgebra.Transpose{Complex{Float64},Array{Complex{Float64},1}}:

0.0 + 0.0im 0.0 + 1.0im

While a Hermitian conjugate of the same vector returns a LinearAlgebra.Adjoint parametrized by the type Array:

julia> xc = [0.0, 1.0im]’

1×2 LinearAlgebra.Adjoint{Complex{Float64},Array{Complex{Float64},1}}:

0.0-0.0im 0.0-1.0im

Values of variables xt and xc are views of the value of variable x. The column and row vectors behave like bras and kets, for example xc*x denotes the inner product of ‘bra’ xc and ‘ket’ x, while x*xc denotes their outer product resulting in a two-index array.

The linear algebra library in Julia provides standard operations on matrices and vectors that are designed to take into account the types of the objects.

## States and channels

In this and the following sections we will denote complex Euclidean spaces ℂ^{d} with , , etc. When needed the dimension of a space will be denoted . The set of matrices transforming vectors from to will be denoted . For simplicity we will write .

### States

By we denote a normed column vector. Notice that any |*ψ*〉 can be expressed as , where and the set is the computational basis.

julia> ket(1,2)

2-element Array{Complex{Float64},1}:

1.0 + 0.0im

0.0 + 0.0im

julia> (1/sqrt(2)) * (ket(1,2) + ket(2,2))

2-element Array{Complex{Float64},1}:

0.7071067811865475 + 0.0im

0.7071067811865475 + 0.0im

According to common academic convention, we count the indices of states starting from one. Following the standard Dirac notation the symbol 〈*ψ*| denotes the row vector dual to |*ψ*〉. Therefore |*ψ*〉 = 〈*ψ*|^{†}, where the symbol ^{†} denotes the Hermitian conjugation.

julia> bra(2,3)

1×3 LinearAlgebra.Adjoint{Complex{Float64},Array{Complex{Float64},1}}:

0.0-0.0im 1.0-0.0im 0.0-0.0im

The inner product of is denoted by 〈*ψ*|*ϕ*〉 and the norm is defined as .

julia> *ψ* = (1/sqrt(2)) * (ket(1,2) + ket(2,2))

2-element Array{Complex{Float64},1}:

0.7071067811865475 + 0.0im

0.7071067811865475 + 0.0im

julia> *ϕ* = (1/2) * ket(1,2) + (sqrt(3)/2) * ket(2,2)

2-element Array{Complex{Float64},1}:

0.5 + 0.0im

0.8660254037844386 + 0.0im

julia> *ϕ*’ * *ψ*

0.9659258262890682 + 0.0im

julia> sqrt(*ϕ*’ * *ϕ*)

0.9999999999999999 + 0.0im

The form denotes outer product of and .

julia> ketbra(2,3,4)

4×4 Array{Complex{Float64},2}:

0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 1.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im 0.0+0.0im

Specifically, |*ψ*〉〈*ψ*| is a rank-one projection operator called a *pure state*. Generally, any *quantum state* *ρ* can be expressed as , where and |*ψ*_{i}〉〈*ψ*_{i}| are rank-one projectors. Notice that *ρ* is a trace-one positive semi-definite linear operator *i.e.*: *ρ* = *ρ*^{†}, *ρ* ≥ 0 and tr*ρ* = 1.

For convenience, the QuantumInformation.jl library provides the implementations of maximally mixed, maximally entangled and Werner states.

julia> max_entangled(4)

4-element reshape(::Diagonal{Complex{Float64},Array{Complex{Float64},1}},4)

with eltype {Complex{Float64}:

0.7071067811865475 + 0.0im

0.0 + 0.0im

0.0 + 0.0im

0.7071067811865475 + 0.0im

julia> max_mixed(4)

4×4 Array{Float64,2}:

0.25 0.0 0.0 0.0

0.0 0.25 0.0 0.0

0.0 0.0 0.25 0.0

0.0 0.0 0.0 0.25

julia> werner_state(4, 0.4)

4×4 Array{Complex{Float64},2}:

0.35+0.0im 0.0+0.0im 0.0+0.0im 0.2+0.0im

0.0+0.0im 0.15+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.15+0.0im 0.0+0.0im

0.2+0.0im 0.0+0.0im 0.0+0.0im 0.35+0.0im

### Non-standard matrix transformations

We will now introduce reshaping operators, which map matrices to vectors and vice versa. We start with the mapping , which transforms the matrix *ρ* into a vector row by row. More precisely, for dyadic operators |*ψ*〉〈*ϕ*|, where , the operation *res* is defined as and can be uniquely extend to the whole space by linearity.

julia> res(ketbra(1,2,2))

4-element reshape(::LinearAlgebra.Transpose{Complex{Float64},Array{Complex{Float64},2}}, 4)

with eltype {Complex{Float64}:

0.0 + 0.0im

1.0 + 0.0im

0.0 + 0.0im

0.0 + 0.0im

The inverse operation to res is , which transforms the vector into a matrix. It is defined as the unique linear mapping satisfying *ρ* = unres(res(*ρ*)).

julia> unres(res(ketbra(1,2,2)))

2×2 LinearAlgebra.Transpose{Complex{Float64},Base.ReshapedArray{Complex{Float64},2,LinearAlgebra.

Transpose{Complex{Float64},Array{Complex{Float64},2}},

Tuple{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64}}}}:

0.0+0.0im 1.0+0.0im

0.0+0.0im 0.0+0.0im

Let us recall that trace is a mapping given by , where {|*e*_{i}〉} is an orthonormal basis of . According to this, *partial trace* is a mapping such that , where , . As this is a linear map, it may be uniquely extended to the case of operators which are not in a tensor product form.

julia> *ρ* = [0.25 0.25im; -0.25im 0.75]

2×2 Array{Complex{Float64},2}:

0.25+0.0im 0.0+0.25im

-0.0-0.25im 0.75+0.0im

julia> *σ* = [0.4 0.1im; -0.1im 0.6]

2×2 Array{Complex{Float64},2:

0.4+0.0im 0.0+0.1im

-0.0-0.1im 0.6+0.0im

julia> ptrace(*ρ* ⊗ *σ*, [2, 2], [2])

2×2 Array{Complex{Float64},2}:

0.25+0.0im 0.0+0.25im

0.0-0.25im 0.75+0.0im

Matrix transposition is a mapping such that (*ρ*^{T})_{ij} = *ρ*_{ji}, where *ρ*_{ij} is a *i*-th row, *j*-th column element of matrix *ρ*. Following this, we may introduce *partial transposition* , which for a product state *ρ*_{A} ⊗ *ρ*_{B} is given by . The definition of partial transposition can be uniquely extended for all operators from linearity.

julia> ptranspose(*ρ* ⊗ *σ*, [2, 2], [1])

4×4 Array{Complex{Float64},2}:

0.1+0.0im 0.0+0.025im 0.0-0.1im 0.025-0.0im

0.0-0.025im 0.15+0.0im -0.025+0.0im 0.0-0.15im

0.0+0.1im -0.025+0.0im 0.3+0.0im 0.0+0.075im

0.025-0.0im 0.0+0.15im 0.0-0.075im 0.45+0.0im

For given multiindexed matrix *ρ*_{(m,μ),(n,ν)} = 〈*mμ*|*ρ*|*nν*〉, the reshuffle operation is defined as .

### Channels

Physical transformations of quantum states into quantum states are called quantum channels *i.e.* linear Completely Positive Trace Preserving (CP-TP) transformations. Probabilistic transformations of quantum states are called quantum operations and mathematically they are defined as linear Completely Positive Trace Non-increasing (CP-TNI) maps. For the sake of simplicity we will refer to both CP-TP and CP-TNI maps as quantum channels when it will not cause confusion.

There exists various representations of quantum channels such as:

- Kraus operators,
- natural representation, also called superoperator representation,
- Stinespring representation,
- Choi-Jamiołkowski matrices, sometimes called dynamical matrices.

Formally, properties of quantum channels can be stated as follows [28]. First, we introduce the notion of *superoperator* as a linear mapping acting on linear operators and transforming them into operators acting on . The set of all such mapping will be denoted by and . In mathematical terms, a quantum channel is a superoperator that is

*trace-preserving*() and*completely positive*().

The product of superoperators , is a mapping that satisfies (Φ_{1} ⊗ Φ_{2})(*ρ*_{1} ⊗ *ρ*_{2}) = Φ_{1}(*ρ*_{1}) ⊗ Φ_{2}(*ρ*_{2}). For the operators that are not in a tensor product form this notion can be uniquely extended from linearity.

According to Kraus’ theorem, any completely positive trace-preserving (CP-TP) map Φ can always be written as for some set of operators satisfying , where *r* is the rank of superoperator Φ.

Another way to represent the quantum channel is based on Choi-Jamiołkowski isomorphism. Consider mapping such that . Equivalently . The action of a superoperator in the Choi representation is given by .

The natural representation of a quantum channel is a mapping res(*ρ*) ↦ res(Φ(*ρ*)). It is represented by a matrix for which the following holds
(1)
for all .

Let and be a complex Euclidean spaces. The action of the Stinespring representation of a quantum channel on a state is given by (2) where .

We now briefly describe the relationships among channel representations [28]. Let be a quantum channel which can be written in the Kraus representation as (3) where are Kraus operators satisfying . According to this assumption, Φ can be represented in

- Choi representation as (4)
- natural representation as (5)
- Stinespring representation as (6) where and .

In QuantumInformation.jl states and channels are always represented in the computational basis therefore channels are stored in the memory as either vectors of matrices in case of Kraus operators or matrices in other cases. In QuantumInformation.jl quantum channels are represented by a set of types deriving from an abstract type AbstractQuantumOperation{T} where type parameter T should inherit from AbstractMatrix{<:Number}. Every type inheriting from AbstractQuantumOperation{T} should contain fields idim and odim representing the dimension of input and output space of the quantum channel.

Two special types of channels are implemented: UnitaryChannel and IdentityChannel that can transform ket vectors into ket vectors.

#### Constructors.

Channel objects can be constructed from matrices that represent them, as shown in the following listing

julia> *γ* = 0.4

0.4

julia> K0 = Matrix([1 0; 0 sqrt(1-*γ*)])

2×2 Array{Float64,2}:

1.0 0.0

0.0 0.774597

julia> K1 = Matrix([0 sqrt(*γ*); 0 0])

2×2 Array{Float64,2}:

0.0 0.632456

0.0 0.0

julia> Φ = KrausOperators([K0,K1])

KrausOperators{Array{Float64,2}}

dimensions: (2, 2)

[1.0 0.0; 0.0 0.774597]

[0.0 0.632456; 0.0 0.0]

julia> iscptp(Φ)

true

There are no checks whether a matrix represents a valid CP-TP or CP-TNI map, because this kind of verification is costly and requires potentially expensive numerical computation. Function such as iscptp(), and iscptni() are provided to test properties of supposed quantum channel or quantum operation.

#### Conversion.

Conversions between all quantum channel types, *i.e.* these that derive from AbstractQuantumOperation{T} are implemented. The users are not limited by any single channel representation and can transform between representations they find the most efficient or suitable for their purpose.

julia> Ψ1 = convert(SuperOperator{Matrix{ComplexF64}}, Φ)

SuperOperator{Array{Complex{Float64},2}}

dimensions: (2, 2)

Complex{Float64}

[1.0+0.0im 0.0+0.0im 0.0+0.0im 0.4+0.0im;

0.0+0.0im 0.774597+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.774597+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.0+0.0im 0.6+0.0im]

julia> Ψ2 = convert(DynamicalMatrix{Matrix{Float64}}, Φ)

DynamicalMatrix{Array{Float64,2}}

dimensions: (2, 2)

[1.0 0.0 0.0 0.774597;

0.0 0.4 0.0 0.0;

0.0 0.0 0.0 0.0;

0.774597 0.00.0 0.6]

julia> Ψ3 = convert(Stinespring{Matrix{Float64}}, Φ)

Stinespring{Array{Float64,2}}

dimensions: (2, 2)

[0.0 0.0;

-1.82501e-8 0.0;

…;

0.0 0.0;

0.0 -0.774597]

#### Application.

Channels can act on pure and mixed states represented by vectors and matrices respectively. Channels are callable and therefore mimic application of a function on a quantum state.

julia> *ρ*1 = *ψ* * *ψ*’

2×2 Array{Complex{Float64},2}:

0.5+0.0im 0.5+0.0im

0.5+0.0im 0.5+0.0im

julia> Φ(*ρ*1)

2×2 Array{Complex{Float64},2}:

0.7+0.0im 0.387298+0.0im

0.387298+0.0im 0.3+0.0im

julia> Ψ1(*ρ*1)

2×2 Array{Complex{Float64},2}:

0.7+0.0im 0.387298+0.0im

0.387298+0.0im 0.3+0.0im

julia> Φ(*ψ*)

2×2 Array{Complex{Float64},2}:

0.7+0.0im 0.387298+0.0im

0.387298+0.0im 0.3+0.0im

#### Composition.

Channels can be composed in parallel or in sequence. Composition in parallel is done using kron() function or the overloaded ⊗ operator. Composition in sequence can be done in two ways either by using Julia built-in function composition operator (*f* ∘ *g*)(⋅) = *f*(*g*)(⋅) or by using multiplication of objects inheriting from AbstractQuantumOperation{T} abstract type.

julia> *ρ*2 = *ϕ* * *ϕ*’

2×2 Array{Complex{Float64},2}:

0.25+0.0im 0.433013+0.0im

0.433013+0.0im 0.75+0.0im

julia> (Φ ⊗ Φ)(*ρ*1 ⊗ *ρ*2)

4×4 Array{Complex{Float64},2}:

0.385+0.0im 0.234787+0.0im 0.213014+0.0im 0.129904+0.0im

0.234787+0.0im 0.315+0.0im 0.129904+0.0im 0.174284+0.0im

0.213014+0.0im 0.129904+0.0im 0.165+0.0im 0.100623+0.0im

0.129904+0.0im 0.174284+0.0im 0.100623+0.0im 0.135+0.0im

julia> (Ψ1 ∘ Ψ2)(*ρ*1)

2×2 Transpose{Complex{Float64},Array{Complex{Float64},2}:

0.82+0.0im 0.3+0.0im

0.3+0.0im 0.18+0.0im

## Functionals

### Trace norm and distance

Let . The *trace norm* is defined as and the trace distance is defined as .

julia> *ψ* = (1/sqrt(2)) * (ket(1,2) + ket(2,2))

2-element Array{Complex{Float64},1}:

0.7071067811865475 + 0.0im

0.7071067811865475 + 0.0im

*ϕ* = (1/2) * ket(1,2) + (sqrt(3)/2) * ket(2,2)

2-element Array{Complex{Float64},1}:

0.5 + 0.0im

0.8660254037844386 + 0.0im

julia> *ρ* = proj(*ψ*)

2×2 Array{Complex{Float64},2}:

0.5+0.0im 0.5+0.0im

0.5+0.0im 0.5+0.0im

julia> *σ* = proj(*ϕ*)

2×2 Array{Complex{Float64},2}:

0.25+0.0im 0.433013+0.0im

0.433013+0.0im 0.75+0.0im

julia> norm_trace(*ρ*)

1.0

julia> trace_distance(*ρ*, *σ*)

0.2588190451025207 + 0.0im

### Hilbert–Schmidt norm and distance

The *Hilbert–Schmidt* norm and distance defined by and , respectively, can be used as follows

### Fidelity and superfidelity

*Fidelity* is a measure of distance of quantum states. It is an example of a distance measure which is not a metric on the space of quantum states. The fidelity of two quantum states is given by

julia> fidelity_sqrt(*ρ*, *σ*)

0.9659258262890682

julia> fidelity(*ρ*, *σ*)

0.9330127018922192

julia> fidelity(*ψ*, *σ*)

0.9330127018922191

julia> fidelity(*ρ*, *ϕ*)

0.9330127018922191

julia> fidelity(*ψ*, *ϕ*)

0.9330127018922192

*Superfidelity* is an upper bound on the fidelity of two quantum states It is defined by .

### Diamond norm

In order to introduce the *diamond norm*, we first introduce the notion of the induced trace norm. Given we define its induced trace norm as . The diamond norm of Φ is defined as . One important property of the diamond norm is that for Hermiticity-preserving we obtain .

julia> K0 = Matrix([1 0; 0 sqrt(1-*γ*)])

2×2 Array{Float64,2}:

1.0 0.0

0.0 0.774597

julia> K1 = Matrix([0 sqrt(*γ*); 0 0])

2×2 Array{Float64,2}:

0.0 0.632456

0.0 0.0

julia> Φ = KrausOperators([K0,K1])

KrausOperators{Array{Float64,2}}

dimensions: (2, 2)

[1.0 0.0; 0.0 0.774597]

[0.0 0.632456; 0.0 0.0]

julia> L0 = Matrix([1 0; 0 sqrt(1-*γ*)])

2×2 Array{Float64,2}:

1.0 0.0

0.0 0.774597

julia> L1 = Matrix([0 0; 0 sqrt(*γ*)])

2×2 Array{Float64,2}:

0.0 0.0

0.0 0.632456

julia> Ψ = KrausOperators([K0,K1])

KrausOperators{Array{Float64,2}}

dimensions: (2, 2)

[1.0 0.0; 0.0 0.774597]

[0.0 0.632456; 0.0 0.0]

julia> norm_diamond(Φ)

1.0000000077706912

julia> diamond_distance(Φ, Ψ)

-5.258429449675825e-7

Diamond norm and diamond distance are implemented using the Convex.jl Julia package [25].

### Shannon entropy and von Neumann entropy

*Shannon entropy* is defined for a probability vector *p* as . We also provide an implementation for the point Shannon entropy. It is defined as *h*(*a*) = −*a* log *a* − (1 − *a*) log(1 − *a*).

julia> p = [0.3, 0.2, 0.5]

3-element Array{Float64,1}:

0.3

0.2

0.5

julia> shannon_entropy(p)

1.0296530140645737

julia> shannon_entropy(0.5)

0.6931471805599453

For a quantum system described by a state *ρ*, the *von Neumann entropy* is *S*(*ρ*) = −tr*ρ* log *ρ*. Let λ_{i}, 0 ≤ *i* < *n* be the eigenvalues of *ρ*, then *S*(*ρ*) can be written as .

### Distinguishability between two quantum states

One of the measure of distinguishability between two quantum states is the *quantum relative entropy*, called also Kullback–Leibler divergence, defined as *S*(*ρ*‖*σ*) = −Tr*ρ* log *σ* + Tr*ρ* log *ρ*

Another type of measure of distinguishability between two quantum state is *quantum Jensen–Shannon divergence* given by .

*The Bures distance* defines an infinitesimal distance between quantum states, and it is defined as . The value related with Bures distance is the Bures angle

### Quantum entanglement

One of the entanglement measures is *negativity* defined as .

julia> negativity(*ρ* ⊗ *σ*, [2, 2], 2)

-0.0

julia> negativity(proj((1/sqrt(2)*(ket(1,2) ⊗ ket(1,2)-ket(2,2) ⊗ ket(2,2)))), [2, 2], 2)

0.4999999999999999

julia> log negativity(*ρ* ⊗ *σ*, [2, 2], 2)

-1.1102230246251565e-16

*Positive partial transpose* (the Peres–Horodecki criterion) is a necessary condition of separability of the joint state *ρ*_{AB}. According PPT criterion, if has non negative eigenvalues, then *ρ*_{AB} is separable.

julia> ppt(*ρ* ⊗ *σ*, [2, 2], 2)

0.052512626584708365

julia> ppt(proj((1/sqrt(2)*(ket(1,2) ⊗ ket(1,2)-ket(2,2) ⊗ ket(2,2)))), [2, 2], 2)

-0.4999999999999999

Another way to quantification of quantum entanglement is *Concurrence* [29]. Concurrence of quantum state *ρ* is a strong separability criterion. For two-qubit systems it is defined as *C*(*ρ*) = max(0, λ_{1} − λ_{2} − λ_{3} − λ_{4}), where λ_{i} are decreasing eigenvalues of with . If *C*(*ρ*) = 0, then *ρ* is separable.

julia> *ρ* = [0.25 0.1im; -0.1im 0.75]

2×2 Array{Complex{Float64},2}:

0.25+0.0im 0.0+0.1im

-0.0-0.1im 0.75+0.0im

julia> *σ* = [0.4 0.1im; -0.1im 0.6]

2×2 Array{Complex{Float64},2}:

0.4+0.0im 0.0+0.1im

-0.0-0.1im 0.6+0.0im

julia> concurrence(*ρ* ⊗ *σ*)

0.0

julia> concurrence(proj(max_entangled(4)))

0.9999999999999998

## Measurements

Measurements are modeled in two ways:

- as Positive Operator Valued Measures (POVMs),
- measurements with post-selection.

In both cases a measurement is treated as a special case of a quantum channel (operation).

### Positive Operator Valued Measure measurement

A POVM measurement is defined as follows. Let be a mapping from a finite alphabet of measurement outcomes to the set of linear positive operators. If then *μ* is a POVM measurement. The set of positive semi-definite linear operators is defined as . POVM measurement models the situation where a quantum object is destroyed during the measurement process and quantum state after the measurement does not exists.

We model POVM measurement as a channel , where such that *θ*(*ρ*) = ∑_{ξ∈Γ} tr(*ρ μ*(*ξ*))|*ξ*〉〈*ξ*|. This channel transforms the measured quantum state into a classical state (diagonal matrix) containing probabilities of measuring given outcomes. Note that in QuantumInformation.jl Γ = {1, 2, …, |Γ|} and POVM measurements are represented by the type

Predicate function ispovm() verifies whether a list of matrices is a proper POVM.

julia> *ρ* = proj(1.0/sqrt(2)*(ket(1,3)+ket(3,3)))

3×3 Array{Complex{Float64},2}:

0.5+0.0im 0.0+0.0im 0.5+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.5+0.0im 0.0+0.0im 0.5+0.0im

julia> E0 = proj(ket(1,3))

3×3 Array{Complex{Float64},2}:

1.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

julia> E1 = proj(ket(2,3))+proj(ket(3,3))

3×3 Array{Complex{Float64},2}:

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 1.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 1.0+0.0im

julia> M = POVMMeasurement([E0,E1])

POVMMeasurement{Array{Complex{Float64},2}}

dimensions: (3, 2)

Complex{Float64}

[1.0+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.0+0.0im]

Complex{Float64}

[0.0+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 1.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 1.0+0.0im]

julia> ispovm(M)

true

julia> M(*ρ*)

2×2 LinearAlgebra.Diagonal{Float64,Array{Float64,1}}:

0.5 .

. 0.5

### Measurement with post-selection

When a quantum system after being measured is not destroyed one can be interested in its state after the measurement. This state depends on the measurement outcome. In this case the measurement process is defined in the following way.

Let be a mapping from a finite set of measurement outcomes to set of linear operators called effects. If then *μ* is a quantum measurement. Given outcome *ξ* was obtained, the state before the measurement, *ρ*, is transformed into sub-normalized quantum state *ρ*_{ξ} = *μ*(*ξ*)*ρμ*(*ξ*)^{†}. The outcome *ξ* will be obtained with probability *tr*(*ρ*_{ξ}).

julia> PM = PostSelectionMeasurement(E1)

PostSelectionMeasurement{Array{Complex{Float64},2}}

dimensions: (3, 3)

Complex{Float64}

[0.0+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 1.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 1.0+0.0im]

julia> iseffect(PM)

true

julia> PM(*ρ*)

3×3 Array{Complex{Float64},2}:

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.5+0.0im

In QuantumInformation.jl this kind of measurement is modeled as CP-TNI map with a single Kraus operator *μ*(*ξ*) and represented as

Measurement types can be composed and converted to Kraus operators, superoperators, Stinespring representation operators, and dynamical matrices.

julia> *α* = 0.3

0.3

julia> K0 = ComplexF64[0 0 sqrt(*α*); 0 1 0; 0 0 0]

3×3 Array{Complex{Float64},2}:

0.0+0.0im 0.0+0.0im 0.547723+0.0im

0.0+0.0im 1.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

julia> K1 = ComplexF64[1 0 0; 0 0 0; 0 0 sqrt(1 − α)]

3×3 Array{Complex{Float64},2}:

1.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.83666+0.0im

julia> Φ = KrausOperators([K0,K1])

KrausOperators{Array{Complex{Float64},2}}

dimensions: (3, 3)

Complex{Float64}

[0.0+0.0im 0.0+0.0im 0.547723+0.0im;

0.0+0.0im 1.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.0+0.0im]

Complex{Float64}

[1.0+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.0+0.0im;

0.0+0.0im 0.0+0.0im 0.83666+0.0im]

julia> *ρ* = proj(1.0/sqrt(2)*(ket(1,3)+ket(3,3)))

3×3 Array{Complex{Float64},2}:

0.5+0.0im 0.0+0.0im 0.5+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.5+0.0im 0.0+0.0im 0.5+0.0im

julia> (PM ∘ Φ)(*ρ*)

3×3 Array{Complex{Float64},2}:

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.0+0.0im

0.0+0.0im 0.0+0.0im 0.35+0.0im

## Random quantum objects

In this section we present the implementation of the sub-package RandomMatrices. The justification for including these functionalities in our package is twofold. First, the application of random matrix theory (RMT) in quantum information is a blooming field of research with a plethora of interesting results [30–39]. Hence, it is useful to have readily available implementations of known algorithms of generating random matrices. Secondly, when performing numerical investigations, we often need “generic” inputs. Generating random matrices with a known distribution is one of the ways to obtain such generic inputs.

### Ginibre matrices

In this section we introduce the Ginibre random matrices ensemble [40]. This ensemble is at the core of a vast majority of algorithms for generating random matrices presented in later subsections. Let (*G*_{ij})_{1≤i≤m,1≤j≤n} be a *m* × *n* table of independent identically distributed (i.i.d.) random variable on . The field can be either of ℝ, ℂ or ℚ. With each of the fields we associate a Dyson index *β* equal to 1, 2, or 4 respectively. Let *G*_{ij} be i.i.d random variables with the real and imaginary parts sampled independently from the distribution . Hence, ,where matrix *G* is
(7)
This law is unitarily invariant, meaning that for any unitary matrices *U* and *V*, *G* and *UGV* are equally distributed. It can be shown that for *β* = 2 the eigenvalues of *G* are uniformly distributed over the unit disk on the complex plane [41].

In our library the ensemble Ginibre matrices is implemented in the GinibreEnsemble{*β*} parametric type. The parameter determines the Dyson index. The following constructors are provided

julia> GinibreEnsemble{*β*}(m::Int, n::Int)

julia> GinibreEnsemble{*β*}(m::Int)

julia> GinibreEnsemble(m::Int, n::Int)

julia> GinibreEnsemble(m::Int)

The parameters *n* and *m* determine the dimensions of output and input spaces. The versions with one argument assume *m* = *n*. When the Dyson index is omitted it assumed that *β* = 2. Sampling from these distributions can be performed as follows

julia> g = GinibreEnsemble{2}(2,3)

GinibreEnsemble{2}(m = 2, n = 3)

julia> rand(g)

2×3 Array{Complex{Float64},2}:

0.835803+1.10758im -0.622744-0.130165im -0.677944+0.636562im

1.32826+0.106582im -0.460737-0.531975im -0.656758+0.0244259im

The function rand has specialized methods for each possible value of the Dyson index *β*.

### Wishart matrices

Wishart matrices form an ensemble of random positive semidefinite matrices. They are parametrized by two factors. First is the Dyson index *β* which is equal to one for real matrices, two for complex matrices and four for symplectic matrices. The second parameter, *K*, is responsible for the rank of the matrices. They are sampled as follows

- Choose
*β*and*K*. - Sample a Ginibre matrix with the Dyson index
*β*and and . - Return
*GG*^{†}.

In QuantumInformation.jl this is implemented using the type WishartEnsemble{*β*, K}. We also provide additional constructors for convenience

WishartEnsemble{*β*}(d::Int) where *β* = WishartEnsemble{*β*, 1}(d)

WishartEnsemble(d::Int) = WishartEnsemble{2}(d)

These can be used in the following way

julia> w = WishartEnsemble{1,0.2}(5)

WishartEnsemble{1,0.2}(d = 5)

julia> z = rand(w)

5×5 Array{Float64,2}:

0.0897637 0.0257443 0.0314593 0.0223569 0.093517

0.0257443 0.00738347 0.00902253 0.00641196 0.0268207

0.0314593 0.00902253 0.0110254 0.00783535 0.0327746

0.0223569 0.00641196 0.00783535 0.00556828 0.0232917

0.093517 0.0268207 0.0327746 0.0232917 0.0974271

julia> eigvals(z)

5-element Array{Float64,1}:

-1.549149323294561e-17

-1.11670454111383e-18

1.5797866551971292e-18

6.408793727745745e-18

0.21116803949130986

julia> w = WishartEnsemble(3)

WishartEnsemble{2,1}(d = 3)

julia> z = rand(w)

3×3 Array{Complex{Float64},2}:

0.474628+0.0im 0.177244-0.0227445im 0.137337-0.0929298im

0.177244+0.0227445im 0.128676+0.0im 0.0938587-0.165916im

0.137337+ 0.0929298im 0.0938587+0.165916im 0.555453+0.0im

julia> eigvals(z)

3-element Array{Float64,1}:

0.01707438064450695

0.35884924300093163

0.7828337014291611

### Circular ensembles

Circular ensembles are measures on the space of unitary matrices. There are three main circular ensembles. Each of this ensembles has an associated Dyson index *β* [42]

- Circular orthogonal ensemble (COE),
*β*= 1. - Circular unitary ensemble (CUE),
*β*= 2. - Circular symplectic ensemble (CSE),
*β*= 4.

They can be characterized as follows. The CUE is simply the Haar measure on the unitary group. Now, if *U* is an element of CUE then *U*^{T} *U* is an element of *COE* and *U*_{R} *U* is an element CSE. Here
(8)
As can be seen the sampling of Haar unitaries is at the core of sampling these ensembles. Hence, we will focus on them in the remainder of this section.

There are several possible approaches to generating random unitary matrices according to the Haar measure. One way is to consider known parametrizations of unitary matrices, such as the Euler [43] or Jarlskog [44] ones. Sampling these parameters from appropriate distributions yields a Haar random unitary. The downside is the long computation time, especially for large matrices, as this involves a lot of matrix multiplications. We will not go into this further, we refer the interested reader to the papers on these parametrizations.

Another approach is to consider a Ginibre matrix and its polar decomposition *G* = *UP*, where is unitary and *P* is a positive matrix. The matrix *P* is unique and given by . Hence, assuming *P* is invertible, we could recover *U* as
(9)
As this involves the inverse square root of a matrix, this approach can be potentially numerically unstable.

The optimal approach is to utilize the QR decomposition of *G*, *G* = *QR*, where is unitary and is upper triangular. This procedure is unique if *G* is invertible and we require the diagonal elements of *R* to be positive. As typical implementations of the QR algorithm do not consider this restriction, we must enforce it ourselves. The algorithm is as follows

- Generate a Ginibre matrix , with Dyson index
*β*= 2. - Perform the QR decomposition obtaining
*Q*and*R*. - Multiply the
*i*^{th}column of*Q*by*r*_{ii}/|*r*_{ii}|.

This gives us a Haar distributed random unitary. For detailed analysis of this algorithm see [27]. This procedure can be generalized in order to obtain a random isometry. The only required changed is the dimension of *G*. We simply start with , where .

Furthermore, we may introduce two additional circular ensembles corresponding to the Haar measure on the orthogonal and symplectic groups. These are the circular real ensemble (CRE) and circular quaternion ensemble (CQE). Their sampling is similar to sampling from CUE. The only difference is the initial Dyson index of the Ginibre matrix. This is set to *β* = 1 for CRE and *β* = 4 for CQE.

In QuantumInformation.jl these distributions can be sampled as

julia> c = CircularEnsemble{2}(3)

CircularEnsemble{2}(

d: 3

g: GinibreEnsemble{2}(m = 3, n = 3)

)

julia> u = rand(c)

3×3 Array{Complex{Float64},2}:

0.339685+0.550434im -0.392266-0.3216im -0.53172+0.203988im

0.515118-0.422262im 0.392165-0.626859im -0.0504431-0.084009im

0.297203+0.222832im -0.418737-0.143578im 0.607012-0.545525im

julia> u*u’

3×3 Array{Complex{Float64},2}:

1.0+0.0im -5.55112e-17-5.55112e-17im -2.77556e-17-4.16334e-17im

-5.55112e-17+5.55112e-17im 1.0+0.0im -2.498e-16+0.0im

-2.77556e-17+4.16334e-17im -2.498e-16+0.0im 1.0+0.0im

Sampling from the Haar measure on the orthogonal group can be achieved as

julia> c = CircularRealEnsemble(3)

CircularRealEnsemble(

d: 3

g: GinibreEnsemble{1}(m = 3, n = 3)

)

julia> o = rand(c)

3×3 Array{Float64,2}:

0.772464 0.611349 -0.171907

0.0524376 0.208368 0.976644

0.63289 -0.763436 0.128899

julia> o*o’

3×3 Array{Float64,2}:

1.0 -1.38778e-16 -8.67362e-17

-1.38778e-16 1.0 8.32667e-17

-8.67362e-17 8.32667e-17 1.0

For convenience we provide the following type aliases

### Random quantum states

In this section we discuss the properties and methods of generating random quantum states. We will treat quantum channels as a special case of quantum states.

#### Pure states.

Pure states are elements of the unit sphere in . Thus it is straightforward to generate them randomly. We start with a vector of independent complex numbers sampled from the standard normal distribution. What remains is to normalize the length of this vector to unity.

This is implemented using the HaarKet{*β*} type. The value *β* = 1 corresponds to the Haar measure on the unit sphere in ℝ^{d}, while *β* = 2 corresponds to the Haar measure on the unit sphere in ℂ^{d}. The usage is as follows

julia> h = HaarKet{2}(3)

HaarKet{2}(d = 3)

julia> *ψ* = rand(h)

3-element Array{Complex{Float64},1}:

0.1687649644765863 − 0.3201009507269653im

0.7187423269572294 − 0.39405022770434767im

0.1342475675218075 + 0.42327915636096036im

julia> norm(*ψ*)

1.0

For convenience we provide the following constructor

as the majority of uses cases require sampling complex states.

#### Mixed states.

Random mixed states can be generated in one of two equivalent ways. The first one comes from the partial trace of random pure states. Suppose we have a pure state . Then we can obtain a random mixed as (10) Note that in the case we recover the (flat) Hilbert-Schmidt distribution on the set of quantum states.

An alternative approach is to start with a Ginibre matrix . We obtain a random quantum state *ρ* as
(11)
It can be easily verified that this approach is equivalent to the one utilizing random pure states. First, note that in both cases we start with complex random numbers sampled from the standard normal distribution. Next, we only need to note that taking the partial trace of a pure state |*ψ*〉 is equivalent to calculating *AA*^{†} where *A* is a matrix obtained from reshaping |*ψ*〉.

The properties of these states have been extensively studied. We will omit stating all the properties here and refer the reader to [31–36].

Sampling random mixed states is implemented using the HilbertSchmidtStates{*β*, K} type. The meaning of the type parameters is the same as in the Wishart matrices case. We provide additional constructors which set the default values of the parameters

HilbertSchmidtStates{*β*}(d::Int) where *β* = HilbertSchmidtStates{*β*, 1}(d)

HilbertSchmidtStates(d::Int) = HilbertSchmidtStates{2, 1}(d)

The latter one is the most frequent use case. Here is an example

julia> h = HilbertSchmidtStates(3)

HilbertSchmidtStates{2,1}(WishartEnsemble{2,1}(d = 3), 3)

julia> *ρ* = rand(h)

3×3 Array{Complex{Float64},2}:

0.335603+0.0im 0.0696096+0.0606972im 0.0373103+0.0853966im

0.0696096-0.0606972im 0.209561+0.0im -0.000865656+0.0129982im

0.0373103-0.0853966im -0.000865656-0.0129982im 0.454836+0.0im

julia> tr(*ρ*)

1.0 + 0.0im

julia> eigvals(*ρ*)

3-element Array{Float64,1}:

0.15460054248543945

0.3306739537037592

0.5147255038108014

### Random quantum channels

Quantum channels are a special subclass of quantum states with constraints imposed on their *partial* trace as well as trace. Formally, we start with a Ginibre matrix . We obtain a random Choi-Jamiołkowski matrix *J*_{Φ} corresponding to a channel Φ as
(12)
When this is known to generate a uniform distribution over the set of quantum channels [37, 38].

The implementation uses the type ChoiJamiolkowskiMatrices{*β*, K}. The parameters *β* and *K* have the same meaning as in the Wishart matrix case. Additionally here, the constructor

takes two parameters—the input and output dimension of the channel. As in the previous cases we provide some additional constructors for convenience

function ChoiJamiolkowskiMatrices{*β*}(idim::Int, odim::Int) where *β*

ChoiJamiolkowskiMatrices{*β*, 1}(idim, odim)

end

function ChoiJamiolkowskiMatrices{*β*}(d::Int) where *β*

ChoiJamiolkowskiMatrices{*β*}(d, d)

end

function ChoiJamiolkowskiMatrices(idim::Int, odim::Int)

ChoiJamiolkowskiMatrices{2}(idim, odim)

end

function ChoiJamiolkowskiMatrices(d::Int)

ChoiJamiolkowskiMatrices(d, d)

end

Here is an example of usage

julia> c = ChoiJamiolkowskiMatrices(2, 3)

ChoiJamiolkowskiMatrices{2,1}(WishartEnsemble{2,1}(d = 6), 2, 3)

julia> Φ = rand(c)

DynamicalMatrix{Array{Complex{Float64},2}}

dimensions: (2, 3)

Complex{Float64}

[0.307971-4.98733e-18im -0.00411588+0.0368471im…

-0.0676732+0.024328im 0.0860858+0.00302876im;

-0.00411588-0.0368471im 0.167651+2.1684e-19im…

-0.0428561+0.0266119im 0.0191888+0.0101013im;

… ;

-0.0676732-0.024328im -0.0428561 − 0.0266119im…

0.210419+0.0im -0.103401 − 0.142753im;

0.0860858-0.00302876im 0.0191888 − 0.0101013im…

-0.103401+0.142753im 0.411068+0.0im]

julia> ptrace(Φ.matrix, [3, 2],[1])

2×2 Array{Complex{Float64},2}:

1.0 − 1.53957e-17im − 1.38778e-17 − 3.05311e-16im

1.38778e-17 + 3.05311e-16im 1.0 + 2.1684e-19im

Note that the resulting sample is of type DynamicalMatrix.

## Example

As an example we provide the teleportation protocol in the presence of noise. Imagine we have an entangled pair of particles in the state
(13)
One of the particles stays with Alice and another is sent through a noisy channel to Bob. As a noise model we chose the amplitude damping channel given by the Kraus operators
(14)
The channel has one parameter *γ* ∈ [0, 1] modeling the strength of the noise. Assume that Alice possesses a random pure state |*ϕ*〉 that she teleports to Bob. The protocol is shown in Fig 1.

Squiggly line represents the maximally entangled state, and box Φ represents the noise operator.

Our examples show the fidelity of the final state at Bob’s site averaged over 100 random pure initial states. We also check how the parameter *γ* influences this fidelity.

using QuantumInformation

steps = 100

haar = HaarKet(2)

*ψ* = (ket(0, 4) + ket(3, 4))/sqrt(2)

*γ*s = 0.0:0.01:1.0

Φ = KrausOperators([[1 0; 0 sqrt(1-γ)], [0 sqrt(*γ*); 0 0]])

post = [PostSelectionMeasurement(proj(ket(i, 4)) ⊗ eye(2)) for i = 0:3]

rots = [UnitaryChannel(eye(2)), UnitaryChannel(sx), UnitaryChannel(sz),

UnitaryChannel(sx*sz)]

had = UnitaryChannel{Matrix{ComplexF64}}(hadamard(2))

cnot = UnitaryChannel{Matrix{ComplexF64}}([1 0 0 0; 0 1 0 0; 0 0 0 1; 0 0 1 0])

r = zeros(steps, length(*γ*s), 4);

for (k, *γ*) in enumerate(*γ*s)

for i = 1:steps

*ϕ* = rand(haar)

*ξ* = *ϕ* ⊗ *ψ*

*ρ* = ((had ⊗ IdentityChannel(4))∘(cnot ⊗

IdentityChannel(2))∘(IdentityChannel(4) ⊗ Φ))(*ξ*)

for j = 1:4

*σ* = rots[j](ptrace(post[j](*ρ*), [2, 2, 2], [1, 2]))

r[i, k, j] = fidelity(*ϕ*, *σ*/tr(*σ*))

end

end

end

mean(r, 1)

## Benchmarks

In the benchmarks we compare our library to the state-of-the-art Python library, QuTiP [12, 13]. We perform the following tests:

- sampling a random unitary matrix,
- sampling a random pure state,
- sampling a random mixed state,
- sampling a random channel,
- calculating the trace distance of a random mixed state from the maximally mixed state,
- calculating the trace distance between two random mixed states,
- calculating the entropy of the stationary state of a random channel.

The latter is done as follows. First we sample a random quantum channel. Next, we apply the reshuffle operation and calculate its eigenvectors and eigenvalues. We take the state corresponding to the eigenvalue equal to one and calculate its von Neumann entropy. All tests are performed 1000 times and an average time of computation from these samples is calculated. In the case of Julia code we ensure all functions are compiled prior to testing. The tests are performed for dimensions 4, 16, 64, 256, 1024. In the next subsections we present and discuss results for all the aforementioned cases.

The tests were performed on a machine equipped with Intel Core i7-6800K and 64 GB of RAM. The libraries installed on the system were

julia> versioninfo()

julia Version 1.0.0

Platform Info:

OS: Linux (x86_64-redhat-linux)

CPU: Intel(R) Core(TM) i7-6800K CPU @ 3.40GHz

WORD_SIZE: 64

LIBM: libopenlibm

LLVM: libLLVM − 6.0.0 (ORCJIT, broadwell)

julia> LAPACK.version()

v“3.7.0”

julia> BLAS.vendor()

:openblas

julia> BLAS.openblas_get_config()

“DYNAMIC_ARCH_NO_AFFINITY Haswell”

The Python libraries were

In [1]: import numpy as np

In [2]: np.__version__

Out[2]: ‘1.7.1’

In [3]: np.__config__.show()

Out[3]:

blas_mkl_info:

NOT AVAILABLE

blis_info:

NOT AVAILABLE

openblas_info:

libraries = [‘openblas’, ‘openblas’]

library_dirs = [‘/home/user/anaconda3/lib’]

language = c

define_macros = [(‘HAVE CBLAS’, None)]

blas_opt_info:

libraries = [‘openblas’, ‘openblas’]

library_dirs = [‘/home/user/anaconda3/lib’]

language = c

define_macros = [(‘HAVE CBLAS’, None)]

lapack_mkl_info:

NOT AVAILABLE

openblas_lapack_info:

libraries = [‘openblas’, ‘openblas’]

library_dirs = [‘/home/user/anaconda3/lib’]

language = c

define_macros = [(‘HAVE CBLAS’, None)]

lapack_opt_info:

libraries = [‘openblas’, ‘openblas’]

library_dirs = [‘/home/user/anaconda3/lib’]

language = c

define_macros = [(‘HAVE CBLAS’, None)]

### Sampling a random unitary matrix

The Julia code for this test is

using QuantumInformation

function random_unitary(steps::Int, d::Int)

dist = CUE(d)

for i = 1:steps U = rand(dist) end

end

The Python implementation reads

The benchmark results are presented in Fig 2. Note that, as advertised, our implementation is faster and the gap gets bigger as the dimension of the input system increases.

### Sampling a random pure state

The Julia code for this test is

using QuantumInformation

function random_pure_state(steps::Int, d::Int)

dist = HaarKet(d)

for i = 1:steps *ψ* = rand(dist) end

end

The Python implementation reads

The benchmark results are presented in Fig 3. In this case we get a huge difference in the computation times. This is due to the fact that QuTiP first samples an entire random unitary matrix and returns its first column as the sampled state. On the other hand our implementation samples only one vector.

### Sampling a random mixed state

The Julia code for this test is

using QuantumInformation

function random_mixed_state(steps::Int, d::Int)

dist = HilbertSchmidtStates(d)

for i = 1:steps *ρ* = rand(dist) end

end

The Python implementation reads

The benchmark results are presented in Fig 4. Again, our package is faster compared to QuTiP.

### Sampling a random channel

The Julia code for this test is

using QuantumInformation

function random_channel(steps::Int, d::Int)

dist = ChoiJamiolkowskiMatrices(round(Int, sqrt(d)))

for i = 1:steps Φ = convert(SuperOperator{Matrix{ComplexF64}}, rand(dist)) end

end

The Python implementation reads

import qutip as q

def random_channel(steps, d):

for _ in range(steps):

q.rand_super_bcsz(int(np.sqrt(d)))

Note the conversion to SuperOperator in the benchmark. This is to mimic QuTiP’s behavior which returns a superoperator. The benchmark results are presented in Fig 5.

### Calculating the trace distance form the maximally mixed state

The Julia code for this test is

using QuantumInformation

function trace_distance_max_mixed(steps::Int, d::Int)

dist = HilbertSchmidtStates(d)

for i = 1:steps trace distance(rand(dist), *ρ*) end

end

The Python implementation reads

import qutip as q

def trace_distance_max_mixed(steps, d):

rho = q.Qobj(np.eye(d) / d)

for _ in range(steps):

q.metrics.tracedist(q.rand_dm_hs(d), rho)

The benchmark results are presented in Fig 6. Again, for all studied dimensions, our implementation is faster compared to Python.

### Calculating the trace distance between two random mixed states

The Julia code for this test is

using QuantumInformation

function trace_distance_random(steps::Int, d::Int)

dist = HilbertSchmidtStates(d)

for i = 1:steps trace_distance(rand(dist), rand(dist)) end

end

The Python implementation reads

import qutip as q

def trace_distance_random(steps, d):

for _ in range(steps):

q.metrics.tracedist(q.rand_dm_hs(d), q.rand_dm_hs(d))

The benchmark results are presented in Fig 7.

### Calculating the entropy of the stationary state of a random channel

The Julia code for this test is

using QuantumInformation

function random_unitary(steps::Int, d::Int)

dist = CUE(d)

for i = 1:steps U = rand(dist) end

end

The Python implementation reads

The benchmark results are presented in Fig 8.

## Conclusions and future work

Numerical investigations are important part of research in many fields of science, especially in quantum information. The Julia language is a modern programming language, which provides strong support for linear algebra and posses an extensive type system. One of the important feature of Julia is high performance approaching statically-compiled languages like C or Fortran. Those were the reasons why we created the QuantumInformation.jl library in Julia.

We performed benchmark comparisons of QuantumInformation.jl with QuTiP. They clearly state that our library is faster compared to the current state of the art. As the core numerical libraries were the same for both tested packages, we conclude that this speedup is due to the advantages offered by Julia.

Future work will consists of optimization of numerical code, extending the type system, developing further functionals, better integration with Convex.jl package. Additional work will also include parallelization of the code and support for writing quantum circuits in more intuitive manner.

## References

- 1.
QuantumInformation.jl;. Available from: https://doi.org/10.5281/zenodo.1745488.
- 2.
Quantiki: List of QC simulators;. Available from: https://quantiki.org/wiki/list-qc-simulators.
- 3. Miszczak JA. Singular value decomposition and matrix reorderings in quantum information theory. International Journal of Modern Physics C. 2011;22(09):897–918.
- 4. Miszczak JA. Generating and using truly random quantum states in Mathematica. Computer Physics Communications. 2012;183(1):118–124.
- 5. Munoz JG, Delgado F. QUANTUM: A Wolfram Mathematica add-on for Dirac Bra-Ket Notation, Non-Commutative Algebra, and Simulation of Quantum Computing Circuits. In: Journal of Physics: Conference Series. vol. 698. IOP Publishing; 2016. p. 012019.
- 6. Radtke T, Fritzsche S. Simulation of n-qubit quantum systems. I. Quantum registers and quantum gates. Computer Physics Communications. 2005;173(1-2):91–113.
- 7. Radtke T, Fritzsche S. Simulation of n-qubit quantum systems. II. Separability and entanglement. Computer Physics Communications. 2006;175(2):145–166.
- 8. Radtke T, Fritzsche S. Simulation of n-qubit quantum systems. III. Quantum operations. Computer Physics Communications. 2007;176(9-10):617–633.
- 9. Radtke T, Fritzsche S. Simulation of n-qubit quantum systems. IV. Parametrizations of quantum states, matrices and probability distributions. Computer Physics Communications. 2008;179(9):647–664.
- 10. Radtke T, Fritzsche S. Simulation of n-qubit quantum systems. V. Quantum measurements. Computer Physics Communications. 2010;181(2):440–453.
- 11. Ince DC, Hatton L, Graham-Cumming J. The case for open computer programs. Nature. 2012;482(7386):485. pmid:22358837
- 12. Johansson J, Nation P, Nori F. QuTiP: An open-source Python framework for the dynamics of open quantum systems. Computer Physics Communications. 2012;183(8):1760–1772.
- 13. Johansson J, Nation P, Nori F. QuTiP 2: A Python framework for the dynamics of open quantum systems. Computer Physics Communications. 2013;184:1234–1240.
- 14.
Travis E O. A guide to NumPy. Trelgol Publishing;.
- 15.
Jones E, Oliphant T, Peterson P, et al. SciPy: Open source scientific tools for Python; 2001–. Available from: http://www.scipy.org/.
- 16.
The Julia language;. Available from: https://docs.julialang.org/.
- 17.
QuantumInfo.jl;. Available from: https://github.com/BBN-Q/QuantumInfo.jl.
- 18.
Quantum.jl;. Available from: https://github.com/acroy/Quantum.jl.
- 19.
Julia|Quantum〉. Available from: http://juliaquantum.github.io/projects/.
- 20. Krämer S, Plankensteiner D, Ostermann L, Ritsch H. QuantumOptics.jl: A Julia framework for simulating open quantum systems. Computer Physics Communications. 2018;227:109–116. https://doi.org/10.1016/j.cpc.2018.02.004.
- 21.
Bengtsson I, Życzkowski K. Geometry of Quantum States: An Introduction to Quantum Entanglement. 1st ed. Cambridge University Press; 2008.
- 22. Bezanson J, Edelman A, Karpinski S, Shah VB. Julia: A fresh approach to numerical computing. SIAM Review. 2017;59(1):65–98.
- 23.
Lattner C, Adve V. LLVM: A compilation framework for lifelong program analysis & transformation. In: Proceedings of the international symposium on Code generation and optimization: feedback-directed and runtime optimization. IEEE Computer Society; 2004. p. 75.
- 24.
Bezanson J, Chen J, Karpinski S, Shah V, Edelman A. Array operators using multiple dispatch: A design methodology for array implementations in dynamic languages. In: Proceedings of ACM SIGPLAN International Workshop on Libraries, Languages, and Compilers for Array Programming. ACM; 2014. p. 56.
- 25.
Udell M, Mohan K, Zeng D, Hong J, Diamond S, Boyd S. Convex Optimization in Julia. SC14 Workshop on High Performance Technical Computing in Dynamic Languages. 2014;.
- 26.
Jutho. TensorOperations.jl;. Available from: https://github.com/Jutho/TensorOperations.jl.
- 27. Mezzadri F. How to generate random matrices from the classical compact groups. Notices of the American Mathematical Society. 2007;54(5):592–604.
- 28.
Watrous J. The Theory of Quantum Information. Cambridge University Press; 2018.
- 29. Hill S, Wootters WK. Entanglement of a pair of quantum bits. Physical Review Letters. 1997;78(26):5022.
- 30. Collins B, Nechita I. Random matrix techniques in quantum information theory. Journal of Mathematical Physics. 2016;57(1):015215.
- 31. Wootters WK. Random quantum states. Foundations of Physics. 1990;20(11):1365–1378.
- 32. Życzkowski K, Sommers HJ. Induced measures in the space of mixed quantum states. Journal of Physics A: Mathematical and General. 2001;34(35):7111.
- 33. Sommers HJ, Życzkowski K. Statistical properties of random density matrices. Journal of Physics A: Mathematical and General. 2004;37(35):8457.
- 34. Puchała Z, Pawela Ł, Życzkowski K. Distinguishability of generic quantum states. Physical Review A. 2016;93(6):062112.
- 35. Zhang L, Singh U, Pati AK. Average subentropy, coherence and entanglement of random mixed quantum states. Annals of Physics. 2017;377:125–146.
- 36. Zhang L. Average coherence and its typicality for random mixed quantum states. Journal of Physics A: Mathematical and Theoretical. 2017;50(15):155303.
- 37. Bruzda W, Cappellini V, Sommers HJ, Życzkowski K. Random quantum operations. Physics Letters A. 2009;373(3):320–324.
- 38. Nechita I, Puchała Z, Pawela Ł, Życzkowski K. Almost all quantum channels are equidistant. Journal of Mathematical Physics. 2018;59(5):052201.
- 39. Zhang L, Wang J, Chen Z. Spectral density of mixtures of random density matrices for qubits. Physics Letters A. 2018;382(23):1516–1523.
- 40. Ginibre J. Statistical ensembles of complex, quaternion, and real matrices. Journal of Mathematical Physics. 1965;6(3):440–449.
- 41. Tao T, Vu V. Random matrices: the circular law. Communications in Contemporary Mathematics. 2008;10(02):261–307.
- 42.
Mehta ML. Random matrices. vol. 142. Elsevier; 2004.
- 43. Życzkowski K, Kuś M. Random unitary matrices. Journal of Physics A: Mathematical and General. 1994;27(12):4235.
- 44. Jarlskog C. A recursive parametrization of unitary matrices. Journal of Mathematical Physics. 2005;46(10):103508.