diff --git a/.github/workflows/ci-julia-nightly.yml b/.github/workflows/ci-julia-nightly.yml index 17506a3..2fa8e75 100644 --- a/.github/workflows/ci-julia-nightly.yml +++ b/.github/workflows/ci-julia-nightly.yml @@ -38,6 +38,13 @@ jobs: with: channel: ${{ matrix.version }}~${{ matrix.arch }} - uses: julia-actions/cache@v2 + + # --- START OF ADDED/MODIFIED BLOCK --- + - name: Update Julia Registries (Aggressive) + run: | + julia -e 'import Pkg; Pkg.Registry.rm("General"); Pkg.Registry.add("General"); Pkg.Registry.update()' + # --- END OF ADDED/MODIFIED BLOCK --- + - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 env: @@ -47,4 +54,4 @@ jobs: - uses: codecov/codecov-action@v5 with: file: lcov.info - token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 705c365..78a55c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,11 @@ jobs: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - uses: julia-actions/cache@v2 + # --- MODIFIED STEP TO UPDATE JULIA REGISTRIES (AGGRESSIVE) --- + - name: Update Julia Registries (Aggressive) + run: | + julia -e 'import Pkg; Pkg.Registry.rm("General"); Pkg.Registry.add("General"); Pkg.Registry.update()' + # --- END OF MODIFIED STEP --- - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 env: diff --git a/.github/workflows/downgrade.yml b/.github/workflows/downgrade.yml index 9af107d..1114527 100644 --- a/.github/workflows/downgrade.yml +++ b/.github/workflows/downgrade.yml @@ -25,5 +25,12 @@ jobs: with: skip: Pkg,TOML,InteractiveUtils,Random,LinearAlgebra - uses: julia-actions/cache@v2 + + + - name: Update Julia Registries (Aggressive) + run: | + julia -e 'import Pkg; Pkg.Registry.rm("General"); Pkg.Registry.add("General"); Pkg.Registry.update()' + + - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-runtest@v1 \ No newline at end of file + - uses: julia-actions/julia-runtest@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1851f3f..e2f74b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # News +## [Unreleased] +### Added +- New `StateVectorRepr` backend for converting symbolic quantum objects to numerical vectors/matrices using `QuantumOpticsRepr` (Conversion of symbolic objects to base linear algebra objects (vectors, matrices, sparse matrices, etc)) (#118). + ## v0.4.10 - 2025-05-11 - Polish `Base.show` methods for application products and scaled quantum objects. diff --git a/Project.toml b/Project.toml index ba5d880..d48c577 100644 --- a/Project.toml +++ b/Project.toml @@ -19,8 +19,8 @@ QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" [extensions] MixedCliffordOpticsExt = ["QuantumClifford", "QuantumOpticsBase"] -QuantumCliffordExt = "QuantumClifford" -QuantumOpticsExt = "QuantumOpticsBase" +QuantumCliffordExt = ["QuantumClifford"] +QuantumOpticsExt = ["QuantumOpticsBase"] [compat] Latexify = "0.16" diff --git a/docs/src/index.md b/docs/src/index.md index 62c75f6..5964501 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -203,3 +203,18 @@ express(MixedState(X1)/2+SProjector(Z1)/2, CliffordRepr()) !!! warning "Stabilizer state expressions" The state written as $\frac{|Z₁⟩⊗|Z₁⟩+|Z₂⟩⊗|Z₂⟩}{√2}$ is a well known stabilizer state, namely a Bell state. However, automatically expressing it as a stabilizer is a prohibitively expensive computational operation in general. We do not perform that computation automatically. If you want to ensure that states you define can be automatically converted to tableaux for Clifford simulations, avoid using summation of kets. On the other hand, in all of our Clifford Monte-Carlo simulations, `⊗` is fully supported, as well as [`projector`](@ref), [`MixedState`](@ref), [`StabilizerState`](@ref), and summation of density matrices. + +## Backends + +`QuantumSymbolics.jl` supports multiple numerical backends via the `express` API. In addition to structured representations like `QuantumOpticsRepr` and `CliffordRepr`, the new `StateVectorRepr` backend converts symbolic objects to basic linear algebra objects: + +```julia +julia> express(X1, StateVectorRepr()) +2-element Vector{ComplexF64}: + 0.7071067811865475 + 0.0im + 0.7071067811865475 + 0.0im + +julia> express(Z1, StateVectorRepr()) +2×2 Matrix{ComplexF64}: + 1.0+0.0im 0.0+0.0im + 0.0+0.0im -1.0+0.0im diff --git a/ext/QuantumOpticsExt/QuantumOpticsExt.jl b/ext/QuantumOpticsExt/QuantumOpticsExt.jl index 0046ff6..f86c785 100644 --- a/ext/QuantumOpticsExt/QuantumOpticsExt.jl +++ b/ext/QuantumOpticsExt/QuantumOpticsExt.jl @@ -15,6 +15,8 @@ import QuantumSymbolics: express, express_nolookup using TermInterface using TermInterface: isexpr, head, operation, arguments, metadata +using SymbolicUtils + const _b2 = SpinBasis(1//2) const _l0 = spinup(_b2) const _l1 = spindown(_b2) @@ -100,4 +102,29 @@ express_nolookup(s::SOuterKetBra, r::QuantumOpticsRepr) = projector(express(s.ke include("should_upstream.jl") +""" + StateVectorRepr(config=nothing) + +A custom backend for `QuantumSymbolics.express`. Converts symbolic quantum +objects into Julia's `Vector{ComplexF64}` (kets) or `Matrix{ComplexF64}` (operators). + +Internally uses `QuantumOptics.QuantumOpticsRepr` and extracts `.data`. +An optional `config` (e.g., `(cutoff=4,)`) is forwarded to `QuantumOptics.QuantumOpticsRepr`. +""" +struct StateVectorRepr + cutoff::Int + StateVectorRepr(cutoff::Int) = new(cutoff) + StateVectorRepr(; cutoff::Int = 0) = new(cutoff) +end + +function QuantumSymbolics.express(sym_obj::SymbolicUtils.Symbolic, backend::StateVectorRepr) + qo_repr = if backend.config isa Nothing + QuantumOptics.QuantumOpticsRepr() + else + QuantumOptics.QuantumOpticsRepr(backend.config...) + end + qo_obj = QuantumSymbolics.express(sym_obj, qo_repr) + return qo_obj.data +end + end diff --git a/test/Project.toml b/test/Project.toml index 1d4a8c4..45a5aa7 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,6 @@ [deps] +QuantumSavory = "b2e4b478-f7b5-4b07-94a2-11c52b7b515a" +QuantumSymbolics = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" @@ -9,7 +11,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" QuantumClifford = "0525e862-1e90-11e9-3e4d-1b39d7109de1" QuantumInterface = "5717a53b-5d69-4fa3-b976-0bf2f97ca1e5" -QuantumOptics = "6e0679c1-51ea-5a7c-ac74-d61b76210b0c" QuantumOpticsBase = "4f57444f-1401-5e15-980d-4471b28d5678" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" @@ -20,3 +21,13 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" + + + +[compat] +julia = "1" +QuantumSavory = "0.5" +QuantumSymbolics = "0.4" + +[targets] +test = ["Test", "TestItemRunner", "QuantumSavory", "QuantumClifford", "QuantumInterface", "QuantumOpticsBase", "SymbolicUtils", "TermInterface"] diff --git a/test/test_statevectorrepr.jl b/test/test_statevectorrepr.jl new file mode 100644 index 0000000..558e31d --- /dev/null +++ b/test/test_statevectorrepr.jl @@ -0,0 +1,37 @@ +using QuantumSymbolics +using QuantumOptics +using QuantumSavory + +@testitem "StateVectorRepr Conversion Tests" begin + # Test: Symbolic Ket (X1) conversion + sym_X1_test = QuantumSavory.X1 + qo_X1_data = QuantumOptics.express(sym_X1_test, QuantumOptics.QuantumOpticsRepr()).data + sv_X1_data = express(sym_X1_test, StateVectorRepr()) + @test sv_X1_data isa Vector{ComplexF64} + @test isapprox(sv_X1_data, qo_X1_data) + @test length(sv_X1_data) == 2 + + # Test: Symbolic Operator (Z1) conversion + sym_Z1_test = QuantumSavory.Z1 + qo_Z1_data = QuantumOptics.express(sym_Z1_test, QuantumOptics.QuantumOpticsRepr()).data + sv_Z1_data = express(sym_Z1_test, StateVectorRepr()) + @test sv_Z1_data isa Matrix{ComplexF64} + @test isapprox(sv_Z1_data, qo_Z1_data) + @test size(sv_Z1_data) == (2, 2) + + # Test: Product operator (X1 * Y2) conversion + sym_XY_test = QuantumSavory.X1 * QuantumSavory.Y2 + qo_XY_data = QuantumOptics.express(sym_XY_test, QuantumOptics.QuantumOpticsRepr()).data + sv_XY_data = express(sym_XY_test, StateVectorRepr()) + @test sv_XY_data isa Matrix{ComplexF64} + @test isapprox(sv_XY_data, qo_XY_data) + @test size(sv_XY_data) == (4, 4) + + # Test: Bosonic operator (N) with custom cutoff + sym_N_test = QuantumSavory.N + qo_N_cutoff4_data = QuantumOptics.express(sym_N_test, QuantumOptics.QuantumOpticsRepr(cutoff=4)).data + sv_N_cutoff4_data = express(sym_N_test, StateVectorRepr(cutoff=4)) + @test sv_N_cutoff4_data isa Matrix{ComplexF64} + @test isapprox(sv_N_cutoff4_data, qo_N_cutoff4_data) + @test size(sv_N_cutoff4_data) == (5, 5) +end