|
1 | 1 | import Base: isapprox
|
2 | 2 | import QuantumInterface: AbstractSuperOperator
|
3 | 3 | import FastExpm: fastExpm
|
| 4 | +import KrylovKit: eigsolve |
| 5 | + |
| 6 | +# TODO: this should belong in QuantumInterface.jl |
| 7 | +abstract type OperatorBasis{BL<:Basis,BR<:Basis} end |
| 8 | +abstract type SuperOperatorBasis{BL<:OperatorBasis,BR<:OperatorBasis} end |
| 9 | + |
| 10 | +""" |
| 11 | + tensor(E::AbstractSuperOperator, F::AbstractSuperOperator, G::AbstractSuperOperator...) |
| 12 | +
|
| 13 | +Tensor product ``\\mathcal{E}⊗\\mathcal{F}⊗\\mathcal{G}⊗…`` of the given super operators. |
| 14 | +""" |
| 15 | +tensor(a::AbstractSuperOperator, b::AbstractSuperOperator) = arithmetic_binary_error("Tensor product", a, b) |
| 16 | +tensor(op::AbstractSuperOperator) = op |
| 17 | +tensor(operators::AbstractSuperOperator...) = reduce(tensor, operators) |
| 18 | + |
4 | 19 |
|
5 | 20 | """
|
6 | 21 | SuperOperator <: AbstractSuperOperator
|
@@ -355,8 +370,25 @@ dagger(a::ChoiState) = ChoiState(dagger(SuperOperator(a)))
|
355 | 370 | ==(a::ChoiState, b::ChoiState) = (SuperOperator(a) == SuperOperator(b))
|
356 | 371 | isapprox(a::ChoiState, b::ChoiState; kwargs...) = isapprox(SuperOperator(a), SuperOperator(b); kwargs...)
|
357 | 372 |
|
358 |
| -# TOOD: decide whether to document and export this |
359 |
| -choi_to_operator(c::ChoiState) = Operator(c.basis_l[2]⊗c.basis_l[1], c.basis_r[2]⊗c.basis_r[1], c.data) |
| 373 | +# Container to hold each of the four bases for a Choi operator when converting it to |
| 374 | +# an operator so that if any are CompositeBases tensor doesn't lossily collapse them |
| 375 | +struct ChoiSubBasis{S,B<:Basis} <: Basis |
| 376 | + shape::S |
| 377 | + basis::B |
| 378 | +end |
| 379 | +ChoiSubBasis(b::Basis) = ChoiSubBasis(b.shape, b) |
| 380 | + |
| 381 | +# TODO: decide whether to document and export this |
| 382 | +choi_to_operator(c::ChoiState) = Operator( |
| 383 | + ChoiSubBasis(c.basis_l[2])⊗ChoiSubBasis(c.basis_l[1]), ChoiSubBasis(c.basis_r[2])⊗ChoiSubBasis(c.basis_r[1]), c.data) |
| 384 | + |
| 385 | +function tensor(a::ChoiState, b::ChoiState) |
| 386 | + op = choi_to_operator(a) ⊗ choi_to_operator(b) |
| 387 | + op = permutesystems(op, [1,3,2,4]) |
| 388 | + ChoiState((a.basis_l[1] ⊗ b.basis_l[1], a.basis_l[2] ⊗ b.basis_l[2]), |
| 389 | + (a.basis_r[1] ⊗ b.basis_r[1], a.basis_r[2] ⊗ b.basis_r[2]), op.data) |
| 390 | +end |
| 391 | +tensor(a::SuperOperator, b::SuperOperator) = SuperOperator(tensor(ChoiState(a), ChoiState(b))) |
360 | 392 |
|
361 | 393 | # reshape swaps within systems due to colum major ordering
|
362 | 394 | # https://docs.qojulia.org/quantumobjects/operators/#tensor_order
|
@@ -425,16 +457,18 @@ end
|
425 | 457 | KrausOperators{BL,BR}(b1::BL,b2::BR,data::Vector{T}) where {BL,BR,T} = KrausOperators{BL,BR,T}(b1,b2,data)
|
426 | 458 | KrausOperators(b1::BL,b2::BR,data::Vector{T}) where {BL,BR,T} = KrausOperators{BL,BR,T}(b1,b2,data)
|
427 | 459 |
|
428 |
| -tensor(a::KrausOperators, b::KrausOperators) = |
429 |
| - KrausOperators(a.basis_l ⊗ b.basis_l, a.basis_r ⊗ b.basis_r, |
430 |
| - [A ⊗ B for A in a.data for B in b.data]) |
| 460 | +dense(a::KrausOperators) = KrausOperators(a.basis_l, a.basis_r, [dense(op) for op in a.data]) |
| 461 | +sparse(a::KrausOperators) = KrausOperators(a.basis_l, a.basis_r, [sparse(op) for op in a.data]) |
431 | 462 | dagger(a::KrausOperators) = KrausOperators(a.basis_r, a.basis_l, [dagger(op) for op in a.data])
|
432 | 463 | *(a::KrausOperators{B1,B2}, b::KrausOperators{B2,B3}) where {B1,B2,B3} =
|
433 | 464 | KrausOperators(a.basis_l, b.basis_r, [A*B for A in a.data for B in b.data])
|
434 | 465 | *(a::KrausOperators, b::KrausOperators) = throw(IncompatibleBases())
|
435 | 466 | *(a::KrausOperators{BL,BR}, b::Operator{BR,BR}) where {BL,BR} = sum(op*b*dagger(op) for op in a.data)
|
436 | 467 | ==(a::KrausOperators, b::KrausOperators) = (SuperOperator(a) == SuperOperator(b))
|
437 | 468 | isapprox(a::KrausOperators, b::KrausOperators; kwargs...) = isapprox(SuperOperator(a), SuperOperator(b); kwargs...)
|
| 469 | +tensor(a::KrausOperators, b::KrausOperators) = |
| 470 | + KrausOperators(a.basis_l ⊗ b.basis_l, a.basis_r ⊗ b.basis_r, |
| 471 | + [A ⊗ B for A in a.data for B in b.data]) |
438 | 472 |
|
439 | 473 | """
|
440 | 474 | orthogonalize(kraus::KrausOperators; tol=1e-12)
|
@@ -509,17 +543,29 @@ _is_hermitian(M; tol=1e-12) = ishermitian(M) || isapprox(M, M', atol=tol)
|
509 | 543 | _is_identity(M; tol=1e-12) = isapprox(M, I, atol=tol)
|
510 | 544 |
|
511 | 545 | # TODO: document
|
| 546 | +# data must be Hermitian! |
| 547 | +# performance of dense version typically faster until underlying hilbert spaces |
| 548 | +# have dimension on the order 60 or so? |
512 | 549 | function _positive_eigen(data; tol=1e-12)
|
513 |
| - # TODO: figure out how to do this with sparse matrices using e.g. Arpack.jl or ArnoldiMethod.jl |
514 |
| - # I will want to run twice, first asking for smallest eigenvalue to check it is above -tol |
515 |
| - # Then run a second time with asking for maybe sqrt(N) largest eigenvalues? |
516 |
| - # If smallest of these is not smaller than tol, bail do dense method? |
517 | 550 | # LinearAlgebra's eigen returns eigenvals sorted smallest to largest for Hermitian matrices
|
518 | 551 | vals, vecs = eigen(Hermitian(Matrix(data)))
|
519 | 552 | vals[1] < -tol && return vals[1]
|
520 | 553 | return [(val, vecs[:,i]) for (i, val) in enumerate(vals) if val > tol]
|
521 | 554 | end
|
522 | 555 |
|
| 556 | +# To control the precision of eigsolve, set KrylovDefaults.tol |
| 557 | +# this isn't controlled by tol since we genally want KrolovKit to run |
| 558 | +# with much higher precision so the eigenvectors we get are "good" |
| 559 | +function _positive_eigen(data::SparseMatrixCSC; tol=1e-12) |
| 560 | + vals, vecs, info = eigsolve(Hermitian(data), 1, :SR) |
| 561 | + info.converged < 1 && return -Inf |
| 562 | + vals[1] < -tol && return vals[1] |
| 563 | + vals, vecs, info = eigsolve(Hermitian(data), 1, :LM) |
| 564 | + vals[end] > tol && return _positive_eigen(Matrix(data); tol=tol) |
| 565 | + return [(val, vec) for (val, vec) in zip(vals, vecs) if val > tol] |
| 566 | +end |
| 567 | + |
| 568 | + |
523 | 569 | function KrausOperators(choi::ChoiState; tol=1e-12)
|
524 | 570 | if !_choi_state_maps_density_ops(choi)
|
525 | 571 | throw(DimensionMismatch("Tried to convert Choi state of something that isn't a quantum channel mapping density operators to density operators"))
|
|
0 commit comments