|
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) |
@@ -508,18 +542,33 @@ _choi_state_maps_density_ops(choi::ChoiState) = (samebases(choi.basis_l[1], choi |
508 | 542 | _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 | | -# TODO: document |
| 545 | +# TODO: document, data must be Hermitian! |
512 | 546 | 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 | 547 | # LinearAlgebra's eigen returns eigenvals sorted smallest to largest for Hermitian matrices |
518 | 548 | vals, vecs = eigen(Hermitian(Matrix(data))) |
519 | 549 | vals[1] < -tol && return vals[1] |
520 | 550 | return [(val, vecs[:,i]) for (i, val) in enumerate(vals) if val > tol] |
521 | 551 | end |
522 | 552 |
|
| 553 | +# first asks for smallest eigenvalue to check it is above -tol |
| 554 | +# Then run a second time with asking for sqrt(N) largest eigenvalues |
| 555 | +# If smallest of these is not smaller than tol, bail to dense method |
| 556 | +function _positive_eigen(data::SparseMatrixCSC; tol=1e-12) |
| 557 | + vals, vecs, info = eigsolve(Hermitian(data), 1, :SR; tol=tol) |
| 558 | + info.converged < 1 && return -Inf |
| 559 | + vals[1] < -tol && return vals[1] |
| 560 | + n = 1 + floor(Int, sqrt(size(data)[1])) |
| 561 | + vals, vecs, info = eigsolve(Hermitian(data), n, :LM; tol=tol) |
| 562 | + println(vals) |
| 563 | + if info.converged < n || vals[n] > tol |
| 564 | + println("bailed to dense method!!! n=", n, " size(data)=", size(data)) |
| 565 | + return _positive_eigen(Matrix(data)) |
| 566 | + else |
| 567 | + return [(val, vec) for (val, vec) in zip(vals, vecs) if val > tol] |
| 568 | + end |
| 569 | +end |
| 570 | + |
| 571 | + |
523 | 572 | function KrausOperators(choi::ChoiState; tol=1e-12) |
524 | 573 | if !_choi_state_maps_density_ops(choi) |
525 | 574 | throw(DimensionMismatch("Tried to convert Choi state of something that isn't a quantum channel mapping density operators to density operators")) |
|
0 commit comments