diff --git a/lib/YaoPlots/Project.toml b/lib/YaoPlots/Project.toml index 7e53dc1e..179e79c7 100644 --- a/lib/YaoPlots/Project.toml +++ b/lib/YaoPlots/Project.toml @@ -5,14 +5,12 @@ version = "0.9.2" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Luxor = "ae8d54c2-7ccd-5906-9d76-62fc9837b5bc" -Thebes = "8b424ff8-82f5-59a4-86a6-de3761897198" YaoArrayRegister = "e600142f-9330-5003-8abb-0ebd767abc51" YaoBlocks = "418bc28f-b43b-5e0b-a6e7-61bbc1a2c1df" [compat] LinearAlgebra = "1" -Luxor = "3" -Thebes = "1" +Luxor = "4" YaoArrayRegister = "0.9" YaoBlocks = "0.13" julia = "1" diff --git a/lib/YaoPlots/README.md b/lib/YaoPlots/README.md index ec5d9cd7..42f63e5a 100644 --- a/lib/YaoPlots/README.md +++ b/lib/YaoPlots/README.md @@ -23,7 +23,7 @@ using YaoPlots, Yao reg = zero_state(1) |> Rx(π/8) |> Rx(π/8) rho = density_matrix(ghz_state(2), 1) -bloch_sphere("|ψ⟩"=>reg, "ρ"=>rho; show_projection_lines=true) +bloch_sphere("|ψ>"=>reg, "ρ"=>rho; show_projection_lines=true) ``` Similarly, you will see diff --git a/lib/YaoPlots/src/3d.jl b/lib/YaoPlots/src/3d.jl new file mode 100644 index 00000000..597e357c --- /dev/null +++ b/lib/YaoPlots/src/3d.jl @@ -0,0 +1,100 @@ +# The following code is copied from Thebes.jl, which is a package for 3D plotting in Julia. +# Since Thebes.jl is not updated for a while, we need to update the code to make it work with the latest version of Luxor.jl. + +module Thebes +using Luxor +struct Point3D + x::Float64 + y::Float64 + z::Float64 +end + +mutable struct Projection + U::Point3D # + V::Point3D # + W::Point3D # + ue::Float64 # + ve::Float64 # + we::Float64 # + eyepoint::Point3D + centerpoint::Point3D + uppoint::Point3D + perspective::Float64 # +end + +function newprojection(ipos::Point3D, center::Point3D, up::Point3D, perspective=0.0) + if iszero(ipos.x) + ipos = Point3D(10e-9, ipos.y, ipos.z) + end + if iszero(ipos.y) + ipos = Point3D(ipos.x, 10e-9, ipos.z) + end + if iszero(ipos.z) + ipos = Point3D(ipos.x, ipos.y, 10e-9) + end + + # w is the line of sight + W = Point3D(center.x - ipos.x, center.y - ipos.y, center.z - ipos.z) + r = (W.x * W.x) + (W.y * W.y) + (W.z * W.z) + if r < eps() + @info("eye position and center are the same") + + W = Point3D(0.0, 0.0, 0.0) + else + # distancealise w to unit length + rinv = 1 / sqrt(r) + W = Point3D(W.x * rinv, W.y * rinv, W.z * rinv) + end + we = W.x * ipos.x + W.y * ipos.y + W.z * ipos.z # project e on to w + U = Point3D(W.y * (up.z - ipos.z) - W.z * (up.y - ipos.y), # u is at right angles to t - e + W.z * (up.x - ipos.x) - W.x * (up.z - ipos.z), # and w ., its' the pictures x axis + W.x * (up.y - ipos.y) - W.y * (up.x - ipos.x)) + r = (U.x * U.x) + (U.y * U.y) + (U.z * U.z) + + if r < eps() + @info("struggling to make a valid projection with these parameters") + U = Point3D(0.0, 0.0, 0.0) + else + rinv = 1 / sqrt(r) # distancealise u + U = Point3D(U.x * rinv, U.y * rinv, U.z * rinv) + end + + ue = U.x * ipos.x + U.y * ipos.y + U.z * ipos.z # project e onto u + + V = Point3D(U.y * W.z - U.z * W.y, # v is at right angles to u and w + U.z * W.x - U.x * W.z, # it's the world's y axis + U.x * W.y - U.y * W.x) + + ve = V.x * ipos.x + V.y * ipos.y + V.z * ipos.z # project e onto v + + Projection(U, V, W, ue, ve, we, ipos, center, up, perspective) +end + +function project(proj::Projection, P::Point3D) + # use default value for perspectiveness if not specified + r = proj.W.x * P.x + proj.W.y * P.y + proj.W.z * P.z - proj.we + if r < eps() + # "point $P is behind eye" + result = nothing + else + if proj.perspective == 0.0 + depth = 1 + else + depth = proj.perspective * (1 / r) + end + uq = depth * (proj.U.x * P.x + proj.U.y * P.y + proj.U.z * P.z - proj.ue) + vq = depth * (proj.V.x * P.x + proj.V.y * P.y + proj.V.z * P.z - proj.ve) + result = Point(uq, -vq) # because Y is down the page in Luxor (?!) + end + return result +end + +function eyepoint(pt::Point3D) + return newprojection(pt, Point3D(0, 0, 0), Point3D(0, 0, 1)) +end + +function Luxor.distance(p1::Point3D, p2::Point3D) + sqrt((p2.x - p1.x)^2 + (p2.y - p1.y)^2 + (p2.z - p1.z)^2) +end + +end \ No newline at end of file diff --git a/lib/YaoPlots/src/YaoPlots.jl b/lib/YaoPlots/src/YaoPlots.jl index baa89090..4efb0113 100644 --- a/lib/YaoPlots/src/YaoPlots.jl +++ b/lib/YaoPlots/src/YaoPlots.jl @@ -4,9 +4,7 @@ using YaoBlocks using YaoBlocks.DocStringExtensions using YaoArrayRegister import Luxor -import Thebes using Luxor: @layer, Point -using Thebes: Point3D, project using LinearAlgebra: tr export CircuitStyles, vizcircuit, darktheme!, lighttheme! @@ -19,6 +17,8 @@ plot(blk::AbstractBlock; kwargs...) = vizcircuit(blk; kwargs...) include("helperblock.jl") include("vizcircuit.jl") +include("3d.jl") +using .Thebes: Point3D, project include("bloch.jl") end diff --git a/lib/YaoPlots/src/bloch.jl b/lib/YaoPlots/src/bloch.jl index a681bf7e..ec75fddf 100644 --- a/lib/YaoPlots/src/bloch.jl +++ b/lib/YaoPlots/src/bloch.jl @@ -37,7 +37,7 @@ module BlochStyles # generic config const lw = Ref(1.0) const textsize = Ref(16.0) - const fontfamily = Ref("monospace") + const fontfamily = Ref("JuliaMono") const background_color = Ref("transparent") const color = Ref("#000000") @@ -132,8 +132,6 @@ function bloch_sphere(states...; Luxor.fontsize(textsize) fontfamily !== nothing && Luxor.fontface(fontfamily) Luxor.setline(lw) - Thebes.eyepoint(eye_point...) - draw_bloch_sphere(states...; eye_point, extra_kwargs...) # Save the drawing to a file @@ -153,8 +151,10 @@ function draw_bloch_sphere(states::Pair{<:AbstractString}...; colors = fill(BlochStyles.color[], length(states)), extra_kwargs... ) + proj = Thebes.eyepoint(Point3D(eye_point...)) + # get coordinate of a state - getcoo(x) = Point3D(ball_size .* state_to_cartesian(x)) + getcoo(x) = Point3D((ball_size .* state_to_cartesian(x))...) # ball Luxor.circle(Point(0, 0), ball_size, :stroke) @@ -162,12 +162,12 @@ function draw_bloch_sphere(states::Pair{<:AbstractString}...; # equator nstep = 100 equator_points = map(LinRange(0, 2π*(1-1/nstep), nstep)) do ϕ - project(Point3D(ball_size .* polar_to_cartesian(1.0, π/2, ϕ))) + project(proj, Point3D((ball_size .* polar_to_cartesian(1.0, π/2, ϕ))...)) end Luxor.line.(equator_points[1:2:end], equator_points[2:2:end], :stroke) # show axes - axes3D(ball_size*3 ÷ 2; extra_kwargs...) + axes3D(ball_size*3 ÷ 2; proj, extra_kwargs...) # show 01 states if show01 @@ -178,7 +178,7 @@ function draw_bloch_sphere(states::Pair{<:AbstractString}...; if Thebes.distance(Point3D(0, 0, 0), Point3D(eye_point...)) < Thebes.distance(p, Point3D(eye_point...)) Luxor.setopacity(0.3) end - show_point(txt, project(p); dot_size, text_offset=Point(10, 0), show_line=false) + show_point(txt, project(proj, p); dot_size, text_offset=Point(10, 0), show_line=false) end end end @@ -191,32 +191,32 @@ function draw_bloch_sphere(states::Pair{<:AbstractString}...; if Thebes.distance(Point3D(0, 0, 0), Point3D(eye_point...)) < Thebes.distance(p, Point3D(eye_point...)) Luxor.setopacity(0.3) end - show_point(txt, project(p); dot_size, text_offset=Point(10, 0), show_line=show_line) + show_point(txt, project(proj, p); dot_size, text_offset=Point(10, 0), show_line=show_line) end if show_projection_lines # show θ ratio = 0.2 - sz = project(Point3D(0, 0, ball_size*ratio)) + sz = project(proj, Point3D(0, 0, ball_size*ratio)) if show_angle_texts Luxor.move(sz) - Luxor.arc2r(Point(0, 0), sz, project(p) * ratio, :stroke) + Luxor.arc2r(Point(0, 0), sz, project(proj, p) * ratio, :stroke) Luxor.text("θ", sz - Point(0, ball_size*0.07)) end # show equator projection and ϕ - equatorp = Point3D(p[1], p[2], 0) - sx = project(Point3D(ball_size*ratio, 0, 0)) + equatorp = Point3D(p.x, p.y, 0) + sx = project(proj, Point3D(ball_size*ratio, 0, 0)) if show_angle_texts Luxor.move(sx) - Luxor.carc2r(Point(0, 0), sx, project(equatorp) * ratio, :stroke) + Luxor.carc2r(Point(0, 0), sx, project(proj, equatorp) * ratio, :stroke) Luxor.text("ϕ", sx - Point(ball_size*0.12, 0)) end @layer begin Luxor.setdash("dot") Luxor.setline(1) - Luxor.line(project(p), project(equatorp), :stroke) - Luxor.line(Point(0, 0), project(equatorp), :stroke) + Luxor.line(project(proj, p), project(proj, equatorp), :stroke) + Luxor.line(Point(0, 0), project(proj, equatorp), :stroke) end end end @@ -253,6 +253,7 @@ state_to_cartesian(state) = polar_to_cartesian(state_to_polar(state)...) # Draw labelled 3D axes with length `n`. function axes3D(n::Int; + proj, axes_lw = BlochStyles.axes_lw[], axes_textsize = BlochStyles.textsize[], axes_colors = BlochStyles.axes_colors, @@ -262,10 +263,10 @@ function axes3D(n::Int; Luxor.fontsize(axes_textsize) Luxor.setline(axes_lw) for i = 1:3 - axis1 = project(Point3D(0.1, 0.1, 0.1)) + axis1 = project(proj, Point3D(0.1, 0.1, 0.1)) axis2 = [0.1, 0.1, 0.1] axis2[i] = n - axis2 = project(Point3D(axis2...)) + axis2 = project(proj, Point3D(axis2...)) Luxor.sethue(axes_colors[i]) if (axis1 !== nothing) && (axis2 !== nothing) && !isapprox(axis1, axis2) Luxor.arrow(axis1, axis2) diff --git a/lib/YaoPlots/src/vizcircuit.jl b/lib/YaoPlots/src/vizcircuit.jl index d4d28cb9..90e6a004 100644 --- a/lib/YaoPlots/src/vizcircuit.jl +++ b/lib/YaoPlots/src/vizcircuit.jl @@ -34,7 +34,7 @@ module CircuitStyles const lw = Ref(1.0) const textsize = Ref(16.0) const paramtextsize = Ref(10.0) - const fontfamily = Ref("monospace") + const fontfamily = Ref("JuliaMono") #const fontfamily = Ref("Dejavu Sans") const linecolor = Ref("#000000") const gate_bgcolor = Ref("transparent") @@ -421,7 +421,7 @@ get_brush_texts(c, ::ConstGate.P0Gate) = (c.gatestyles.g, "P₀") get_brush_texts(c, ::ConstGate.P1Gate) = (c.gatestyles.g, "P₁") get_brush_texts(c, b::PrimitiveBlock) = (c.gatestyles.g, string(b)) get_brush_texts(c, b::TimeEvolution) = (c.gatestyles.g, string(b)) -get_brush_texts(c, b::ShiftGate) = (c.gatestyles.g, "ϕ($(pretty_angle(b.theta)))") +get_brush_texts(c, b::ShiftGate) = (c.gatestyles.g, "φ($(pretty_angle(b.theta)))") get_brush_texts(c, b::PhaseGate) = (CircuitStyles.Phase("$(pretty_angle(b.theta))"), "") function get_brush_texts(c, b::T) where T<:ConstantGate namestr = string(T.name.name) @@ -462,7 +462,7 @@ They are defined as: * CircuitStyles.lw = Ref(1.0) # line width * CircuitStyles.textsize = Ref(16.0) # text size * CircuitStyles.paramtextsize = Ref(10.0) # text size (longer texts) -* CircuitStyles.fontfamily = Ref("monospace") # font family +* CircuitStyles.fontfamily = Ref("JuliaMono") # font family * CircuitStyles.linecolor = Ref("#000000") # line color * CircuitStyles.gate_bgcolor = Ref("transparent") # gate background color * CircuitStyles.textcolor = Ref("#000000") # text color diff --git a/lib/YaoPlots/test/vizcircuit.jl b/lib/YaoPlots/test/vizcircuit.jl index bf063aee..d1bf25ec 100644 --- a/lib/YaoPlots/test/vizcircuit.jl +++ b/lib/YaoPlots/test/vizcircuit.jl @@ -9,7 +9,7 @@ using Luxor c = YaoPlots.CircuitGrid(1) @test YaoPlots.get_brush_texts(c, X)[2] == "X" @test YaoPlots.get_brush_texts(c, Rx(0.5))[2] == "Rx(0.5)" - @test YaoPlots.get_brush_texts(c, shift(0.5))[2] == "ϕ(0.5)" + @test YaoPlots.get_brush_texts(c, shift(0.5))[2] == "φ(0.5)" @test YaoPlots.get_brush_texts(c, YaoBlocks.phase(0.5))[2] == "" end