├── TODO.md ├── _config.yml ├── docs ├── src │ ├── contents.md │ ├── function-index.md │ ├── cnot2-pop.png │ ├── JuQbox_favicon1.png │ ├── JuQbox_logo-inline-color.png │ ├── types.md │ ├── methods.md │ ├── index.md │ ├── installation.md │ ├── workflow.md │ └── examples.md └── make.jl ├── .gitignore ├── examples ├── cnot2-pop.png ├── drives │ ├── cnot3-pcof-opt.jld2 │ ├── cnot2-pcof-opt-t50.jld2 │ ├── rabi-pcof-opt-t100.jld2 │ ├── cnot2-pcof-opt-t100.jld2 │ ├── cnot2-pcof-opt-t200.jld2 │ └── cnot2.dat ├── cnot1-leakieq-setup.jl ├── cnot1-objthreshold-setup.jl ├── xgate-setup.jl ├── cnot1-setup.jl ├── flux-setup.jl ├── rabi-lab.jl ├── rabi-setup.jl ├── Risk_Neutral │ ├── swap-02-risk-neutral.jl │ └── Bimodal_Gaussian │ │ └── swap-02-risk-neutral.jl ├── cnot2-lab.jl └── swap2-setup.jl ├── test ├── reference_solutions │ ├── cnot2-ref.jld2 │ ├── cnot3-ref.jld2 │ ├── flux-ref.jld2 │ ├── rabi-ref.jld2 │ ├── err-mat-ref.jld2 │ ├── swap02-ref.jld2 │ ├── cnot-lab-ref.jld2 │ ├── cnot2-ref-imr.jld2 │ ├── cnot3-ref-imr.jld2 │ ├── flux-ref-imr.jld2 │ ├── rabi-ref-imr.jld2 │ ├── swap02-ref-imr.jld2 │ ├── cnot2-jacobi-ref.jld2 │ ├── err-mat-imr-ref.jld2 │ ├── cnot2-leakieq-ref.jld2 │ ├── cnot2-jacobi-ref-imr.jld2 │ └── cnot2-leakieq-ref-imr.jld2 ├── cases │ ├── rabi.dat │ ├── swap02.dat │ ├── refSol.jl │ ├── refSol_imr.jl │ ├── cnot2.dat │ ├── cnot2-jacobi.dat │ ├── cnot2-leakieq.dat │ ├── flux.dat │ ├── cnot-lab-setup.jl │ ├── cnot3.dat │ ├── swap02-setup.jl │ ├── flux-setup.jl │ ├── rabi-setup.jl │ └── cnot2-jacobi-setup.jl ├── runtests.jl ├── evalGrad.jl ├── test-stormer-verlet.jl └── test-implicit-midpoint.jl ├── .github └── workflows │ ├── Testing.yml │ └── Documentation.yml ├── Project.toml ├── LICENSE ├── NOTICE ├── src ├── backwards_compat.jl ├── Juqbox.jl ├── save_pcof.jl ├── ImplicitMidpoint.jl └── linear_solvers.jl └── README.md /TODO.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-leap-day -------------------------------------------------------------------------------- /docs/src/contents.md: -------------------------------------------------------------------------------- 1 | ```@contents 2 | ``` 3 | 4 | -------------------------------------------------------------------------------- /docs/src/function-index.md: -------------------------------------------------------------------------------- 1 | ```@index 2 | Modules = [Juqbox] 3 | ``` 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/build 2 | *.jl~ 3 | *.md~ 4 | .gitignore~ 5 | *.DS_Store* 6 | *.julia/ 7 | -------------------------------------------------------------------------------- /docs/src/cnot2-pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/docs/src/cnot2-pop.png -------------------------------------------------------------------------------- /examples/cnot2-pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/examples/cnot2-pop.png -------------------------------------------------------------------------------- /docs/src/JuQbox_favicon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/docs/src/JuQbox_favicon1.png -------------------------------------------------------------------------------- /examples/drives/cnot3-pcof-opt.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/examples/drives/cnot3-pcof-opt.jld2 -------------------------------------------------------------------------------- /docs/src/JuQbox_logo-inline-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/docs/src/JuQbox_logo-inline-color.png -------------------------------------------------------------------------------- /examples/drives/cnot2-pcof-opt-t50.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/examples/drives/cnot2-pcof-opt-t50.jld2 -------------------------------------------------------------------------------- /examples/drives/rabi-pcof-opt-t100.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/examples/drives/rabi-pcof-opt-t100.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot2-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot2-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot3-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot3-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/flux-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/flux-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/rabi-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/rabi-ref.jld2 -------------------------------------------------------------------------------- /examples/drives/cnot2-pcof-opt-t100.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/examples/drives/cnot2-pcof-opt-t100.jld2 -------------------------------------------------------------------------------- /examples/drives/cnot2-pcof-opt-t200.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/examples/drives/cnot2-pcof-opt-t200.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/err-mat-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/err-mat-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/swap02-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/swap02-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot-lab-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot-lab-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot2-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot2-ref-imr.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot3-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot3-ref-imr.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/flux-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/flux-ref-imr.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/rabi-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/rabi-ref-imr.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/swap02-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/swap02-ref-imr.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot2-jacobi-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot2-jacobi-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/err-mat-imr-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/err-mat-imr-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot2-leakieq-ref.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot2-leakieq-ref.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot2-jacobi-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot2-jacobi-ref-imr.jld2 -------------------------------------------------------------------------------- /test/reference_solutions/cnot2-leakieq-ref-imr.jld2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llnl/Juqbox.jl/HEAD/test/reference_solutions/cnot2-leakieq-ref-imr.jld2 -------------------------------------------------------------------------------- /docs/src/types.md: -------------------------------------------------------------------------------- 1 | The following types are exported and available by `using Juqbox`. 2 | ```@autodocs 3 | Modules = [Juqbox] 4 | Order = [:type] 5 | ``` 6 | -------------------------------------------------------------------------------- /test/cases/rabi.dat: -------------------------------------------------------------------------------- 1 | 3.0616169978684e-17 2 | 3.0616169978684e-17 3 | 3.0616169978684e-17 4 | 5.0000000000000e-01 5 | 5.0000000000000e-01 6 | 5.0000000000000e-01 7 | -------------------------------------------------------------------------------- /docs/src/methods.md: -------------------------------------------------------------------------------- 1 | The following methods (functions) are exported and available by `using Juqbox`. 2 | ```@autodocs 3 | Modules = [Juqbox] 4 | Order = [:function] 5 | ``` 6 | 7 | -------------------------------------------------------------------------------- /.github/workflows/Testing.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: '*' 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | version: ['1.10'] # Test against LTS and current release 16 | os: [macOS-latest, ubuntu-latest] #[macOS-latest, windows-latest, ubuntu-latest] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: julia-actions/setup-julia@latest 20 | with: 21 | version: ${{ matrix.version }} 22 | - uses: julia-actions/julia-buildpkg@v1 23 | - uses: julia-actions/julia-runtest@v1 24 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, DocumenterTools, Juqbox 2 | 3 | const ROOT = joinpath(@__DIR__, "..") 4 | 5 | 6 | 7 | makedocs( 8 | modules = [Juqbox], 9 | format = Documenter.HTML( 10 | prettyurls = get(ENV, "CI", nothing) == "true" 11 | ), 12 | #format = Documenter.LaTeX(platform = "docker"), # errors with no such file or directory??? 13 | #clean = false, 14 | sitename="Juqbox.jl", 15 | authors = "Anders Petersson, Fortino Garcia, and contributors.", 16 | pages = [ 17 | "Home" => "index.md", 18 | "Workflow" => "workflow.md", 19 | "Examples" => "examples.md", 20 | "Types" => "types.md", 21 | "Methods" => "methods.md", 22 | "Index" => "function-index.md", 23 | ], 24 | doctest = true, # set to true once initial implementation is done 25 | source = "src", 26 | ) 27 | 28 | deploydocs( 29 | repo = "github.com/LLNL/Juqbox.jl", 30 | target = "build", 31 | devurl="docs", 32 | ) 33 | -------------------------------------------------------------------------------- /test/cases/swap02.dat: -------------------------------------------------------------------------------- 1 | 1.4837848762241e-04 2 | -3.3615881123396e-04 3 | 8.9757335567520e-04 4 | 8.2169968293156e-04 5 | 2.4872085355615e-04 6 | -8.1798900667201e-04 7 | -8.5538205987285e-04 8 | 1.0248789904186e-03 9 | -8.5361237238390e-04 10 | 6.9641706797311e-04 11 | 5.7041991361149e-04 12 | 1.7994217278573e-04 13 | -6.5764466222682e-04 14 | 7.9926501491636e-04 15 | -1.9668694396613e-04 16 | 2.7778025955311e-04 17 | -2.1658188899196e-04 18 | 6.9887363132114e-04 19 | -4.4781283492775e-04 20 | -7.7820888082436e-04 21 | 6.0370471777760e-04 22 | 2.7111783340935e-04 23 | -8.5531936093071e-05 24 | 2.7672823118203e-04 25 | 6.1524568589541e-04 26 | -7.2201590868892e-05 27 | 4.9527735705072e-04 28 | -8.5848825645662e-04 29 | 3.5950810003561e-04 30 | -8.9255736334931e-04 31 | 2.8812251226007e-04 32 | -6.0786786671227e-04 33 | -7.5118707810675e-06 34 | -7.3221105718579e-05 35 | -9.7351412729130e-04 36 | -4.5646471744390e-05 37 | 8.7348216786145e-04 38 | 9.7041941411094e-04 39 | -8.0835408928313e-04 40 | -6.2590903954395e-04 -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "Juqbox" 2 | uuid = "d847742d-ef10-4feb-8f3c-cfc2faa2caf7" 3 | authors = ["Anders Petersson ", "Fortino Garcia ", "Chase Hodges-Heilmann " ] 4 | version = "0.3" 5 | 6 | [deps] 7 | DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" 8 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 9 | DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" 10 | FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" 11 | FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838" 12 | FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" 13 | Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" 14 | JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" 15 | LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" 16 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 17 | Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" 18 | Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" 19 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 20 | SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" 21 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 22 | -------------------------------------------------------------------------------- /test/cases/refSol.jl: -------------------------------------------------------------------------------- 1 | # This script can be used to generate reference solutions for new cases 2 | using DelimitedFiles 3 | using Printf 4 | import Juqbox 5 | 6 | include("evalGrad.jl") 7 | 8 | caseNames =["rabi", "swap02", "flux", "cnot-lab", "cnot2", "cnot3"] 9 | dirName = "reference_solutions" 10 | 11 | nCases = length(caseNames) 12 | pass = BitArray(undef, nCases) 13 | pass .= false 14 | 15 | q = 0 16 | for case in caseNames 17 | global q += 1 18 | 19 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 20 | println("Case: ", case) 21 | 22 | # setup the testcase (assign pcof0, params and wa) 23 | juliaFile = case * "-setup.jl" 24 | include(juliaFile); 25 | 26 | # evaluate fidelity & save reference sol 27 | refFile = dirName * "/" * case * "-ref.jld2" 28 | evalObjGrad(pcof0, params, wa, refFile, true) # true for saving a reference jld2 file 29 | 30 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 31 | 32 | end 33 | 34 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 35 | println(" TEST SUMMARY") 36 | 37 | for q in 1:nCases 38 | println("case=", caseNames[q], " pass=", pass[q]) 39 | end 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lawrence Livermore National Laboratory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | ![Logo](JuQbox_logo-inline-color.png) 2 | 3 | # 4 | 5 | Juqbox.jl is a package for solving quantum optimal control problems in closed quantum systems, where the evolution of the state vector is governed by Schroedinger's equation. 6 | 7 | The main features of Juqbox include 8 | - Symplectic time integration of Schroedinger's equation using the Stormer-Verlet scheme. 9 | - Efficient parameterization of the control functions via B-splines with carrier waves. 10 | - Objective function includes target gate infidelity and occupation of guarded (forbidden) states. 11 | - Exact computation of the gradient of the objective function by solving the discrete adjoint equation. 12 | 13 | The numerical methods in Juqbox.jl are documented in these papers: 14 | 1. N. A. Petersson and F. M. Garcia, "Optimal Control of Closed Quantum Systems via B-Splines with Carrier Waves", SIAM J. Sci. Comput. (2022) 44(6): A3592-A3616, LLNL-JRNL-823853, [arXiv:2106.14310](https://arxiv.org/abs/2106.14310). 15 | 2. N. A. Petersson, F. M. Garcia, A. E. Copeland, Y. L. Rydin and J. L. DuBois, “Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control”, LLNL-JRNL-800457, [arXiv:2001.01013](https://arxiv.org/abs/2001.01013). 16 | 17 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This work was produced under the auspices of the U.S. Department of 2 | Energy by Lawrence Livermore National Laboratory under Contract 3 | DE-AC52-07NA27344. LLNL-CODE-820043. 4 | 5 | This work was prepared as an account of work sponsored by an agency of 6 | the United States Government. Neither the United States Government nor 7 | Lawrence Livermore National Security, LLC, nor any of their employees 8 | makes any warranty, expressed or implied, or assumes any legal liability 9 | or responsibility for the accuracy, completeness, or usefulness of any 10 | information, apparatus, product, or process disclosed, or represents that 11 | its use would not infringe privately owned rights. 12 | 13 | Reference herein to any specific commercial product, process, or service 14 | by trade name, trademark, manufacturer, or otherwise does not necessarily 15 | constitute or imply its endorsement, recommendation, or favoring by the 16 | United States Government or Lawrence Livermore National Security, LLC. 17 | 18 | The views and opinions of authors expressed herein do not necessarily 19 | state or reflect those of the United States Government or Lawrence 20 | Livermore National Security, LLC, and shall not be used for advertising 21 | or product endorsement purposes. -------------------------------------------------------------------------------- /.github/workflows/Documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: '*' 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | version: ['1.10'] # Test against LTS and current release 16 | os: [macOS-latest, ubuntu-latest] #[macOS-latest, windows-latest, ubuntu-latest] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: julia-actions/setup-julia@latest 20 | with: 21 | version: ${{ matrix.version }} 22 | - name: Install dependencies, part 1 23 | env: 24 | PYTHON: 25 | run: julia --project=docs/ -e 'using Pkg; Pkg.add("PyCall"); Pkg.build("PyCall") ' 26 | - name: Checking environment 27 | run: | 28 | echo "PYTHON='"${PYTHON}"'" 29 | echo "CI='"${CI}"'" 30 | - name: Install dependencies, part 2 31 | run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate(); Pkg.add("Documenter"); Pkg.add("DocumenterTools"); Pkg.add("PyPlot") ' 32 | - name: Build and deploy 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token 35 | run: julia --color=yes --project=docs/ docs/make.jl 36 | -------------------------------------------------------------------------------- /test/cases/refSol_imr.jl: -------------------------------------------------------------------------------- 1 | # This script can be used to generate reference solutions for new cases for the Implicit Midpoint Rule 2 | using DelimitedFiles 3 | using Printf 4 | using LinearAlgebra 5 | import Juqbox 6 | using Random 7 | 8 | include("evalGrad.jl") 9 | 10 | caseNames =["rabi", "swap02", "flux", "cnot2", "cnot3", "cnot2-leakieq", "cnot2-jacobi"] 11 | dirName = "reference_solutions" 12 | 13 | nCases = length(caseNames) 14 | pass = BitArray(undef, nCases) 15 | pass .= false 16 | 17 | q = 0 18 | for case in caseNames 19 | global q += 1 20 | 21 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 22 | println("Case: ", case) 23 | 24 | # setup the testcase (assign pcof0, params and wa) 25 | juliaFile = "cases" * "/" * case * "-setup.jl" 26 | include(juliaFile); 27 | 28 | params.Integrator_id = 2 29 | params.linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 30 | 31 | local wa = Juqbox.Working_Arrays_M(params, nCoeff) 32 | 33 | # evaluate fidelity & save reference sol 34 | refFile = dirName * "/" * case * "-ref-imr.jld2" 35 | evalObjGrad(pcof0, params, wa, refFile, true) # true for saving a reference jld2 file 36 | 37 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 38 | 39 | end 40 | 41 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 42 | println(" TEST SUMMARY") 43 | 44 | for q in 1:nCases 45 | println("case=", caseNames[q], " pass=", pass[q]) 46 | end 47 | -------------------------------------------------------------------------------- /src/backwards_compat.jl: -------------------------------------------------------------------------------- 1 | # This version assumes full matrices. Compute C-> α*A*B + β*C 2 | @inline function mul!(C::Array{Float64,M}, A::Array{Float64,N}, B::Array{Float64,M}, α::Float64, β::Float64) where {M,N} 3 | C .*= β 4 | szB1 = size(B,1) 5 | szB2 = size(B,2) 6 | szC2 = size(C,2) 7 | szC1 = size(C,1) 8 | for j=1:szC2 9 | offset = (j-1)*szB1 10 | offset2 = (j-1)*szC1 11 | for i=1:szC1 12 | tmp = 0.0 13 | for k=1:szB1 14 | ind2 = offset + k 15 | tmp += A[i,k]*B[ind2] 16 | end 17 | ind = offset2 + i 18 | C[ind] += α*tmp 19 | end 20 | end 21 | end 22 | 23 | # This version assumes A is a sparse matrix. Compute C-> α*A*B + β*C 24 | @inline function mul!(C::Array{Float64,M}, A::SparseMatrixCSC{Float64,Int64}, B::Array{Float64,M}, α::Float64, β::Float64) where {M} 25 | C .*= β 26 | szB1 = size(B,1) 27 | szB2 = size(B,2) 28 | szA2 = size(A,2) 29 | szC1 = size(C,1) 30 | csc_ind = 1 31 | for j=1:szA2 32 | offset = (j-1)*szB1 33 | # offset2 = (j-1)*szC1 34 | start = A.colptr[j] 35 | stop = A.colptr[j+1] 36 | len = stop-start 37 | for i=1:len 38 | row = A.rowval[csc_ind] 39 | val = A.nzval[csc_ind] 40 | for k=1:szB2 41 | ind = (k-1)*szC1 + row 42 | ind2 = (k-1)*szB1 + j 43 | C[ind] += α*val*B[ind2] 44 | end 45 | csc_ind += 1 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /docs/src/installation.md: -------------------------------------------------------------------------------- 1 | The following instructions assume that you have already installed Julia (currently version 1.8.5) on your system. Before proceeding, we recommend that you add the following to the file ~/.julia/config/startup.jl. You may have to first create the config folder under .julia in your home directory. Then add this line to the startup.jl file: 2 | 3 | - **ENV["PLOTS_DEFAULT_BACKEND"]="GR"** 4 | 5 | This is an environment variable. It specifies the backend for plotting. Most of the examples in this document uses the GR backend, which assumes that you have installed that package. If you have trouble with GR, you can instead install the "PyPlot" package and set the default backend to "PyPlot". 6 | 7 | Start julia and type `]` to enter the package manager. Then do: 8 | - (@v1.8) pkg> add https://github.com/LLNL/Juqbox.jl 9 | - (@v1.8) pkg> precompile 10 | - (@v1.8) pkg> test Juqbox 11 | - ... all tests should pass (case=flux gives a Warning message) ... 12 | 13 | To exit the package manager you type ``, and to exit julia you type `exit()`. 14 | 15 | To access the examples, clone the Juqbox.jl git repository: 16 | - shell> git clone https://github.com/LLNL/Juqbox.jl.git 17 | 18 | Then go to the examples directory in the Juqbox.jl folder (on some systems the folder is named juqbox.jl): 19 | - shell> cd Juqbox.jl/examples 20 | 21 | Start julia and try the `cnot1-setup.jl' test case: 22 | - shell> julia 23 | - julia> include("cnot1-setup.jl") 24 | - julia> pcof = run_optimizer(prob,pcof0); 25 | - julia> pl = plot_results(params,pcof); 26 | - julia> pl[1] 27 | 28 | See the workflow section for further instructions. -------------------------------------------------------------------------------- /src/Juqbox.jl: -------------------------------------------------------------------------------- 1 | module Juqbox 2 | 3 | using LinearAlgebra 4 | using Plots 5 | #pyplot() 6 | using Printf 7 | using Random 8 | using LaTeXStrings 9 | using SparseArrays 10 | using Ipopt 11 | using FileIO 12 | using FFTW 13 | using DelimitedFiles 14 | 15 | 16 | export splineparams, bspline2, gradbspline2 17 | export bcparams, bcarrier2, gradbcarrier2! 18 | 19 | export objparams, traceobjgrad, traceobjgrad_m, identify_guard_levels, identify_forbidden_levels, initial_cond 20 | export plotunitary, plotspecified, evalctrl, plot_results, plot_energy, plot_final_unitary 21 | export setup_ipopt_problem, Working_Arrays, WorkingArrays_M, estimate_Neumann!, setup_rotmatrices 22 | export run_optimizer, plot_conv_hist, wmatsetup, orig_wmatsetup 23 | export zero_start_end!, assign_thresholds, assign_thresholds_freq, assign_thresholds_ctrl_freq 24 | export calculate_timestep, marginalize3, change_target!, set_adjoint_Sv_type! 25 | export save_pcof, read_pcof, juq2qis 26 | export lsolver_object 27 | 28 | # Julia versions prior to v"1.3.1" can't use LinearAlgebra's 5 argument mul!, routines 29 | # included here for backwards compatability 30 | if(VERSION < v"1.3.1") 31 | include("backwards_compat.jl") 32 | end 33 | 34 | include("bsplines.jl") # add all B-spline functionality to the Juqbox module 35 | 36 | include("linear_solvers.jl") 37 | 38 | include("StormerVerlet.jl") # add in time-stepping functionality 39 | 40 | include("ImplicitMidpoint.jl") # add in time-stepping functionality for Implicit Midpoint 41 | 42 | # union type for different ctrl parameterizations 43 | BsplineParams = Union{splineparams, bcparams} 44 | # union type for different matrix representations 45 | MyRealMatrix = Union{Array{Float64,2}, SparseMatrixCSC{Float64, Int64}} 46 | 47 | WeightMatrix = Union{Array{Float64,2}, Diagonal{Float64, Vector{Float64}}} 48 | 49 | include("evalobjgrad.jl") 50 | 51 | include("plotstatectrl.jl") 52 | 53 | include("plot-results.jl") 54 | 55 | include("ipopt_interface.jl") 56 | 57 | include("save_pcof.jl") 58 | 59 | end # module 60 | -------------------------------------------------------------------------------- /test/cases/cnot2.dat: -------------------------------------------------------------------------------- 1 | 1.1431222409309e-04 2 | 6.7574947592860e-05 3 | 1.8657771899593e-04 4 | 1.7925913107612e-04 5 | 1.2399100199606e-04 6 | 2.1098791632315e-05 7 | 1.7491949660066e-05 8 | 1.9885730751282e-04 9 | 1.7662649364062e-05 10 | 1.6717467807363e-04 11 | 1.5502130235716e-04 12 | 1.1735677955730e-04 13 | 3.6565212853698e-05 14 | 1.7709513816022e-04 15 | 8.1028060985547e-05 16 | 1.2679400084923e-04 17 | 7.9109043504697e-05 18 | 1.6741163213415e-04 19 | 5.6805074994700e-05 20 | 2.4935884762904e-05 21 | 1.5823187272861e-04 22 | 1.2615135960452e-04 23 | 9.1749799744586e-05 24 | 1.2669252477921e-04 25 | 1.5934508613710e-04 26 | 9.3035612069162e-05 27 | 1.4777323610673e-04 28 | 1.7192333574900e-05 29 | 1.3467726739530e-04 30 | 1.3906111290842e-05 31 | 1.2779158911651e-04 32 | 4.1366560161223e-05 33 | 9.9275423414137e-05 34 | 9.2937272173475e-05 35 | 6.0972208919985e-06 36 | 9.5597053567985e-05 37 | 1.8425394225320e-04 38 | 1.9360427068370e-04 39 | 2.2028152073965e-05 40 | 3.9626352988291e-05 41 | 1.4142971126649e-04 42 | 1.0894245056438e-05 43 | 1.6420528038324e-04 44 | 1.4296623010331e-04 45 | 6.6728575449902e-05 46 | 1.7281698090378e-04 47 | 2.4018870982848e-05 48 | 3.0907576037874e-05 49 | 1.8403828680843e-04 50 | 1.7178284691585e-04 51 | 1.8505956462031e-04 52 | 6.0917299703134e-05 53 | 1.7725281283346e-04 54 | 1.1564219473080e-04 55 | 1.2017878535046e-04 56 | 4.4994665644266e-05 57 | 1.3101741479350e-04 58 | 9.8165722673614e-06 59 | 1.1943012403599e-04 60 | 1.5868040300883e-04 61 | 3.1888419070354e-06 62 | 1.0826230228444e-04 63 | 1.2900037310461e-04 64 | 1.0104721089822e-05 65 | 1.1310521275927e-04 66 | 8.1139864204804e-06 67 | 6.7179270956015e-05 68 | 1.0871954157089e-04 69 | 1.9832213167305e-04 70 | 2.5604406590100e-05 71 | 1.7733567737033e-04 72 | 1.5624906710464e-04 73 | 2.9644963472534e-05 74 | 5.3822904280792e-05 75 | 6.3085663948347e-05 76 | 1.1117306772198e-04 77 | 1.0143346291514e-04 78 | 4.3009423961125e-05 79 | 3.2813604603156e-05 80 | 2.7316542633062e-05 81 | -------------------------------------------------------------------------------- /examples/drives/cnot2.dat: -------------------------------------------------------------------------------- 1 | 1.1431222409309e-04 2 | 6.7574947592860e-05 3 | 1.8657771899593e-04 4 | 1.7925913107612e-04 5 | 1.2399100199606e-04 6 | 2.1098791632315e-05 7 | 1.7491949660066e-05 8 | 1.9885730751282e-04 9 | 1.7662649364062e-05 10 | 1.6717467807363e-04 11 | 1.5502130235716e-04 12 | 1.1735677955730e-04 13 | 3.6565212853698e-05 14 | 1.7709513816022e-04 15 | 8.1028060985547e-05 16 | 1.2679400084923e-04 17 | 7.9109043504697e-05 18 | 1.6741163213415e-04 19 | 5.6805074994700e-05 20 | 2.4935884762904e-05 21 | 1.5823187272861e-04 22 | 1.2615135960452e-04 23 | 9.1749799744586e-05 24 | 1.2669252477921e-04 25 | 1.5934508613710e-04 26 | 9.3035612069162e-05 27 | 1.4777323610673e-04 28 | 1.7192333574900e-05 29 | 1.3467726739530e-04 30 | 1.3906111290842e-05 31 | 1.2779158911651e-04 32 | 4.1366560161223e-05 33 | 9.9275423414137e-05 34 | 9.2937272173475e-05 35 | 6.0972208919985e-06 36 | 9.5597053567985e-05 37 | 1.8425394225320e-04 38 | 1.9360427068370e-04 39 | 2.2028152073965e-05 40 | 3.9626352988291e-05 41 | 1.4142971126649e-04 42 | 1.0894245056438e-05 43 | 1.6420528038324e-04 44 | 1.4296623010331e-04 45 | 6.6728575449902e-05 46 | 1.7281698090378e-04 47 | 2.4018870982848e-05 48 | 3.0907576037874e-05 49 | 1.8403828680843e-04 50 | 1.7178284691585e-04 51 | 1.8505956462031e-04 52 | 6.0917299703134e-05 53 | 1.7725281283346e-04 54 | 1.1564219473080e-04 55 | 1.2017878535046e-04 56 | 4.4994665644266e-05 57 | 1.3101741479350e-04 58 | 9.8165722673614e-06 59 | 1.1943012403599e-04 60 | 1.5868040300883e-04 61 | 3.1888419070354e-06 62 | 1.0826230228444e-04 63 | 1.2900037310461e-04 64 | 1.0104721089822e-05 65 | 1.1310521275927e-04 66 | 8.1139864204804e-06 67 | 6.7179270956015e-05 68 | 1.0871954157089e-04 69 | 1.9832213167305e-04 70 | 2.5604406590100e-05 71 | 1.7733567737033e-04 72 | 1.5624906710464e-04 73 | 2.9644963472534e-05 74 | 5.3822904280792e-05 75 | 6.3085663948347e-05 76 | 1.1117306772198e-04 77 | 1.0143346291514e-04 78 | 4.3009423961125e-05 79 | 3.2813604603156e-05 80 | 2.7316542633062e-05 81 | -------------------------------------------------------------------------------- /test/cases/cnot2-jacobi.dat: -------------------------------------------------------------------------------- 1 | 1.1431222409309e-04 2 | 6.7574947592860e-05 3 | 1.8657771899593e-04 4 | 1.7925913107612e-04 5 | 1.2399100199606e-04 6 | 2.1098791632315e-05 7 | 1.7491949660066e-05 8 | 1.9885730751282e-04 9 | 1.7662649364062e-05 10 | 1.6717467807363e-04 11 | 1.5502130235716e-04 12 | 1.1735677955730e-04 13 | 3.6565212853698e-05 14 | 1.7709513816022e-04 15 | 8.1028060985547e-05 16 | 1.2679400084923e-04 17 | 7.9109043504697e-05 18 | 1.6741163213415e-04 19 | 5.6805074994700e-05 20 | 2.4935884762904e-05 21 | 1.5823187272861e-04 22 | 1.2615135960452e-04 23 | 9.1749799744586e-05 24 | 1.2669252477921e-04 25 | 1.5934508613710e-04 26 | 9.3035612069162e-05 27 | 1.4777323610673e-04 28 | 1.7192333574900e-05 29 | 1.3467726739530e-04 30 | 1.3906111290842e-05 31 | 1.2779158911651e-04 32 | 4.1366560161223e-05 33 | 9.9275423414137e-05 34 | 9.2937272173475e-05 35 | 6.0972208919985e-06 36 | 9.5597053567985e-05 37 | 1.8425394225320e-04 38 | 1.9360427068370e-04 39 | 2.2028152073965e-05 40 | 3.9626352988291e-05 41 | 1.4142971126649e-04 42 | 1.0894245056438e-05 43 | 1.6420528038324e-04 44 | 1.4296623010331e-04 45 | 6.6728575449902e-05 46 | 1.7281698090378e-04 47 | 2.4018870982848e-05 48 | 3.0907576037874e-05 49 | 1.8403828680843e-04 50 | 1.7178284691585e-04 51 | 1.8505956462031e-04 52 | 6.0917299703134e-05 53 | 1.7725281283346e-04 54 | 1.1564219473080e-04 55 | 1.2017878535046e-04 56 | 4.4994665644266e-05 57 | 1.3101741479350e-04 58 | 9.8165722673614e-06 59 | 1.1943012403599e-04 60 | 1.5868040300883e-04 61 | 3.1888419070354e-06 62 | 1.0826230228444e-04 63 | 1.2900037310461e-04 64 | 1.0104721089822e-05 65 | 1.1310521275927e-04 66 | 8.1139864204804e-06 67 | 6.7179270956015e-05 68 | 1.0871954157089e-04 69 | 1.9832213167305e-04 70 | 2.5604406590100e-05 71 | 1.7733567737033e-04 72 | 1.5624906710464e-04 73 | 2.9644963472534e-05 74 | 5.3822904280792e-05 75 | 6.3085663948347e-05 76 | 1.1117306772198e-04 77 | 1.0143346291514e-04 78 | 4.3009423961125e-05 79 | 3.2813604603156e-05 80 | 2.7316542633062e-05 81 | -------------------------------------------------------------------------------- /test/cases/cnot2-leakieq.dat: -------------------------------------------------------------------------------- 1 | 1.1431222409309e-04 2 | 6.7574947592860e-05 3 | 1.8657771899593e-04 4 | 1.7925913107612e-04 5 | 1.2399100199606e-04 6 | 2.1098791632315e-05 7 | 1.7491949660066e-05 8 | 1.9885730751282e-04 9 | 1.7662649364062e-05 10 | 1.6717467807363e-04 11 | 1.5502130235716e-04 12 | 1.1735677955730e-04 13 | 3.6565212853698e-05 14 | 1.7709513816022e-04 15 | 8.1028060985547e-05 16 | 1.2679400084923e-04 17 | 7.9109043504697e-05 18 | 1.6741163213415e-04 19 | 5.6805074994700e-05 20 | 2.4935884762904e-05 21 | 1.5823187272861e-04 22 | 1.2615135960452e-04 23 | 9.1749799744586e-05 24 | 1.2669252477921e-04 25 | 1.5934508613710e-04 26 | 9.3035612069162e-05 27 | 1.4777323610673e-04 28 | 1.7192333574900e-05 29 | 1.3467726739530e-04 30 | 1.3906111290842e-05 31 | 1.2779158911651e-04 32 | 4.1366560161223e-05 33 | 9.9275423414137e-05 34 | 9.2937272173475e-05 35 | 6.0972208919985e-06 36 | 9.5597053567985e-05 37 | 1.8425394225320e-04 38 | 1.9360427068370e-04 39 | 2.2028152073965e-05 40 | 3.9626352988291e-05 41 | 1.4142971126649e-04 42 | 1.0894245056438e-05 43 | 1.6420528038324e-04 44 | 1.4296623010331e-04 45 | 6.6728575449902e-05 46 | 1.7281698090378e-04 47 | 2.4018870982848e-05 48 | 3.0907576037874e-05 49 | 1.8403828680843e-04 50 | 1.7178284691585e-04 51 | 1.8505956462031e-04 52 | 6.0917299703134e-05 53 | 1.7725281283346e-04 54 | 1.1564219473080e-04 55 | 1.2017878535046e-04 56 | 4.4994665644266e-05 57 | 1.3101741479350e-04 58 | 9.8165722673614e-06 59 | 1.1943012403599e-04 60 | 1.5868040300883e-04 61 | 3.1888419070354e-06 62 | 1.0826230228444e-04 63 | 1.2900037310461e-04 64 | 1.0104721089822e-05 65 | 1.1310521275927e-04 66 | 8.1139864204804e-06 67 | 6.7179270956015e-05 68 | 1.0871954157089e-04 69 | 1.9832213167305e-04 70 | 2.5604406590100e-05 71 | 1.7733567737033e-04 72 | 1.5624906710464e-04 73 | 2.9644963472534e-05 74 | 5.3822904280792e-05 75 | 6.3085663948347e-05 76 | 1.1117306772198e-04 77 | 1.0143346291514e-04 78 | 4.3009423961125e-05 79 | 3.2813604603156e-05 80 | 2.7316542633062e-05 81 | -------------------------------------------------------------------------------- /docs/src/workflow.md: -------------------------------------------------------------------------------- 1 | The work flow for solving a quantum optimal control problem consists of the following general steps: 2 | 1. Setup 3 | 2. Optimize 4 | 3. Visualize the results 5 | 6 | ## 1. Setup 7 | The setup phase includes specifying 8 | - The size of the state vector 9 | - The system and control Hamiltonians 10 | - The target gate transformation 11 | - Duration of the gate and number of time steps for integrating Schroedinger's equation. 12 | For the parameterization of the control functions, you need to specify 13 | - Carrier wave frequencies 14 | - Number of B-spline coefficients in each spline 15 | 16 | The properties of the control problem are stored in a `mutable struct` that is populated by calling 17 | - `params = Juqbox.objparams()`. 18 | 19 | The next steps are: 20 | - Assign the initial parameter vector (called `pcof0` in the examples below) 21 | - Set bounds for the parameter vector to be imposed during the optimization 22 | - Allocate working arrays by calling `wa = Juqbox.Working_arrays()` 23 | - Assign convergence criteria and other parameters for the optimizer 24 | - Build the optimization structure by calling `prob = Juqbox.setup_ipopt_problem()` 25 | 26 | ## 2. Optimization 27 | Once you have been assigned the `params` and `prob` objects, as well as the initial parameter vector 28 | `pcof0`, the optimizer is invoked by 29 | - `pcof = Juqbox.run_optimizer(prob, pcof0)` 30 | 31 | ## 3. Visualizing the results 32 | General properties of the optimized solution such as trace infidelity and unitary accuracy can be evaluated, 33 | and a number of figures can generated by invoking 34 | - `pl = Juqbox.plot_results(params, pcof)` 35 | 36 | An array of Julia plot objects is returned in `pl`. These objects can be visualized on the screen 37 | - `display(pl[1])` 38 | where `pl[1]` is the first Julia plot object. The following plot objects are populated by the script: 39 | - `pl[1]` Evolution of the state vector population 40 | - `pl[2]` Control functions in the rotating frame of reference 41 | - `pl[3]` Population of forbidden energy levels 42 | - `pl[4]` Lab frame control function(s) 43 | - `pl[5]` Fourier transform of the lab-frame control functions (linear scale) 44 | - `pl[6]` Fourier transform of the lab-frame control functions (log scale) 45 | - `pl[7]` Coefficients of the optimized parameter vector 46 | - `pl[8]` Convergence of the optimization 47 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using Test 2 | using LinearAlgebra 3 | using Printf 4 | using Random 5 | 6 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 7 | 8 | # include("../src/Juqbox.jl") 9 | import Juqbox # quantum control module 10 | 11 | include("evalGrad.jl") 12 | include("test-stormer-verlet.jl") 13 | include("test-implicit-midpoint.jl") 14 | 15 | refDir = "reference_solutions" 16 | 17 | # test Stormer-Verlet time stepping 18 | refFile = refDir * "/" * "err-mat-ref.jld2" 19 | success = timestep_convergence(refFile) 20 | println("Stormer-Verlet time stepping test: pass=", success) 21 | @test success 22 | 23 | #test Implicit Midpoint Time stepping 24 | refFile_imr = refDir * "/" * "err-mat-imr-ref.jld2" 25 | success_imr = timestep_convergence_implicit(refFile_imr) 26 | println("Implicit Midpoint time stepping test: pass=", success_imr) 27 | @test success_imr 28 | 29 | # test traceobjgrad for various setups 30 | caseNames =["rabi", "swap02", "flux", "cnot2", "cnot3", "cnot2-leakieq", "cnot2-jacobi"] 31 | caseDir = "cases" 32 | nCases = length(caseNames) 33 | pass = BitArray(undef, nCases) 34 | pass .= false 35 | 36 | q = 0 37 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 38 | println("Testing Stormer Verlet adjoint method") 39 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 40 | for case in caseNames 41 | global q += 1 42 | 43 | # setup the testcase (assign pcof0, params and wa 44 | juliaFile = caseDir * "/" * case * "-setup.jl" 45 | include(juliaFile); 46 | 47 | # evaluate fidelity 48 | caseRefFile = refDir * "/" * case * "-ref.jld2" 49 | pass[q] = evalObjGrad(pcof0, params, wa, caseRefFile) 50 | 51 | println("case=", case, " pass=", pass[q]) 52 | @test pass[q] 53 | 54 | end 55 | pass .= false 56 | p = 0 57 | 58 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 59 | println("Testing Implicit Midpoint Rule adjoint method") 60 | println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") 61 | for case in caseNames 62 | global p += 1 63 | 64 | # setup the testcase (assign pcof0, params and wa 65 | juliaFile = caseDir * "/" * case * "-setup.jl" 66 | include(juliaFile); 67 | 68 | params.Integrator_id = 2 69 | params.linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 70 | 71 | local wa = Juqbox.Working_Arrays_M(params, nCoeff) 72 | 73 | # evaluate fidelity 74 | caseRefFile = refDir * "/" * case * "-ref-imr.jld2" 75 | pass[p] = evalObjGrad(pcof0, params, wa, caseRefFile) 76 | 77 | println("case=", case, " pass=", pass[p]) 78 | @test pass[p] 79 | 80 | end -------------------------------------------------------------------------------- /test/evalGrad.jl: -------------------------------------------------------------------------------- 1 | using FileIO 2 | 3 | function evalObjGrad( pcof0:: Array{Float64, 1}, params:: Juqbox.objparams, wa, refFileName:: String, writeFile:: Bool=false) 4 | rtol = 1e-10 5 | atol = 1e-14 6 | verbose = false 7 | 8 | # return flag 9 | success=false 10 | 11 | # evaluate fidelity 12 | nCoeff = length(pcof0) 13 | grad = zeros(nCoeff) 14 | objv = Juqbox.eval_f_par(pcof0,params, wa, [0.0], [1.0]); # Use these functions to compute obj 15 | Juqbox.eval_grad_f_par(pcof0,grad,params, wa, [0.0], [1.0]) # and grad since they now include the tikhonov terms 16 | 17 | if params.objFuncType != 1 18 | leakage = params.last_leak 19 | leak_grad = zeros(nCoeff) 20 | tmp = zeros(Int32,0) 21 | Juqbox.eval_jac_g_par(pcof0, tmp, tmp,leak_grad,params,wa) 22 | 23 | objv = [objv, leakage] 24 | grad = vcat(grad,leak_grad) 25 | end 26 | 27 | # objv = Juqbox.eval_f_par(pcof0,true,params, wa, [0.0], [1.0]); # Use these functions to compute obj 28 | # Juqbox.eval_grad_f_par(pcof0,false,grad,params, wa, [0.0], [1.0]) # and grad since they now include the tikhonov terms 29 | 30 | if writeFile 31 | save(refFileName, "obj0", objv, "grad0", grad) 32 | success=true 33 | else 34 | # read reference solution 35 | # pcofRef = vec(readdlm(refFileName)) # change to JLD2 file to get full accuracy 36 | # objvRef = pcofRef[1] 37 | # gradRef = pcofRef[2:end] 38 | dict = load(refFileName) 39 | objvRef = dict["obj0"] 40 | gradRef = dict["grad0"] 41 | 42 | if length(objv) == 1 43 | objDiff = abs(objv - objvRef) 44 | else 45 | objDiff = norm(objv - objvRef) 46 | end 47 | 48 | refNorm = norm(gradRef) 49 | aNorm = norm(grad-gradRef) 50 | if verbose 51 | println("rel objfunc error =", objDiff, " absolute grad error =", aNorm, " relative grad error =", aNorm/refNorm) 52 | end 53 | pass1 = false 54 | 55 | 56 | if objDiff < atol 57 | pass1 = true 58 | elseif abs(objvRef) >= atol && objDiff/abs(objvRef) < rtol 59 | pass1 = true 60 | end 61 | 62 | pass2 = false 63 | if aNorm < atol 64 | pass2 = true 65 | elseif refNorm >= atol && aNorm/refNorm < rtol 66 | pass2 = true 67 | end 68 | 69 | success = pass1 && pass2 70 | 71 | if !success 72 | println("rel objfunc error =", objDiff, " absolute grad error =", aNorm, " relative grad error =", aNorm/refNorm) 73 | end 74 | 75 | end 76 | 77 | return success 78 | end 79 | -------------------------------------------------------------------------------- /src/save_pcof.jl: -------------------------------------------------------------------------------- 1 | using FileIO 2 | 3 | """ 4 | save_pcof(refFileName, pcof) 5 | 6 | Save the parameter vector `pcof` on a JLD2 formatted file with handle `pcof` 7 | 8 | # Arguments 9 | - `refFileName`: String holding the name of the file. 10 | - `pcof`: Vector of floats holding the parameters. 11 | """ 12 | function save_pcof(refFileName:: String, pcof:: Vector{Float64}) 13 | save(refFileName, "pcof", pcof) 14 | end 15 | 16 | """ 17 | pcof = read_pcof(refFileName) 18 | 19 | Read the parameter vector `pcof` from a JLD2 formatted file 20 | 21 | # Arguments 22 | - `refFileName`: String holding the name of the file. 23 | """ 24 | function read_pcof(refFileName:: String) 25 | dict = load(refFileName) 26 | pcof = dict["pcof"] 27 | return pcof 28 | end 29 | 30 | """ 31 | juq2qis(params, pcof, samplerate, q_ind, fileName="ctrl.dat", node_loc="c") 32 | 33 | Evaluate control functions and export them into a format that is readable by Qiskit. 34 | 35 | # Arguments 36 | - `params:: objparams`: Struct with problem definition 37 | - `pcof:: Array{Float64,1})`: Vector of parameter values 38 | - `samplerate:: Float64`: Samplerate for quantum device (number of samples per ns for the IQ mixer) 39 | - `q_ind:: Int64`: Index of the control function to output (`1 <= q_ind <= Nctrl*Nfreq`) 40 | - `fileName:: String`: Name of output file containing controls to be handled by Qiskit 41 | - `node_loc:: String`: Node location, "c" for cell centered, "n" for node-centered, default is cell-centered 42 | """ 43 | function juq2qis(params::objparams, pcof:: Array{Float64, 1}, samplerate:: Float64, q_ind:: Int64, fileName:: String="ctrl.dat", node_loc:: String="c") 44 | 45 | Nctrl = params.Ncoupled + params.Nunc 46 | Nfreq = params.Nfreq 47 | 48 | qiskit_dt = 1/samplerate 49 | @assert(q_ind >=1) 50 | @assert(q_ind <= Nctrl*Nfreq) 51 | 52 | if params.T < qiskit_dt 53 | throw(ArgumentError("Final simulation time shorter than qiskit_dt, please increase final simulation time.\n")) 54 | end 55 | 56 | @assert( params.Ncoupled == 0 || params.Nunc == 0) # can't have both 57 | 58 | # Check if we have an integer number of timesteps of size qiskit_dt in our signal 59 | # FG: This can be a problem if the width of a B-spline is small enough as 60 | # the qiskit_dt could ask for data outside of the control function definition 61 | # if we don't take value from left. 62 | nsteps = round(Int64,params.T/qiskit_dt) 63 | rmdr = params.T-qiskit_dt*nsteps 64 | println("Number of ctrl samples = ", nsteps) 65 | if rmdr > 1e-10 66 | throw(ArgumentError("Non-integral number of timesteps of size qiskit_dt, adjust final simulation time.\n")) 67 | end 68 | 69 | # Take midpoint value of signal in interval [(k-1)*qiskit_dt,k*qiskit_dt] ∀ k 70 | if(node_loc=="c") 71 | td = collect((0:nsteps-1).*qiskit_dt) 72 | td .+= 0.5*qiskit_dt # Use the ctrl value at the center of each interval 73 | else 74 | td = collect((0:nsteps-1).*qiskit_dt) # Use the ctrl value at the beginning of each interval 75 | end 76 | D = zeros(length(td),2) 77 | 78 | # output the B-splines without carrier waves 79 | bc_flag = params.use_bcarrier 80 | params.use_bcarrier = false 81 | 82 | # Controls (in rad/ns) 83 | p,q = Juqbox.evalctrl(params, pcof, td, q_ind) # evalctrl uses a 1-based indexing of q_ind 84 | D[:,1] = copy(p) 85 | D[:,2] = copy(q) 86 | 87 | println("Making a plot of the ctrl functions") 88 | plc = plot(td,p,lab="p(t)") 89 | plot!(td,q,lab="q(t)") 90 | 91 | # reset the bcarrier flag 92 | params.use_bcarrier = bc_flag 93 | 94 | # Save signal to delimited file 95 | if length(fileName)>0 96 | open(fileName, "w") do io 97 | writedlm(io, D) 98 | end 99 | println("Saved control signal in Qiskit compatible format to delimited file '", fileName, "'"); 100 | end 101 | 102 | return plc 103 | end 104 | -------------------------------------------------------------------------------- /test/test-stormer-verlet.jl: -------------------------------------------------------------------------------- 1 | using LinearAlgebra 2 | using JLD2 3 | using Juqbox 4 | using Printf 5 | #Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 6 | 7 | function timesteptest( cfl = 0.1, testcase = 2, order = 2, verbose = false) 8 | 9 | N = 2 # vector dimension 10 | IN = Matrix{Float64}(I, N, N) 11 | 12 | if testcase == 1 || testcase == 2 13 | K0 = [0 1; 1 0] 14 | S0 = [0 0; 0 0] 15 | elseif testcase == 0 || testcase == 3 16 | K0 = [0 0; 0 0] 17 | S0 = [0 1; -1 0] 18 | end 19 | 20 | #Final time 21 | period = 1 22 | T = 5pi 23 | omega = 2*pi/period 24 | 25 | lamb = eigvals(K0 .+ S0) 26 | maxeig = maximum(abs.(lamb)) 27 | 28 | #time step 29 | dt = cfl/maxeig 30 | nsteps = ceil(Int64,T/dt) 31 | dt = T/nsteps 32 | 33 | if verbose 34 | println("Testcase: ", testcase, " Cfl: ", cfl, " Final time: ", T, " nsteps: ", nsteps ) 35 | end 36 | 37 | # Initial conditions 38 | u = [1.0; 0.0] 39 | v = [0.0; 0.0] 40 | 41 | t = 0.0 42 | 43 | # timefunctions and forcing 44 | if testcase == 1 45 | # timefunc1(t) = 0.5*(sin(0.5*omega*(t)))^2; 46 | timefunc1(t) = 0.25*(1.0 - cos(omega*t)); 47 | uforce1(t::Float64) = [0.0; 0.0] 48 | vforce1(t::Float64) = [0.0; 0.0] 49 | 50 | timefunc =timefunc1 51 | uforce = uforce1 52 | vforce = vforce1 53 | 54 | elseif testcase == 0 55 | timefunc0(t::Float64) = 0.25*(1-sin(omega*t)) 56 | uforce0(t::Float64) = [0.0; 0.0] 57 | vforce0(t::Float64) = [0.0; 0.0] 58 | 59 | timefunc =timefunc0 60 | uforce = uforce0 61 | vforce = vforce0 62 | elseif testcase == 2 63 | timefunc2(t::Float64) = 4/T^2 *t*(T-t) 64 | phi12(t::Float64) = 0.25*(t - sin(omega*t)/omega) 65 | phidot2(t::Float64) = 0.5*(sin(0.5*omega*(t)))^2 66 | uforce2(t::Float64) = [(timefunc2(t) - phidot2(t))*sin(phi12(t)); 0.0] 67 | vforce2(t::Float64) = [0.0; -(timefunc2(t) - phidot2(t)) * cos(phi12(t))] 68 | 69 | timefunc =timefunc2 70 | uforce = uforce2 71 | vforce = vforce2 72 | elseif testcase == 3 73 | timefunc3(t::Float64) = 4/T^2 *t*(T-t) 74 | phi13(t::Float64) = 0.25*(t - sin(omega*t)/omega) 75 | phidot3(t::Float64) = 0.5*(sin(0.5*omega*(t)))^2 76 | uforce3(t::Float64) = [-phidot3(t)*sin(phi13(t)); timefunc3(t)*cos(phi13(t))] 77 | vforce3(t::Float64) = [-timefunc3(t)*sin(phi13(t)); phidot3(t)*cos(phi13(t))] 78 | 79 | timefunc =timefunc3 80 | uforce = uforce3 81 | vforce = vforce3 82 | end 83 | 84 | K(t::Float64) = timefunc(t)*K0 85 | S(t::Float64) = timefunc(t)*S0 86 | 87 | 88 | #Create time stepper 89 | gamma, stages = Juqbox.getgamma(order) 90 | timestepper = Juqbox.svparams(K,S,IN) 91 | 92 | #Time integration 93 | usave = u 94 | vsave = -v 95 | tsave = t 96 | 97 | # Stormer-Verlet 98 | start = time() 99 | for ii in 1:nsteps 100 | for jj in 1:stages 101 | t, u, v, v05 = Juqbox.step(timestepper,t,u,v,dt*gamma[jj],uforce,vforce) 102 | end 103 | usave = [usave u] 104 | vsave = [vsave -v] 105 | tsave = [tsave t] 106 | end 107 | elapsed = time() - start 108 | 109 | if verbose 110 | @show(elapsed) 111 | end 112 | 113 | # Evaluate analytical solution 114 | if testcase == 1 || testcase == 2 || testcase==3 115 | # phi = 0.25.*(tsave - 1/omega.*sin.(omega.*tsave)) 116 | phi = 0.25*(t - 1.0/omega*sin(omega*t)) 117 | cg = cos(phi) 118 | ce = -im*sin(phi) 119 | elseif testcase == 0 120 | # phi = 0.25.*( tsave + 1/omega.*(cos.(omega.*tsave) .- 1) ); 121 | phi = 0.25*( t + 1/omega*(cos(omega*t) - 1.0) ); 122 | cg = cos(phi); 123 | ce = -sin(phi); 124 | end 125 | 126 | #compute errors @ final time 127 | # cg_err = sqrt( (usave[1,end]-real(cg) )^2 + (vsave[1,end]-imag(cg) )^2 ); 128 | # ce_err = sqrt( (usave[2,end]-real(ce) )^2 + (vsave[2,end]-imag(ce) )^2 ); 129 | 130 | cg_err = sqrt( (u[1]-real(cg) )^2 + (v[1]+imag(cg) )^2 ); # negative imaginary part in v 131 | ce_err = sqrt( (u[2]-real(ce) )^2 + (v[2]+imag(ce) )^2 ); 132 | 133 | 134 | return t,u,v,dt,cg_err,ce_err 135 | end 136 | 137 | function timestep_convergence( errFileName:: String, writeFile:: Bool=false) 138 | CFL_vec = 10.0.^(-1.0:-0.5:-2.0) 139 | 140 | ntests = 4 141 | err_mat = zeros(length(CFL_vec),2,ntests) 142 | order = 2 143 | 144 | verbose = false 145 | 146 | for j = 1:ntests 147 | for i = 1:length(CFL_vec) 148 | t,u,v,dt,cg_err,ce_err = timesteptest(CFL_vec[i], j-1, order, verbose) 149 | err_mat[i,1,j] = cg_err 150 | err_mat[i,2,j] = ce_err 151 | end 152 | end 153 | 154 | if writeFile 155 | @save errFileName err_mat 156 | println("Saved final errors on file: ", errFileName) 157 | return true 158 | else 159 | # compare results with those on the reference file 160 | err_mat0 = copy(err_mat) 161 | @load errFileName err_mat 162 | # check sizes 163 | if length(err_mat0) != length(err_mat) 164 | printf("timestep_convergence: size missmatch with reference solution!") 165 | return false 166 | end 167 | # compare 168 | max_diff = maximum(abs.(err_mat - err_mat0)) 169 | if verbose 170 | println("Max abs diff = ", max_diff) 171 | end 172 | success = (max_diff <= 1e-13) 173 | return success 174 | 175 | # for j = 1:ntests 176 | # for i = 1:length(CFL_vec) 177 | # println("test #", j, " cfl = ", CFL_vec[i], " diff (cg) = ", abs(err_mat[i,1,j] - err_mat0[i,1,j]), " diff (ce) = ", abs(err_mat[i,2,j] - err_mat0[i,2,j]) ) 178 | # end 179 | # end 180 | end 181 | 182 | end -------------------------------------------------------------------------------- /test/test-implicit-midpoint.jl: -------------------------------------------------------------------------------- 1 | using LinearAlgebra 2 | using JLD2 3 | using Juqbox 4 | using Printf 5 | #Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 6 | 7 | function timesteptest_implicit( cfl = 0.1, testcase = 2, order = 2, verbose = false) 8 | 9 | N = 2 # vector dimension 10 | IN = Matrix{Float64}(I, N, N) 11 | 12 | if testcase == 1 || testcase == 2 13 | K0 = [0 1; 1 0] 14 | S0 = [0 0; 0 0] 15 | elseif testcase == 0 || testcase == 3 16 | K0 = [0 0; 0 0] 17 | S0 = [0 1; -1 0] 18 | end 19 | 20 | #Final time 21 | period = 1 22 | T = 5pi 23 | omega = 2*pi/period 24 | 25 | lamb = eigvals(K0 .+ S0) 26 | maxeig = maximum(abs.(lamb)) 27 | 28 | #time step 29 | dt = cfl/maxeig 30 | nsteps = ceil(Int64,T/dt) 31 | dt = T/nsteps 32 | 33 | if verbose 34 | println("Testcase: ", testcase, " Cfl: ", cfl, " Final time: ", T, " nsteps: ", nsteps ) 35 | end 36 | 37 | # Initial conditions 38 | u = [1.0; 0.0] 39 | v = [0.0; 0.0] 40 | 41 | t = 0.0 42 | 43 | # timefunctions and forcing 44 | if testcase == 1 45 | # timefunc1(t) = 0.5*(sin(0.5*omega*(t)))^2; 46 | timefunc1(t) = 0.25*(1.0 - cos(omega*t)); 47 | uforce1(t::Float64) = [0.0; 0.0] 48 | vforce1(t::Float64) = [0.0; 0.0] 49 | 50 | timefunc =timefunc1 51 | uforce = uforce1 52 | vforce = vforce1 53 | 54 | elseif testcase == 0 55 | timefunc0(t::Float64) = 0.25*(1-sin(omega*t)) 56 | uforce0(t::Float64) = [0.0; 0.0] 57 | vforce0(t::Float64) = [0.0; 0.0] 58 | 59 | timefunc =timefunc0 60 | uforce = uforce0 61 | vforce = vforce0 62 | elseif testcase == 2 63 | timefunc2(t::Float64) = 4/T^2 *t*(T-t) 64 | phi12(t::Float64) = 0.25*(t - sin(omega*t)/omega) 65 | phidot2(t::Float64) = 0.5*(sin(0.5*omega*(t)))^2 66 | uforce2(t::Float64) = [(timefunc2(t) - phidot2(t))*sin(phi12(t)); 0.0] 67 | vforce2(t::Float64) = [0.0; -(timefunc2(t) - phidot2(t)) * cos(phi12(t))] 68 | 69 | timefunc =timefunc2 70 | uforce = uforce2 71 | vforce = vforce2 72 | elseif testcase == 3 73 | timefunc3(t::Float64) = 4/T^2 *t*(T-t) 74 | phi13(t::Float64) = 0.25*(t - sin(omega*t)/omega) 75 | phidot3(t::Float64) = 0.5*(sin(0.5*omega*(t)))^2 76 | uforce3(t::Float64) = [-phidot3(t)*sin(phi13(t)); timefunc3(t)*cos(phi13(t))] 77 | vforce3(t::Float64) = [-timefunc3(t)*sin(phi13(t)); phidot3(t)*cos(phi13(t))] 78 | 79 | timefunc =timefunc3 80 | uforce = uforce3 81 | vforce = vforce3 82 | end 83 | 84 | K(t::Float64) = timefunc(t)*K0 85 | S(t::Float64) = timefunc(t)*S0 86 | 87 | 88 | #Create time stepper 89 | gamma, stages = Juqbox.getgamma(order) 90 | timestepper = Juqbox.svparams(K,S,IN) 91 | 92 | #Time integration 93 | usave = u 94 | vsave = -v 95 | tsave = t 96 | 97 | # Stormer-Verlet 98 | start = time() 99 | 100 | for ii in 1:nsteps 101 | t, u, v = Juqbox.step_midpoint(timestepper, t, u, v, dt, uforce, vforce) 102 | usave = [usave u] 103 | vsave = [vsave -v] 104 | tsave = [tsave t] 105 | end 106 | elapsed = time() - start 107 | 108 | if verbose 109 | @show(elapsed) 110 | end 111 | 112 | # Evaluate analytical solution 113 | if testcase == 1 || testcase == 2 || testcase==3 114 | # phi = 0.25.*(tsave - 1/omega.*sin.(omega.*tsave)) 115 | phi = 0.25*(t - 1.0/omega*sin(omega*t)) 116 | cg = cos(phi) 117 | ce = -im*sin(phi) 118 | elseif testcase == 0 119 | # phi = 0.25.*( tsave + 1/omega.*(cos.(omega.*tsave) .- 1) ); 120 | phi = 0.25*( t + 1/omega*(cos(omega*t) - 1.0) ); 121 | cg = cos(phi); 122 | ce = -sin(phi); 123 | end 124 | 125 | #compute errors @ final time 126 | # cg_err = sqrt( (usave[1,end]-real(cg) )^2 + (vsave[1,end]-imag(cg) )^2 ); 127 | # ce_err = sqrt( (usave[2,end]-real(ce) )^2 + (vsave[2,end]-imag(ce) )^2 ); 128 | 129 | cg_err = sqrt( (u[1]-real(cg) )^2 + (v[1]+imag(cg) )^2 ); # negative imaginary part in v 130 | ce_err = sqrt( (u[2]-real(ce) )^2 + (v[2]+imag(ce) )^2 ); 131 | 132 | 133 | return t,u,v,dt,cg_err,ce_err 134 | end 135 | 136 | function timestep_convergence_implicit( errFileName:: String, writeFile:: Bool=false) 137 | CFL_vec = 10.0.^(-1.0:-0.5:-2.0) 138 | 139 | ntests = 4 140 | err_mat = zeros(length(CFL_vec),2,ntests) 141 | order = 2 142 | 143 | verbose = false 144 | 145 | for j = 1:ntests 146 | for i = 1:length(CFL_vec) 147 | t,u,v,dt,cg_err,ce_err = timesteptest_implicit(CFL_vec[i], j-1, order, verbose) 148 | err_mat[i,1,j] = cg_err 149 | err_mat[i,2,j] = ce_err 150 | end 151 | end 152 | if writeFile 153 | @save errFileName err_mat 154 | println("Saved final errors on file: ", errFileName) 155 | return true 156 | else 157 | # compare results with those on the reference file 158 | err_mat0 = copy(err_mat) 159 | @load errFileName err_mat 160 | # check sizes 161 | if length(err_mat0) != length(err_mat) 162 | printf("timestep_convergence: size missmatch with reference solution!") 163 | return false 164 | end 165 | # compare 166 | max_diff = maximum(abs.(err_mat - err_mat0)) 167 | if verbose 168 | println("Max abs diff = ", max_diff) 169 | end 170 | success = (max_diff <= 1e-13) 171 | return success 172 | 173 | # for j = 1:ntests 174 | # for i = 1:length(CFL_vec) 175 | # println("test #", j, " cfl = ", CFL_vec[i], " diff (cg) = ", abs(err_mat[i,1,j] - err_mat0[i,1,j]), " diff (ce) = ", abs(err_mat[i,2,j] - err_mat0[i,2,j]) ) 176 | # end 177 | # end 178 | end 179 | 180 | end 181 | -------------------------------------------------------------------------------- /docs/src/examples.md: -------------------------------------------------------------------------------- 1 | Examples of the setup procedure can be found in the scripts in the `Juqbox.jl/examples` directory. 2 | The examples are invoked by, e.g. 3 | - `include("cnot1-setup.jl")` 4 | The following cases are included: 5 | - `rabi-setup.jl` Pi-pulse (X-gate) for a qubit, i.e. a Rabi oscillator. 6 | - `cnot1-setup.jl` CNOT gate for a single qudit with 4 essential and 2 guard levels. 7 | - `flux-setup.jl` CNOT gate for single qubit with a flux-tuning control Hamiltonian. 8 | - `cnot2-setup.jl` CNOT gate for a pair of coupled qubits with guard levels. 9 | - `cnot3-setup.jl` Cross-resonance CNOT gate for a pair of qubits that are coupled by a cavity resonator. 10 | **Note:** The last case reads an optimized solution from file. 11 | 12 | ## Risk-Neutral Optimization 13 | In practice the entries of the Hamiltonian may have some uncertainty, especially for higher energy levels, and it is desirable to design control pulses that are more robust to noise. 14 | For both of the following examples, we consider a risk-neutral strategy to design a ``|0\rangle \leftrightarrow |2\rangle`` SWAP gate on a single qubit, with three essential levels and one guard level. Let ``\epsilon`` be a random variable and consider the uncertain system Hamiltonian ``H^{u}_s(\epsilon) = H^{rw}_s + H'(\epsilon)`` where ``H^{rw}_s`` is the system Hamiltonian in the rotating frame, and ``H'(\epsilon)`` is the uncertain diagonal perturbation: 15 | ```math 16 | \frac{H'(\epsilon)}{2\pi} = \begin{pmatrix} 17 | 0 & & & \\ 18 | & \epsilon/100 & & \\ 19 | & & \epsilon/10 & \\ 20 | & & & \epsilon 21 | \end{pmatrix}. 22 | ``` 23 | We note that the uncertain system Hamiltonian has expectation 24 | ``\mathbb{E}[H^{u}_s(\epsilon)] = H^{rw}_s``. We may correspondingly 25 | update the original objective function, ``\mathcal{G}(\bm{\alpha},H^{rw}_s)``, to the risk-neutral utility function ``\widetilde{\mathcal{G}}(\bm{\alpha}) = \mathbb{E}[\mathcal{G}(\bm{\alpha}, H^u_s(\epsilon))]``. For simple forms of the random variable ``\epsilon``, we may compute ``\widetilde{\mathcal{G}}`` by quadrature: 26 | ```math 27 | \mathbb{E}[\mathcal{G}(\alpha,H^u_s (\epsilon))] 28 | = \int_{-\epsilon_\text{max}}^{ \epsilon_\text{max}} 29 | \mathcal{G}(\alpha, H^u_s(\epsilon)) \, d\epsilon 30 | \approx 31 | \sum_{k = 1}^M w_k \mathcal{G}(\alpha, H^u_s(\epsilon_k) ), 32 | ``` 33 | where ``w_k`` and ``\epsilon_k`` are the weights and collocation points 34 | of a quadrature rule. 35 | 36 | Examples of the setup procedure can be found in the scripts in the `Juqbox.jl/examples/Risk_Neutral` directory. 37 | The `run_all.jl` routine for both examples performs both a deterministic optimization, and a risk-neutral optimization 38 | where the system Hamiltonian is perturbed by additive noise. Full details of the first example can be found in Section 6.2 of the manuscript found [here](https://arxiv.org/abs/2106.14310). 39 | 40 | ### Example 1 : Uniform Noise 41 | For a first example, we let ``\epsilon \sim \text{Unif}(- \epsilon_\text{max}, \epsilon_\text{max})`` be a uniform random variable for some ``\epsilon_\text{max} > 0``. We may compute the risk-neutral objective function via a simple 42 | Gauss-Legendre quadrature. To do this, Juqbox calls the `FastGaussianQuadrature.jl` package to generate 43 | appropriate nodes and weights via the call 44 | ```julia 45 | nodes, weights = gausslegendre(nquad) 46 | ``` 47 | for `nquad` quadrature points. We note, however, that the usual Gauss-Legendre quadrature integrates over 48 | the interval ``[-1,1]``. A simple modification of the nodes and weights then creates a quadrature rule 49 | in the interval ``(- \epsilon_\text{max}, \epsilon_\text{max})``: 50 | ```julia 51 | # Map nodes to [-ϵ/2,ϵ/2] 52 | nodes .*= 0.5*ep_max 53 | weights .*= 0.5 54 | ``` 55 | Passing `nodes` and `weights` as keyword arguments to the `Juqbox.setup_ipopt_problem` routine then allows Juqbox to 56 | perform a risk-neutral optimization: 57 | ```julia 58 | prob = Juqbox.setup_ipopt_problem(params,..., nodes=nodes, weights=weights) 59 | ``` 60 | 61 | ### Example 2 : Bimodal Gaussian Noise 62 | From the previous example, we see that performing a risk-neutral optimization 63 | requires the user to provide an appropriate quadrature rule for a given noise model. 64 | If one is interested in considered models with normally-distributed (Gaussian) noise, 65 | we may instead use a Gauss-Hermite rule to generate quadrature for a risk-neutral 66 | optimization. For a bimodal Gaussian distribution, for instance, we may specify an 67 | array of means and standard deviations for each: 68 | ```julia 69 | mean_vec = 2*pi.*[-1e-2 1e-2] 70 | sig_vec = 2*pi*[1e-3, 1e-3] 71 | ``` 72 | With the mean and standard deviation for each Gaussian mode defined, we may then 73 | generate a set of reference Gauss-Hermite quadrature weights and nodes (chosen specifically 74 | for the ``e^{-x^2}`` weight function): 75 | ```julia 76 | nodes_tmp, weights_tmp = gausshermite(nquad) 77 | ``` 78 | We note that this quadrature rule essentially integrates a function against a 79 | Gaussian with a mean of zero and unit variance. The simple transformation below 80 | obtains a rule for the above user-specified probability distribution: 81 | ```julia 82 | # Make a larger node list for full distribution 83 | n_modes = length(mean_vec) 84 | nodes = zeros(n_modes*nquad) 85 | weights = copy(nodes) 86 | inv_n = 1.0/(n_modes*sqrt(pi)) 87 | 88 | # Mapping to reference weighting function 89 | for i = 1:n_modes 90 | μ = mean_vec[i] 91 | σ = sig_vec[i] 92 | 93 | offset = (i-1)*nquad 94 | for j = 1:length(nodes_tmp) 95 | nodes[j + offset] = sqrt(2)*σ*nodes_tmp[j] + μ 96 | weights[j+offset] = weights_tmp[j]*inv_n 97 | end 98 | end 99 | ``` 100 | 101 | ### Other Noise Models 102 | We note that by modifying the `eval_f_par` and `eval_grad_f_par` routines in `src/ipopt_interface.jl` file 103 | allows the use of other noise models for a risk-neutral optimization. Juqbox currently does not support 104 | time-dependent noise processes. 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](docs/src/JuQbox_logo-inline-color.png) 2 | 3 | # 4 | 5 | Juqbox.jl is a package for solving quantum optimal control problems in closed quantum systems, where the evolution of the state vector is governed by Schroedinger's equation. 6 | 7 | The main features of Juqbox include 8 | - Symplectic time integration of Schroedinger's equation using the Stormer-Verlet scheme. 9 | - Efficient parameterization of the control functions using B-splines with carrier waves. 10 | - Objective function includes target gate infidelity and occupation of guarded (forbidden) states. 11 | - Exact computation of the gradient of the objective function by solving the discrete adjoint equation. 12 | 13 | The numerical methods in Juqbox.jl are documented in these papers: 14 | 1. N. A. Petersson and F. M. Garcia, "Optimal Control of Closed Quantum Systems via B-Splines with Carrier Waves", SIAM J. Sci. Comput. (2022) 44(6): A3592-A3616, LLNL-JRNL-823853, [arXiv:2106.14310](https://arxiv.org/abs/2106.14310). 15 | 2. N. A. Petersson, F. M. Garcia, A. E. Copeland, Y. L. Rydin and J. L. DuBois, “Discrete Adjoints for Accurate Numerical Optimization with Application to Quantum Control”, LLNL-JRNL-800457, [arXiv:2001.01013](https://arxiv.org/abs/2001.01013). 16 | 17 | ## Installation 18 | 19 | The following instructions assume that you have already installed Julia (currently version 1.8.5) on your system. Before proceeding, we recommend that you add the following to the file ~/.julia/config/startup.jl. You may have to first create the config folder under .julia in your home directory. Then add this line to the startup.jl file: 20 | 21 | - **ENV["PLOTS_DEFAULT_BACKEND"]="GR"** 22 | 23 | This is an environment variable. It specifies the backend for plotting. Most of the examples in this document uses the GR backend, which assumes that you have installed that package. If you have trouble with GR, you can instead install the "PyPlot" package and set the default backend to "PyPlot". 24 | 25 | Start julia and type `]` to enter the package manager. First install these packages (unless they are already installed): 26 | - (@v1.8) pkg> add Plots 27 | - (@v1.8) pkg> add GR 28 | - (@v1.8) pkg> add FFTW 29 | - (@v1.8) pkg> add Ipopt 30 | - (@v1.8) pkg> add JLD2 31 | - (@v1.8) pkg> add LaTeXStrings 32 | - (@v1.8) pkg> add Printf 33 | - (@v1.8) pkg> add Random 34 | - (@v1.8) pkg> add SparseArrays 35 | - (@v1.8) pkg> add Test 36 | 37 | Then install Juqbox.jl: 38 | - (@v1.8) pkg> add https://github.com/LLNL/Juqbox.jl 39 | - (@v1.8) pkg> precompile 40 | - (@v1.8) pkg> test Juqbox 41 | - ... all tests should pass (case=flux gives a Warning message) ... 42 | 43 | To exit the package manager you type ``, and to exit julia you type `exit()`. 44 | 45 | ## Documentation 46 | 47 | The Juqbox.jl documentation can be found [here](https://software.llnl.gov/Juqbox.jl/). 48 | 49 | ## Examples 50 | 51 | To access the examples, clone the Juqbox.jl git repository: 52 | - shell> git clone https://github.com/LLNL/Juqbox.jl.git 53 | 54 | Then go to the examples directory in the Juqbox.jl folder (on some systems the folder is named juqbox.jl): 55 | - shell> cd Juqbox.jl/examples 56 | 57 | Start julia and try the `cnot1-setup.jl' test case: 58 | - shell> julia 59 | - julia> include("cnot1-setup.jl") 60 | - julia> pcof = run_optimizer(prob,pcof0); 61 | - julia> pl = plot_results(params,pcof); 62 | - julia> pl[1] 63 | 64 | Examples of the setup procedure can be found in the following scripts in the **Juqbox.jl/examples** directory (invoke by, e.g. **include("cnot1-setup.jl")**) 65 | - **rabi-setup.jl** Pi-pulse (X-gate) for a qubit, i.e. a Rabi oscillator. 66 | - **cnot1-setup.jl** CNOT gate for a single qudit with 4 essential and 2 guard levels. 67 | - **flux-setup.jl** CNOT gate for single qubit with a flux-tuning control Hamiltonian. 68 | - **cnot2-setup.jl** CNOT gate for a pair of coupled qubits with guard levels. 69 | - **cnot3-setup.jl** Cross-resonance CNOT gate for a pair of qubits that are coupled by a cavity resonator. **Note:** This case reads an optimized solution from file. 70 | - **Risk_Neutral/run_all.jl** SWAP 0-2 gate for a single qudit. This routine performs both a deterministic optimization, and a risk-neutral optimization 71 | where the system Hamiltonian is perturbed by additive noise which is assumed to be uniform. Full details of the example can be found in Section 6.2 in one of our papers, which can be found [here](https://arxiv.org/abs/2106.14310). 72 | 73 | ## Contributing to Juqbox.jl 74 | Juqbox.jl is currently under development. The prefered method of contributing is through a pull request (PR). If you are interested in contributing, please contact Anders Petersson (petersson1@llnl.gov) or Fortino Garcia (fortino.garcia@colorado.edu). 75 | 76 | ## Credits 77 | Most of the Julia code was written by Anders Petersson and Fortino Garcia. Important contributions were also made by Ylva Rydin and Austin Copeland. 78 | 79 | ## License 80 | Juqbox.jl is relased under the MIT license. 81 | 82 | ### Note: FFTW.jl license 83 | Juqbox.jl uses the Julia package FFTW.jl for post processing of the 84 | results. That package is released under the MIT Expat license and provides Julia bindings to the 85 | FFTW library for fast Fourier transforms (FFTs), as well as functionality useful for signal 86 | processing. Note that the FFTW library is licensed under GPLv2 or higher (see its license file), but 87 | the bindings to the FFTW library in the FFTW.jl package are licensed under MIT. As an lternative to 88 | using the FFTW libary, the FFTs in Intel's Math Kernel Library (MKL) can be used by setting an 89 | environment variable JULIA_FFTW_PROVIDER to MKL and running Pkg.build("FFTW"). MKL will be provided 90 | through MKL_jll. Setting this environment variable only needs to be done for the first build of the 91 | package; after that, the package will remember to use MKL when building and updating. 92 | 93 | ### Note: Ipopt.jl license 94 | Juqbox.jl uses the Julia package Ipopt.jl for optimizing control 95 | functions. That package is released under the MIT Expat License and provides Julia bindings to the 96 | Ipopt library, which is released under the Eclipse Public License. 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/cnot1-leakieq-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a single qudit with 4 energy levels (and 2 4 | guard states). The drift Hamiltonian in the rotating frame 5 | is 6 | H_0 = - 0.5*ξ_a(a^†a^†aa), 7 | where a is the annihilation operator for the qudit. 8 | Here the control Hamiltonian includes the usual symmetric 9 | and anti-symmetric terms 10 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†) 11 | which come from the rotating frame approximation and hence 12 | we refer to these as "coupled" controls. 13 | The problem parameters are: 14 | ω_a = 2π × 4.10595 Grad/s, 15 | ξ_a = 2π × 2(0.1099) Grad/s. 16 | We use Bsplines with carrier waves with frequencies 17 | 0, ξ_a, 2ξ_a Grad/s. 18 | ==========================================================# 19 | using LinearAlgebra 20 | using Plots 21 | using FFTW 22 | using DelimitedFiles 23 | using Printf 24 | using Ipopt 25 | using Random 26 | 27 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 28 | 29 | # include("../src/Juqbox.jl") 30 | using Juqbox # quantum control module 31 | 32 | Random.seed!(1234) 33 | 34 | Nosc = 1 # Number of coupled sub-systems = oscillators 35 | N = 4 # Number of essential energy levels 36 | 37 | Nguard = 2 # Number of extra guard levels 38 | Ntot = N + Nguard # Total number of energy levels 39 | 40 | T = 100.0 # Duration of gate. 41 | 42 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 43 | fa = 4.10336 44 | xa = 0.2198 45 | rot_freq = [fa] # Used to calculate the lab frame ctrl function 46 | 47 | # setup drift Hamiltonian 48 | number = Diagonal(collect(0:Ntot-1)) 49 | 50 | H0 = -0.5*(2*pi)*xa* (number*number - number) # xa is in GHz 51 | 52 | # lowering matrix 53 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering matrix 54 | adag = Array(transpose(amat)); 55 | Hsym_ops=[Array(amat + adag)] 56 | Hanti_ops=[Array(amat - adag)] 57 | H0 = Array(H0) 58 | 59 | # Estimate time step 60 | maxctrl = 0.001*2*pi * 8.5 # 9, 10.5, 12, 15 MHz 61 | 62 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxctrl]) 63 | println("# time steps: ", nsteps) 64 | 65 | Nfreq = 3 # number of carrier frequencies 66 | 67 | Nctrl = length(Hsym_ops) # Here, Nctrl = 1 68 | om = zeros(Nctrl,Nfreq) 69 | 70 | om[1:Nctrl,2] .= -2.0*pi *xa # Note negative sign 71 | om[1:Nctrl,3] .= -2.0*pi* 2.0*xa 72 | 73 | println("Carrier frequencies [GHz]: ", om[1,:]./(2*pi)) 74 | 75 | maxamp = zeros(Nfreq) 76 | 77 | if Nfreq >= 3 78 | const_fact = 0.45 79 | maxamp[1] = maxctrl*const_fact 80 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # split the remainder equally 81 | else 82 | # same threshold for all frequencies 83 | maxamp .= maxctrl/Nfreq 84 | end 85 | 86 | maxpar = maximum(maxamp) 87 | 88 | # Initial basis with guard levels 89 | U0 = initial_cond([N], [Nguard]) 90 | 91 | # CNOT target 92 | gate_cnot = zeros(ComplexF64, N, N) 93 | gate_cnot[1,1] = 1.0 94 | gate_cnot[2,2] = 1.0 95 | gate_cnot[3,4] = 1.0 96 | gate_cnot[4,3] = 1.0 97 | 98 | # Initial basis with guard levels 99 | U0 = initial_cond([N], [Nguard]) 100 | 101 | utarget = U0 * gate_cnot 102 | 103 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 104 | 105 | # Compute Ra*utarget 106 | rot1 = Diagonal(exp.(im*omega1*T)) 107 | 108 | # target in the rotating frame 109 | vtarget = rot1*utarget 110 | 111 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 112 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, objFuncType=3,leak_ubound=4.e-5) 113 | # initial parameter guess 114 | 115 | # D1 smaller than 5 does not work 116 | D1 = 10 # Number of B-spline coefficients per segment 117 | nCoeff = 2*Nctrl*Nfreq*D1 # factor '2' is for sin/cos 118 | 119 | # Random.seed!(12456) 120 | 121 | startFromScratch = true # false 122 | #startFile="cnot-pcof-opt.dat" 123 | startFile="leak.jld2" 124 | 125 | # initial parameter guess 126 | if startFromScratch 127 | pcof0 = maxpar*0.01 * rand(nCoeff) 128 | println("*** Starting from random pcof with amplitude ", maxpar*0.01) 129 | else 130 | # use if you want to read the initial coefficients from file 131 | if occursin(".jld2",startFile) 132 | pcof0 = vec(Juqbox.read_pcof(startFile)) 133 | else 134 | pcof0 = vec(readdlm(startFile)) 135 | end 136 | println("*** Starting from B-spline coefficients in file: ", startFile) 137 | nCoeff = length(pcof0) 138 | D1 = div(nCoeff, 2*Nctrl*Nfreq) # number of B-spline coeff per control function 139 | end 140 | 141 | # min and max coefficient values 142 | minCoeff, maxCoeff = Juqbox.assign_thresholds_freq(maxamp, Nctrl, Nfreq, D1) 143 | 144 | samplerate = 32 # only used for plotting 145 | casename = "cnot1" # base file name (used in optimize-once.jl) 146 | 147 | maxIter = 150 # 0 # optional argument 148 | lbfgsMax = 10 # optional argument 149 | ipTol = 1e-5 # optional argument 150 | acceptTol = ipTol # 1e-4 # acceptable tolerance 151 | acceptIter = 15 152 | 153 | println("*** Settings ***") 154 | println("System Hamiltonian coefficients [GHz]: (fa, xa) = ", fa, xa) 155 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 156 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 157 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 158 | for q=1:Nfreq 159 | println("Carrier frequency: ", om[q]/(2*pi), " GHz, max parameter amplitude = ", 1000*maxamp[q]/(2*pi), " MHz") 160 | end 161 | println("Tikhonov coefficients: tik0 = ", params.tik0) 162 | 163 | wa = Juqbox.Working_Arrays(params,nCoeff) 164 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch 165 | # ,jacob_approx="finite-difference-values") 166 | ,jacob_approx="exact") 167 | 168 | println("Initial coefficient vector stored in 'pcof0'") 169 | #pcof = Juqbox.run_optimizer(prob, pcof0) -------------------------------------------------------------------------------- /test/cases/flux.dat: -------------------------------------------------------------------------------- 1 | 5.7248896372377e-04 2 | -1.2970020962856e-03 3 | 3.4631087598373e-03 4 | 3.1703652430449e-03 5 | 9.5964007984235e-04 6 | -3.1560483347074e-03 7 | -3.3003220135974e-03 8 | 3.9542923005128e-03 9 | -3.2934940254375e-03 10 | 2.6869871229451e-03 11 | 2.2008520942865e-03 12 | 6.9427118229198e-04 13 | -2.5373914858521e-03 14 | 3.0838055264086e-03 15 | -7.5887756057812e-04 16 | 1.0717600339691e-03 17 | -8.3563825981213e-04 18 | 2.6964652853659e-03 19 | -1.7277970002120e-03 20 | -3.0025646094838e-03 21 | 2.3292749091443e-03 22 | 1.0460543841809e-03 23 | -3.3000801021654e-04 24 | 1.0677009911684e-03 25 | 2.3738034454841e-03 26 | -2.7857551723352e-04 27 | 1.9109294442694e-03 28 | -3.3123066570040e-03 29 | 1.3870906958120e-03 30 | -3.4437555483663e-03 31 | 1.1116635646605e-03 32 | -2.3453375935511e-03 33 | -2.8983063434532e-05 34 | -2.8250911306100e-04 35 | -3.7561111643201e-03 36 | -1.7611785728059e-04 37 | 3.3701576901279e-03 38 | 3.7441708273478e-03 39 | -3.1188739170414e-03 40 | -2.4149458804684e-03 41 | 1.6571884506594e-03 42 | -3.5642301977425e-03 43 | 2.5682112153298e-03 44 | 1.7186492041322e-03 45 | -1.3308569820039e-03 46 | 2.9126792361513e-03 47 | -3.0392451606861e-03 48 | -2.7636969584850e-03 49 | 3.3615314723373e-03 50 | 2.8713138766339e-03 51 | 3.4023825848126e-03 52 | -1.5633080118746e-03 53 | 3.0901125133384e-03 54 | 6.2568778923218e-04 55 | 8.0715141401853e-04 56 | -2.2002133742294e-03 57 | 1.2406965917400e-03 58 | -3.6073371093055e-03 59 | 7.7720496143958e-04 60 | 2.3472161203533e-03 61 | -3.8724463237186e-03 62 | 3.3049209137757e-04 63 | 1.1600149241846e-03 64 | -3.5958111564071e-03 65 | 5.2420851037076e-04 66 | -3.6754405431808e-03 67 | -1.3128291617594e-03 68 | 3.4878166283556e-04 69 | 3.9328852669219e-03 70 | -2.9758237363960e-03 71 | 3.0934270948131e-03 72 | 2.2499626841855e-03 73 | -2.8142014610986e-03 74 | -1.8470838287683e-03 75 | -1.4765734420661e-03 76 | 4.4692270887939e-04 77 | 5.7338516605776e-05 78 | -2.2796230415550e-03 79 | -2.6874558158738e-03 80 | -2.9073382946775e-03 81 | -6.9822444428105e-04 82 | 2.8993873733882e-03 83 | -5.5915464910562e-04 84 | 2.4866986622906e-03 85 | 3.3889445876728e-03 86 | 3.7366644463005e-03 87 | 3.5430171764569e-03 88 | -2.2690888382179e-03 89 | -3.5782384491544e-03 90 | 3.3062779836036e-03 91 | 3.4072032672624e-03 92 | 2.0619694866119e-03 93 | 1.1795281042573e-04 94 | 1.5142818488018e-03 95 | -2.6712681931542e-03 96 | -9.4134492303027e-04 97 | -1.3601897676362e-03 98 | 1.7316456262501e-03 99 | -3.1036032392971e-03 100 | -2.3305182827996e-03 101 | -1.5711839843839e-03 102 | -2.8349322278823e-03 103 | 1.3549809663267e-03 104 | 1.7736473120116e-03 105 | -1.8396296766895e-03 106 | -1.2433527367604e-03 107 | 2.1863631895610e-04 108 | 2.0709255989943e-03 109 | 2.8597591331417e-03 110 | 2.0963995333940e-03 111 | 3.6099752773863e-04 112 | -7.8830654669332e-04 113 | 2.7920423431454e-03 114 | 2.8202562202265e-03 115 | -2.3560917218461e-03 116 | -1.9689151241221e-03 117 | 3.5083886297481e-03 118 | -3.4355054976646e-03 119 | -1.8346542441912e-03 120 | 1.0775192420133e-03 121 | 7.3820778429539e-04 122 | 6.1615010358308e-04 123 | -1.7342634161265e-03 124 | -9.0624477989183e-04 125 | -2.0847675236697e-03 126 | 3.4713018694390e-03 127 | 1.6808406959309e-04 128 | 2.8128717598815e-03 129 | 3.4136037575992e-03 130 | 1.2075083675210e-03 131 | -1.2284823275250e-03 132 | 3.8004943233406e-03 133 | 3.6410826105110e-03 134 | -2.1261285314089e-03 135 | -1.1054873238691e-03 136 | 3.7283363427194e-04 137 | 3.3911150045819e-04 138 | 3.2597384966295e-03 139 | -2.8182903775454e-03 140 | 4.2096227461505e-04 141 | 3.2858149085355e-03 142 | -2.5989248038910e-03 143 | 7.1520636761544e-04 144 | 2.0531261055123e-03 145 | 3.8470981745110e-03 146 | 2.6020204453960e-04 147 | -3.4852883431914e-03 148 | 3.5626276634314e-03 149 | -3.0734231789090e-03 150 | 3.8268835571782e-03 151 | 3.1777625002881e-04 152 | 2.4955513719614e-03 153 | -2.1611889219237e-03 154 | -1.2698678991046e-04 155 | -6.1917195199388e-04 156 | -3.6129366118860e-04 157 | 4.3146657783344e-04 158 | -1.3689644551899e-03 159 | 1.2828421922338e-03 160 | -3.3635222087393e-03 161 | 3.7353444750580e-03 162 | -1.0855205355036e-03 163 | 2.7406871464636e-03 164 | 3.4631265975999e-03 165 | -8.2270506969437e-04 166 | 6.9804544763834e-05 167 | 1.0333598630855e-03 168 | -1.0488560091555e-03 169 | -3.2543596594825e-03 170 | 3.9857880571792e-03 171 | 3.7536934910006e-03 172 | -3.1572407943891e-03 173 | -3.6058700781930e-03 174 | 3.4241140152396e-03 175 | -3.8588919434445e-03 176 | 2.6223720116933e-03 177 | -1.8955210138495e-04 178 | -2.0619611223655e-03 179 | 2.5772816773552e-03 180 | -3.1251776407867e-03 181 | -2.2553938704653e-04 182 | 1.2149029119327e-03 183 | -2.4911884633941e-04 184 | -3.4184098263774e-03 185 | 1.4913984173291e-03 186 | 1.4550442725931e-03 187 | -2.1914298523814e-03 188 | -7.4837144402853e-04 189 | -1.8883567994471e-03 190 | 4.6122615899142e-04 191 | -3.3197838044741e-03 192 | -1.4916555672964e-04 193 | -3.6034357942717e-03 194 | 2.7570590087800e-03 195 | 2.5219119766429e-03 196 | 2.6712722146008e-03 197 | 2.4080616254347e-04 198 | -2.2250715370546e-03 199 | -2.7394412997983e-03 200 | -8.0289019020961e-04 201 | 1.4170049549498e-03 202 | 2.2047092046493e-03 203 | 2.9494215072976e-03 204 | -1.0204484734869e-03 205 | 1.1869400953435e-03 206 | 3.4514372889134e-03 207 | -4.9214556317745e-05 208 | 1.5016801069053e-03 209 | -1.8259556026105e-03 210 | -6.6269554466655e-04 211 | 2.7152139720363e-03 212 | -2.1187939695291e-03 213 | -1.9190028556966e-03 214 | -3.2768068571948e-03 215 | 3.1323472599365e-03 216 | -2.8428116733705e-03 217 | -1.1150967139466e-03 218 | -3.5225869722596e-03 219 | 6.8236754204125e-04 220 | -3.0163577658940e-04 221 | 3.8894435013252e-03 222 | -1.5731887621998e-04 223 | -2.5782732298876e-03 224 | -2.4615169730689e-04 225 | -3.1088978980785e-03 226 | -3.1223657485770e-03 227 | 1.7128770574027e-03 228 | -2.3198956310511e-03 229 | 3.8769423914929e-03 230 | 1.3509366197609e-03 231 | 2.1087031305262e-03 232 | -8.2998843266035e-04 233 | -1.7474322309224e-03 234 | 2.4266487732414e-04 235 | -1.1614735071609e-03 236 | 3.4284014428772e-03 237 | 2.2979174287212e-03 238 | -1.1241846339465e-03 239 | 4.6193169499085e-04 240 | 3.0118156988017e-03 241 | -------------------------------------------------------------------------------- /examples/cnot1-objthreshold-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a single qudit with 4 energy levels (and 2 4 | guard states). The drift Hamiltonian in the rotating frame 5 | is 6 | H_0 = - 0.5*ξ_a(a^†a^†aa), 7 | where a is the annihilation operator for the qudit. 8 | Here the control Hamiltonian includes the usual symmetric 9 | and anti-symmetric terms 10 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†) 11 | which come from the rotating frame approximation and hence 12 | we refer to these as "coupled" controls. 13 | The problem parameters are: 14 | ω_a = 2π × 4.10595 Grad/s, 15 | ξ_a = 2π × 2(0.1099) Grad/s. 16 | We use Bsplines with carrier waves with frequencies 17 | 0, ξ_a, 2ξ_a Grad/s. 18 | ==========================================================# 19 | using LinearAlgebra 20 | using Plots 21 | using FFTW 22 | using DelimitedFiles 23 | using Printf 24 | using Ipopt 25 | using Random 26 | 27 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 28 | 29 | using Juqbox # quantum control module 30 | 31 | Nosc = 1 # Number of coupled sub-systems = oscillators 32 | N = 4 # Number of essential energy levels 33 | 34 | Nguard = 2 # Number of extra guard levels 35 | Ntot = N + Nguard # Total number of energy levels 36 | 37 | T = 100.0 # Duration of gate. 38 | 39 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 40 | fa = 4.10336 41 | xa = 0.2198 42 | rot_freq = [fa] # Used to calculate the lab frame ctrl function 43 | 44 | # setup drift Hamiltonian 45 | number = Diagonal(collect(0:Ntot-1)) 46 | 47 | H0 = -0.5*(2*pi)*xa* (number*number - number) # xa is in GHz 48 | 49 | # lowering matrix 50 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering matrix 51 | adag = Array(transpose(amat)); 52 | Hsym_ops=[Array(amat + adag)] 53 | Hanti_ops=[Array(amat - adag)] 54 | H0 = Array(H0) 55 | 56 | # Estimate time step 57 | maxctrl = 0.001*2*pi * 8.5 # 9, 10.5, 12, 15 MHz 58 | 59 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxctrl]) 60 | println("# time steps: ", nsteps) 61 | 62 | Nfreq = 3 # number of carrier frequencies 63 | 64 | Nctrl = length(Hsym_ops) # Here, Nctrl = 1 65 | om = zeros(Nctrl,Nfreq) 66 | 67 | om[1:Nctrl,2] .= -2.0*pi *xa # Note negative sign 68 | om[1:Nctrl,3] .= -2.0*pi* 2.0*xa 69 | 70 | println("Carrier frequencies [GHz]: ", om[1,:]./(2*pi)) 71 | 72 | maxamp = zeros(Nfreq) 73 | 74 | if Nfreq >= 3 75 | const_fact = 0.45 76 | maxamp[1] = maxctrl*const_fact 77 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # split the remainder equally 78 | else 79 | # same threshold for all frequencies 80 | maxamp .= maxctrl/Nfreq 81 | end 82 | 83 | maxpar = maximum(maxamp) 84 | 85 | # Initial basis with guard levels 86 | U0 = initial_cond([N], [Nguard]) 87 | 88 | # CNOT target 89 | gate_cnot = zeros(ComplexF64, N, N) 90 | gate_cnot[1,1] = 1.0 91 | gate_cnot[2,2] = 1.0 92 | gate_cnot[3,4] = 1.0 93 | gate_cnot[4,3] = 1.0 94 | 95 | # Initial basis with guard levels 96 | U0 = initial_cond([N], [Nguard]) 97 | 98 | utarget = U0 * gate_cnot 99 | 100 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 101 | 102 | # Compute Ra*utarget 103 | rot1 = Diagonal(exp.(im*omega1*T)) 104 | 105 | # target in the rotating frame 106 | vtarget = rot1*utarget 107 | 108 | Integrator_id = 2 109 | 110 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 111 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, Integrator = Integrator_id) 112 | # terminate if objective function is below this value 113 | params.objThreshold = 1e-3 114 | if Integrator_id == 2 115 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 116 | params.linear_solver = linear_solver 117 | end 118 | 119 | # D1 smaller than 5 does not work 120 | D1 = 10 # Number of B-spline coefficients per segment 121 | nCoeff = 2*Nctrl*Nfreq*D1 # factor '2' is for sin/cos 122 | 123 | # Random.seed!(12456) 124 | 125 | startFromScratch = true # false 126 | startFile="cnot-pcof-opt.dat" 127 | 128 | # initial parameter guess 129 | if startFromScratch 130 | pcof0 = maxpar*0.01 * rand(nCoeff) 131 | println("*** Starting from random pcof with amplitude ", maxpar*0.01) 132 | else 133 | # use if you want to read the initial coefficients from file 134 | pcof0 = vec(readdlm(startFile)) 135 | println("*** Starting from B-spline coefficients in file: ", startFile) 136 | nCoeff = length(pcof0) 137 | D1 = div(nCoeff, 2*Nctrl*Nfreq) # number of B-spline coeff per control function 138 | end 139 | 140 | # min and max coefficient values 141 | minCoeff, maxCoeff = Juqbox.assign_thresholds_freq(maxamp, Nctrl, Nfreq, D1) 142 | 143 | samplerate = 32 # only used for plotting 144 | casename = "cnot1" # base file name (used in optimize-once.jl) 145 | 146 | maxIter = 75 # 0 # optional argument 147 | lbfgsMax = 250 # optional argument 148 | ipTol = 1e-5 # optional argument 149 | acceptTol = ipTol # 1e-4 # acceptable tolerance 150 | acceptIter = 15 151 | 152 | println("*** Settings ***") 153 | println("System Hamiltonian coefficients [GHz]: (fa, xa) = ", fa, xa) 154 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 155 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 156 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 157 | for q=1:Nfreq 158 | println("Carrier frequency: ", om[q]/(2*pi), " GHz, max parameter amplitude = ", 1000*maxamp[q]/(2*pi), " MHz") 159 | end 160 | println("Tikhonov coefficients: tik0 = ", params.tik0) 161 | 162 | if params.Integrator_id == 1 163 | wa = Juqbox.Working_Arrays(params, nCoeff) 164 | elseif params.Integrator_id == 2 165 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 166 | end 167 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 168 | 169 | println("Initial coefficient vector stored in 'pcof0'") 170 | -------------------------------------------------------------------------------- /examples/xgate-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | X-gate for qubit #5 on IBM Casablanca 3 | ==========================================================# 4 | using LinearAlgebra 5 | using Plots 6 | using FFTW 7 | using DelimitedFiles 8 | using Printf 9 | using Ipopt 10 | using Random 11 | 12 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 13 | #pyplot() 14 | 15 | using Juqbox 16 | 17 | N = 2 # 3 18 | Nguard = 1 # 0 19 | 20 | Ntot = N + Nguard 21 | 22 | casename = "x-gate" # base file name (used in optimize-once.jl) 23 | 24 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 25 | fa = 4.9639697 26 | xa = 0.3215826 27 | rot_freq = [fa] # Rotational frequencies 28 | 29 | # Duration tailored for the IBM IQ mixer 30 | dt_IQ = 2.0/9 31 | Npulses = 160 # Must be evenly divisable by 16 32 | T = dt_IQ * Npulses # Nano sec 33 | 34 | utarget = Matrix{ComplexF64}(I, Ntot, N) 35 | vtarget = Matrix{ComplexF64}(I, Ntot, N) 36 | 37 | # unitary target matrix 38 | if Nguard == 0 39 | utarget[1,1] = 0.0 40 | utarget[2,1] = 1.0 41 | utarget[1,2] = 1.0 42 | utarget[2,2] = 0.0 43 | elseif Nguard == 1 44 | utarget[1,1] = 0.0 45 | utarget[2,1] = 1.0 46 | utarget[3,1] = 0.0 47 | utarget[1,2] = 1.0 48 | utarget[2,2] = 0.0 49 | utarget[3,2] = 0.0 50 | end 51 | 52 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], rot_freq) 53 | 54 | # Compute Ra*utarget 55 | rot1 = Diagonal(exp.(im*omega1*T)) 56 | 57 | # target in the rotating frame 58 | vtarget = rot1*utarget 59 | 60 | # target in lab frame 61 | #vtarget = utarget 62 | 63 | # setup ansatz for control functions 64 | Nctrl = 1 65 | Nosc = Nctrl 66 | Nfreq = 1 # number of carrier frequencies 67 | 68 | Random.seed!(2456) 69 | # initial parameter guess 70 | 71 | # setup carrier frequencies 72 | om = zeros(Nosc,Nfreq) 73 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 74 | println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 75 | 76 | # setup drift Hamiltonian 77 | number = Diagonal(collect(0:Ntot-1)) 78 | 79 | H0 = -0.5*(2*pi)*xa* (number*number - number) 80 | println("Drift Hamiltonian/2*pi: ", H0) 81 | 82 | # lowering matrix 83 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 84 | # raising matrix 85 | adag = transpose(amat) # raising operator matrix 86 | 87 | # package the lowering and raising matrices together into an one-dimensional array of two-dimensional arrays 88 | Hsym_ops=[Array(amat + adag)] 89 | Hanti_ops=[Array(amat - adag)] 90 | H0 = Array(H0) 91 | 92 | # max parameter amplitude 93 | maxpar = 4.0*(2*pi/T)/Nfreq # Bigger amplitude than for a constant pulse 94 | 95 | maxamp = zeros(Nctrl, Nfreq) 96 | maxamp .= maxpar # Same parameter bounds for all ctrl Hamiltonians and frequencies 97 | 98 | # Estimate time step 99 | Pmin = 80 100 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxpar], Pmin) 101 | 102 | println("Final time T = ", T, " # time steps per min-period, P = ", Pmin, " # time steps: ", nsteps) 103 | 104 | # Initial conditions 105 | Ident = Matrix{Float64}(I, Ntot, Ntot) 106 | U0 = Ident[1:Ntot,1:N] 107 | 108 | # setup the simulation parameters 109 | Integrator_id = 2 110 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 111 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, Integrator = Integrator_id) 112 | 113 | 114 | if Integrator_id == 2 115 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 116 | params.linear_solver = linear_solver 117 | end 118 | # setup the initial parameter vector, either randomized or from file 119 | startFromScratch = true # true 120 | startFile = "rabi-pcof-opt-alpha-0.5.dat" 121 | 122 | if startFromScratch 123 | D1 = 5 # Number of B-spline coefficients per frequency, sin/cos 124 | nCoeff = 2*Nosc*Nfreq*D1 # factor '2' is for sin/cos 125 | pcof0 = maxpar*0.05.*ones(nCoeff) 126 | 127 | println("*** Starting from constant pcof with amplitude ", maxpar*0.05) 128 | else 129 | # the data on the startfile must be consistent with the setup! 130 | # use if you want to have initial coefficients read from file 131 | pcof0 = vec(readdlm(startFile)) 132 | nCoeff = length(pcof0) 133 | D1 = div(nCoeff, 2*Nosc*Nfreq) # factor '2' is for sin/cos 134 | nCoeff = 2*Nosc*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 135 | println("*** Starting from B-spline coefficients in file: ", startFile) 136 | end 137 | 138 | # min and max coefficient values 139 | useBarrier = true 140 | minCoeff, maxCoeff = assign_thresholds_ctrl_freq(params, D1, maxamp) 141 | 142 | zero_start_end!(params, D1, minCoeff, maxCoeff) 143 | 144 | #-maxpar*ones(nCoeff); 145 | #maxCoeff = maxpar*ones(nCoeff); 146 | 147 | # For ipopt 148 | maxIter = 150 # optional argument 149 | lbfgsMax = 250 # optional argument 150 | 151 | println("*** Settings ***") 152 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 153 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 154 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 155 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 156 | println("Max parameter amplitudes: maxpar = ", maxpar) 157 | println("Tikhonov coefficients: tik0 = ", params.tik0) 158 | 159 | # Allocate all working arrays 160 | if params.Integrator_id == 1 161 | wa = Juqbox.Working_Arrays(params, nCoeff) 162 | elseif params.Integrator_id == 2 163 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 164 | end 165 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 166 | 167 | #uncomment to run the gradient checker for the initial pcof 168 | #= 169 | if @isdefined addOption 170 | addOption( prob, "derivative_test", "first-order"); # for testing the gradient 171 | else 172 | AddIpoptStrOption( prob, "derivative_test", "first-order") 173 | end 174 | =# 175 | 176 | println("Initial coefficient vector stored in 'pcof0'") 177 | @time traceobjgrad(pcof0, params, wa, false, true) -------------------------------------------------------------------------------- /test/cases/cnot-lab-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a single qudit with 4 energy levels (and 2 4 | guard states) and showcases the use of uncoupled controls. 5 | The drift Hamiltonian in the rotating frame is 6 | H_0 = - 0.5*ξ_a(a^†a^†aa), 7 | where a is the annihilation operator for the qudit. Here 8 | the control Hamiltonian includes the usual symmetric and 9 | anti-symmetric terms 10 | H_s = p(t)(a + a^†), H_a = q(t)(a - a^†), 11 | which come from the rotating frame approximation and hence 12 | we refer to these as "coupled" controls. In 13 | addition, we consider a flux-charge term 14 | H_f = f(t) a^†a, 15 | which we refer to as an "uncoupled" control as it is 16 | invariant to the rotating frame approximation. The problem 17 | parameters for this example are from Jonathan and Pranav at 18 | UChicago: 19 | ω_a = 2π × 5.0 Grad/s, 20 | ξ_a = 2π × 0.2 Grad/s. 21 | We use Bsplines with carrier waves with frequencies 22 | 0, ξ_a Grad/s. 23 | ==========================================================# 24 | using LinearAlgebra 25 | #using Plots 26 | #pyplot() 27 | #using FFTW 28 | #using DelimitedFiles 29 | using Printf 30 | #using Ipopt 31 | using Random 32 | using SparseArrays 33 | 34 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 35 | 36 | #import Juqbox 37 | 38 | verbose = false 39 | N = 4 40 | Nguard = 2 41 | Ntot = N + Nguard 42 | 43 | samplerate = 64 # default number of time steps per unit time (plotting only) 44 | casename = "cnot-lab" # base file name (used in optimize-once.jl) 45 | 46 | # Set to false for dense matrix operations 47 | use_sparse = true 48 | 49 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 50 | fa = 5.0 # 4.1 51 | xa = 0.2 52 | 53 | # duration 54 | T = 12.0 # Tperiod/4 55 | 56 | Ident = Matrix{Float64}(I, Ntot, Ntot) 57 | utarget = Matrix{ComplexF64}(I, Ntot, N) 58 | 59 | # CNOT target 60 | utarget[:,4] = Ident[:,3] 61 | utarget[:,3] = Ident[:,4] 62 | 63 | Nosc = 1 64 | Nfreq = 3 # number of carrier frequencies 65 | 66 | Random.seed!(2456) 67 | 68 | # setup drift Hamiltonian 69 | number = Diagonal(collect(0:Ntot-1)) 70 | 71 | H0 = 2*pi*fa*number-0.5*(2*pi)*xa* (number*number - number) 72 | 73 | # lowering matrix 74 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 75 | # raising matrix 76 | adag = transpose(amat) # raising operator matrix 77 | 78 | Hunc_ops=[Array(amat + adag)] 79 | H0 = Array(H0) 80 | 81 | Ncoupled = 0 82 | Nunc = length(Hunc_ops) 83 | 84 | # setup carrier frequencies 85 | om = zeros(1,Nfreq) 86 | use_bcarrier = true 87 | 88 | if use_bcarrier 89 | @assert(Nfreq==1 || Nfreq==2 || Nfreq==3) 90 | if Nfreq == 2 91 | om[1:1,2] .= -2.0*pi*fa 92 | elseif Nfreq == 3 93 | om[:,2] .= -2.0*pi*fa 94 | om[:,3] .= 2.0*pi*fa 95 | end 96 | end 97 | 98 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 99 | #println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 100 | 101 | # max parameter amplitude 102 | max_unc = 2*pi*5.0 103 | 104 | # Initial conditions 105 | Ident = Matrix{Float64}(I, Ntot, Ntot) 106 | U0 = Ident[1:Ntot,1:N] 107 | 108 | # setup the initial parameter vector, either randomized or from file 109 | startFromScratch = true # true 110 | startFile = "flux-pcof-opt-alpha-0.5.dat" 111 | useBarrier = true 112 | 113 | if startFromScratch 114 | # D1 smaller than 3 does not work 115 | D1 = 30 # Number of B-spline coefficients per frequency, sin/cos and real/imag 116 | nCoeff = (2*Ncoupled + Nunc)*Nfreq*D1 117 | pcof0 = zeros(nCoeff) 118 | pcof0 = (rand(nCoeff) .- 0.5).*max_unc*0.1 119 | else 120 | # the data on the startfile must be consistent with the setup! 121 | # use if you want to have initial coefficients read from file 122 | pcof0 = vec(readdlm(startFile)) 123 | nCoeff = length(pcof0) 124 | D1 = div(nCoeff, (2*Ncoupled + Nunc)*Nfreq) # factor '2' is for sin/cos 125 | 126 | nCoeff = (2*Ncoupled + Nunc)*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 127 | 128 | # println("*** Starting from B-spline coefficients in file: ", startFile) 129 | end 130 | 131 | # Estimate time step for simulation 132 | nsteps = Juqbox.calculate_timestep(T, H0, Hunc_ops, [max_unc]) 133 | 134 | # setup the simulation parameters 135 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=utarget, Cfreq=om, Rfreq=[fa], Hconst=H0, Hunc_ops=Hunc_ops) 136 | params.saveConvHist = true 137 | params.nsteps *= 5 138 | 139 | # Quiet mode for testing 140 | params.quiet = true 141 | 142 | #Tikhonov regularization coefficients 143 | params.tik0 = 1e-3 144 | 145 | # Set bounds on coefficients 146 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params,D1,[0.0],[max_unc]) 147 | 148 | # For ipopt 149 | maxIter = 50 # optional argument 150 | lbfgsMax = 250 # optional argument 151 | 152 | if verbose 153 | println("*** Settings ***") 154 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 155 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 156 | if use_bcarrier 157 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 158 | else 159 | println("Using regular B-spline basis functions") 160 | end 161 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 162 | println("Max parameter amplitudes: max_unc = ", max_unc) 163 | println("Tikhonov coefficient: tik0 = ", params.tik0) 164 | end 165 | 166 | # Estimate number of terms in Neumann series for time stepping (Default 3) 167 | tol = eps(1.0); # machine precision 168 | Juqbox.estimate_Neumann!(tol, params, Float64[], [max_unc]) 169 | 170 | wa = Juqbox.Working_Arrays(params, nCoeff) 171 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter, lbfgsMax) 172 | 173 | if @isdefined addOption 174 | addOption(prob, "derivative_test", "first-order"); # for testing the gradient 175 | else 176 | AddIpoptStrOption(prob, "derivative_test", "first-order") 177 | end 178 | 179 | #println("Initial coefficient vector stored in 'pcof0'") 180 | -------------------------------------------------------------------------------- /examples/cnot1-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a single qudit with 4 energy levels (and 2 4 | guard states). The drift Hamiltonian in the rotating frame 5 | is 6 | H_0 = - 0.5*ξ_a(a^†a^†aa), 7 | where a is the annihilation operator for the qudit. 8 | Here the control Hamiltonian includes the usual symmetric 9 | and anti-symmetric terms 10 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†) 11 | which come from the rotating frame approximation and hence 12 | we refer to these as "coupled" controls. 13 | The problem parameters are: 14 | ω_a = 2π × 4.10595 Grad/s, 15 | ξ_a = 2π × 2(0.1099) Grad/s. 16 | We use Bsplines with carrier waves with frequencies 17 | 0, ξ_a, 2ξ_a Grad/s. 18 | ==========================================================# 19 | using LinearAlgebra 20 | using Plots 21 | using FFTW 22 | using DelimitedFiles 23 | using Printf 24 | using Ipopt 25 | using Random 26 | using BenchmarkTools 27 | 28 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 29 | 30 | using Juqbox 31 | #using Juqbox # quantum control module 32 | 33 | Nosc = 1 # Number of coupled sub-systems = oscillators 34 | N = 4 # Number of essential energy levels 35 | 36 | Nguard = 2 # Number of extra guard levels 37 | Ntot = N + Nguard # Total number of energy levels 38 | 39 | T = 100.0 # Duration of gate. 40 | 41 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 42 | fa = 4.10336 43 | xa = 0.2198 44 | rot_freq = [fa] # Used to calculate the lab frame ctrl function 45 | 46 | # setup drift Hamiltonian 47 | number = Diagonal(collect(0:Ntot-1)) 48 | 49 | H0 = -0.5*(2*pi)*xa* (number*number - number) # xa is in GHz 50 | 51 | # lowering matrix 52 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering matrix 53 | adag = Array(transpose(amat)); 54 | Hsym_ops=[Array(amat + adag)] 55 | Hanti_ops=[Array(amat - adag)] 56 | H0 = Array(H0) 57 | 58 | # Estimate time step 59 | maxctrl = 0.001*2*pi * 8.5 # 9, 10.5, 12, 15 MHz 60 | 61 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxctrl]) 62 | 63 | println("# time steps: ", nsteps) 64 | 65 | Nfreq = 3 # number of carrier frequencies 66 | 67 | Nctrl = length(Hsym_ops) # Here, Nctrl = 1 68 | om = zeros(Nctrl,Nfreq) 69 | 70 | om[1:Nctrl,2] .= -2.0*pi *xa # Note negative sign 71 | om[1:Nctrl,3] .= -2.0*pi* 2.0*xa 72 | 73 | println("Carrier frequencies [GHz]: ", om[1,:]./(2*pi)) 74 | 75 | maxamp = zeros(Nfreq) 76 | 77 | if Nfreq >= 3 78 | const_fact = 0.45 79 | maxamp[1] = maxctrl*const_fact 80 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # split the remainder equally 81 | else 82 | # same threshold for all frequencies 83 | maxamp .= maxctrl/Nfreq 84 | end 85 | 86 | maxpar = maximum(maxamp) 87 | 88 | # Initial basis with guard levels 89 | U0 = initial_cond([N], [Nguard]) 90 | 91 | # CNOT target 92 | gate_cnot = zeros(ComplexF64, N, N) 93 | gate_cnot[1,1] = 1.0 94 | gate_cnot[2,2] = 1.0 95 | gate_cnot[3,4] = 1.0 96 | gate_cnot[4,3] = 1.0 97 | 98 | # Initial basis with guard levels 99 | U0 = initial_cond([N], [Nguard]) 100 | 101 | utarget = U0 * gate_cnot 102 | 103 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 104 | 105 | # Compute Ra*utarget 106 | rot1 = Diagonal(exp.(im*omega1*T)) 107 | 108 | # target in the rotating frame 109 | vtarget = rot1*utarget 110 | 111 | Integrator_id = 2 112 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 113 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, Integrator = Integrator_id) 114 | 115 | if params.Integrator_id == 2 116 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 117 | params.linear_solver = linear_solver 118 | end 119 | 120 | # initial parameter guess 121 | 122 | # D1 smaller than 5 does not work 123 | D1 = 10 # Number of B-spline coefficients per segment 124 | nCoeff = 2*Nctrl*Nfreq*D1 # factor '2' is for sin/cos 125 | 126 | # Random.seed!(12456) 127 | 128 | startFromScratch = true # false 129 | startFile="cnot-pcof-opt.dat" 130 | 131 | # initial parameter guess 132 | if startFromScratch 133 | pcof0 = maxpar*0.01 * rand(nCoeff) 134 | pcof1 = zeros(size(pcof0)) 135 | # pcof1 .= pcof0 136 | # pcof1[1] = pcof0[1] + 0.000001 137 | println("*** Starting from random pcof with amplitude ", maxpar*0.01) 138 | else 139 | # use if you want to read the initial coefficients from file 140 | pcof0 = vec(readdlm(startFile)) 141 | println("*** Starting from B-spline coefficients in file: ", startFile) 142 | nCoeff = length(pcof0) 143 | D1 = div(nCoeff, 2*Nctrl*Nfreq) # number of B-spline coeff per control function 144 | end 145 | # min and max coefficient values 146 | minCoeff, maxCoeff = Juqbox.assign_thresholds_freq(maxamp, Nctrl, Nfreq, D1) 147 | 148 | samplerate = 32 # only used for plotting 149 | casename = "cnot1" # base file name (used in optimize-once.jl) 150 | 151 | maxIter = 75 # 0 # optional argument 152 | lbfgsMax = 250 # optional argument 153 | ipTol = 1e-5 # optional argument 154 | acceptTol = ipTol # 1e-4 # acceptable tolerance 155 | acceptIter = 15 156 | 157 | 158 | 159 | 160 | println("*** Settings ***") 161 | println("System Hamiltonian coefficients [GHz]: (fa, xa) = ", fa, xa) 162 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 163 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 164 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 165 | for q=1:Nfreq 166 | println("Carrier frequency: ", om[q]/(2*pi), " GHz, max parameter amplitude = ", 1000*maxamp[q]/(2*pi), " MHz") 167 | end 168 | println("Tikhonov coefficients: tik0 = ", params.tik0) 169 | 170 | if params.Integrator_id == 1 171 | wa = Juqbox.Working_Arrays(params, nCoeff) 172 | elseif params.Integrator_id == 2 173 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 174 | end 175 | 176 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 177 | 178 | println("Initial coefficient vector stored in 'pcof0'") 179 | 180 | @time traceobjgrad(pcof0, params, wa, false, true) -------------------------------------------------------------------------------- /src/ImplicitMidpoint.jl: -------------------------------------------------------------------------------- 1 | using SparseArrays 2 | using LinearAlgebra 3 | 4 | # Julia versions prior to v"1.3.1" can't use LinearAlgebra's 5 argument mul!, routines 5 | # included here for backwards compatability 6 | if(VERSION < v"1.3.1") 7 | include("backwards_compat.jl") 8 | end 9 | 10 | #Used in testing Implicit Midpoint 11 | @inline function step_midpoint(stepper::svparams, t::Float64, u::Array{Float64,N}, 12 | v::Array{Float64,N}, h::Float64, uforce::Function, vforce::Function) where N 13 | 14 | uforce05 = uforce(t + 0.5*h) 15 | vforce05 = vforce(t + 0.5*h) 16 | 17 | t,u,v = step_midpoint(stepper, t, u, v, h, uforce05, vforce05) 18 | 19 | return t, u, v 20 | end 21 | 22 | 23 | @inline function step_midpoint(stepper:: svparams, t::Float64, u::Array{Float64,N}, v::Array{Float64,N}, 24 | h::Float64, uforce05::Array{Float64, N}, vforce05::Array{Float64, N}) where N 25 | 26 | S = stepper.S 27 | K = stepper.K 28 | In = stepper.In 29 | 30 | 31 | u05 = uforce05 32 | v05 = vforce05 33 | 34 | K05 = K(t + 0.5*h) 35 | S05 = S(t + 0.5*h) 36 | 37 | A = h/2 * K05 38 | B = h/2 * K05 * u 39 | C = h/2 * S05 40 | D = h/2 * S05 * v 41 | E = h*v05 42 | F = h/2 * S05 43 | G = h/2 * S05 * u 44 | H = h/2 * K05 * v 45 | J = h*u05 46 | 47 | Q = (In - C) 48 | 49 | u_lhs = In - F + A*(Q\A) 50 | u_rhs = G - A*(Q\(B + D + E + v)) - H + J + u 51 | 52 | u = u_lhs\u_rhs 53 | 54 | v = Q\(A*u + B + D + E + v) 55 | 56 | t = t + h 57 | 58 | return t, u, v 59 | end 60 | 61 | @inline function m_step_slow!(t::Float64, u::Array, v::Array, h::Float64, 62 | K05,S05, uforce::Array, vforce::Array) 63 | 64 | row, col = size(K05) 65 | In = Matrix(1.0I, row, col) 66 | A = h/2 * K05 67 | B = h/2 * K05 * u 68 | C = h/2 * S05 69 | D = h/2 * S05 * v 70 | E = h*vforce 71 | F = h/2 * S05 72 | G = h/2 * S05 * u 73 | H = h/2 * K05 * v 74 | J = h*uforce 75 | 76 | Q = (In - C) 77 | 78 | u_lhs = In .- F .+ A*(Q\A) 79 | u_rhs = G .- A*(Q\(B .+ D .+ E .+ v)) .- H .+ J .+ u 80 | 81 | u .= u_lhs\u_rhs 82 | 83 | v .= Q\(A*u .+ B .+ D .+ E .+ v) 84 | 85 | t = t + h 86 | 87 | return t 88 | end 89 | 90 | @inline function m_step_no_forcing_slow!(t::Float64, u::Array, v::Array, h::Float64, 91 | K05,S05) 92 | 93 | row, col = size(K05) 94 | In = Matrix(1.0I, row, col) 95 | A = h/2 * K05 96 | B = h/2 * K05 * u 97 | C = h/2 * S05 98 | D = h/2 * S05 * v 99 | E = 0.0 100 | F = h/2 * S05 101 | G = h/2 * S05 * u 102 | H = h/2 * K05 * v 103 | J = 0.0 104 | 105 | Q = (In - C) 106 | 107 | u_lhs = In .- F .+ A*(Q\A) 108 | u_rhs = G .- A*(Q\(B .+ D .+ E .+ v)) .- H .+ J .+ u 109 | 110 | u .= u_lhs\u_rhs 111 | 112 | v .= Q\(A*u .+ B .+ D .+ E .+ v) 113 | 114 | t = t + h 115 | 116 | return t 117 | end 118 | 119 | 120 | #Sparse implementation of Implicit Midpoint Rule with no forcing 121 | @inline function m_step_no_forcing!(t::Float64, u::Array{Float64,N}, v::Array{Float64,N}, h::Float64, 122 | K05::SparseMatrixCSC{Float64,Int64},S05::SparseMatrixCSC{Float64,Int64}, rhs_u::Array{Float64,N}, rhs_v::Array{Float64,N}, 123 | x0_u, x0_v, norm_matrix_u, norm_matrix_v, linear_solver::lsolver_object) where N 124 | 125 | #Create the right hand side of the system Ax = b 126 | #Create the real part of the right hand side 127 | mul!(rhs_u, S05, u, h/2, 0) 128 | axpy!(1, u, rhs_u) 129 | mul!(rhs_u, K05, v, -h/2, 1) 130 | 131 | #Create the imaginary part of the right hand side 132 | mul!(rhs_v, S05, v, h/2, 0) 133 | axpy!(1, v, rhs_v) 134 | mul!(rhs_v, K05, u, h/2, 1) 135 | 136 | #Solve the system using Jacobi's method 137 | linear_solver.solve(h, rhs_u, rhs_v, S05, K05, u, v, x0_u, x0_v, norm_matrix_u, norm_matrix_v) 138 | 139 | u .= x0_u 140 | v .= x0_v 141 | 142 | t = t + h 143 | 144 | return t 145 | end 146 | 147 | #Dense version of above function 148 | @inline function m_step_no_forcing!(t::Float64, u::Array{Float64,N}, v::Array{Float64,N}, h::Float64, 149 | K05::Array{Float64, N},S05::Array{Float64, N}, rhs_u::Array{Float64,N}, rhs_v::Array{Float64,N}, 150 | x0_u, x0_v, norm_matrix_u, norm_matrix_v, linear_solver::lsolver_object) where N 151 | 152 | mul!(rhs_u, S05, u, h/2, 0) 153 | axpy!(1, u, rhs_u) 154 | mul!(rhs_u, K05, v, -h/2, 1) 155 | 156 | mul!(rhs_v, S05, v, h/2, 0) 157 | axpy!(1, v, rhs_v) 158 | mul!(rhs_v, K05, u, h/2, 1) 159 | 160 | linear_solver.solve(h, rhs_u, rhs_v, S05, K05, u, v, x0_u, x0_v, norm_matrix_u, norm_matrix_v) 161 | 162 | u .= x0_u 163 | v .= x0_v 164 | 165 | t = t + h 166 | 167 | return t 168 | end 169 | 170 | 171 | #Sparse implmentation of Implicit Midpoint rule with forcing 172 | @inline function m_step!(t::Float64, u::Array{Float64,N}, v::Array{Float64,N}, h::Float64, 173 | K05::SparseMatrixCSC{Float64,Int64},S05::SparseMatrixCSC{Float64,Int64}, uforce::Array{Float64,N}, vforce::Array{Float64,N}, 174 | rhs_u::Array{Float64,N}, rhs_v::Array{Float64,N}, x0_u, x0_v, norm_matrix_u, norm_matrix_v, linear_solver::lsolver_object) where N 175 | 176 | #Create the right hand side of the system Ax = b 177 | #Create the real part of the right hand side 178 | mul!(rhs_u, S05, u, h/2, 0) 179 | axpy!(1, u, rhs_u) 180 | mul!(rhs_u, K05, v, -h/2, 1) 181 | 182 | #Add real forcing term to real right hand side 183 | axpy!(h, uforce, rhs_u) 184 | 185 | #Create the imaginary part of the right hand side 186 | mul!(rhs_v, S05, v, h/2, 0) 187 | axpy!(1, v, rhs_v) 188 | mul!(rhs_v, K05, u, h/2, 1) 189 | 190 | #Add imaginary forcing term to imaginary right hand side 191 | axpy!(h, vforce, rhs_v) 192 | 193 | #Solve the system using Jacobi's method 194 | linear_solver.solve(h, rhs_u, rhs_v, S05, K05, u, v, x0_u, x0_v, norm_matrix_u, norm_matrix_v) 195 | 196 | u .= x0_u 197 | v .= x0_v 198 | 199 | t = t + h 200 | 201 | return t 202 | end 203 | 204 | #Dense version of above function 205 | @inline function m_step!(t::Float64, u::Array{Float64,N}, v::Array{Float64,N}, h::Float64, 206 | K05::Array{Float64, N},S05::Array{Float64, N}, uforce::Array{Float64,N}, vforce::Array{Float64,N}, 207 | rhs_u::Array{Float64,N}, rhs_v::Array{Float64,N}, x0_u, x0_v, norm_matrix_u, norm_matrix_v, 208 | linear_solver::lsolver_object) where N 209 | 210 | mul!(rhs_u, S05, u, h/2, 0) 211 | axpy!(1, u, rhs_u) 212 | mul!(rhs_u, K05, v, -h/2, 1) 213 | axpy!(h, uforce, rhs_u) 214 | 215 | mul!(rhs_v, S05, v, h/2, 0) 216 | axpy!(1, v, rhs_v) 217 | mul!(rhs_v, K05, u, h/2, 1) 218 | axpy!(h, vforce, rhs_v) 219 | 220 | linear_solver.solve(h, rhs_u, rhs_v, S05, K05, u, v, x0_u, x0_v, norm_matrix_u, norm_matrix_v) 221 | 222 | u .= x0_u 223 | v .= x0_v 224 | 225 | t = t + h 226 | 227 | return t 228 | end -------------------------------------------------------------------------------- /examples/flux-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a single qudit with 4 energy levels (and 2 4 | guard states) and showcases the use of uncoupled controls. 5 | The drift Hamiltonian in the rotating frame is 6 | H_0 = - 0.5*ξ_a(a^†a^†aa), 7 | where a is the annihilation operator for the qudit. Here 8 | the control Hamiltonian includes the usual symmetric and 9 | anti-symmetric terms 10 | H_s = p(t)(a + a^†), H_a = q(t)(a - a^†), 11 | which come from the rotating frame approximation and hence 12 | we refer to these as "coupled" controls. In 13 | addition, we consider a magnetic flux-tuning control term 14 | H_f = f(t) a^†a, 15 | which we refer to as an "uncoupled" control as it is 16 | invariant to the rotating frame approximation. The problem 17 | parameters for this example are from Jonathan and Pranav at 18 | UChicago: 19 | ω_a/2π = 5.0 GHz, 20 | ξ_a/2π = 0.2 GHz. 21 | We use Bsplines with carrier waves with carrier frequencies (rotating frame) 22 | 0, -ξ_a rad/ns. 23 | ==========================================================# 24 | using LinearAlgebra 25 | using Plots 26 | using FFTW 27 | using DelimitedFiles 28 | using Printf 29 | using Ipopt 30 | using Random 31 | using SparseArrays 32 | 33 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 34 | 35 | using Juqbox 36 | 37 | verbose = false 38 | N = 4 39 | Nguard = 2 40 | Ntot = N + Nguard 41 | 42 | # Set to false for dense matrix operations 43 | use_sparse = true 44 | 45 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 46 | fa = 5.0 # 4.1 47 | xa = 0.2 48 | rot_freq = [fa, fa] # Rotational frequencies for each control Hamiltonian 49 | 50 | # duration 51 | T = 11.0 # Tperiod/4 52 | 53 | Ident = Matrix{Float64}(I, Ntot, Ntot) 54 | utarget = Matrix{ComplexF64}(I, Ntot, N) 55 | vtarget = Matrix{ComplexF64}(I, Ntot, N) 56 | 57 | # CNOT target 58 | utarget[:,4] = Ident[:,3] 59 | utarget[:,3] = Ident[:,4] 60 | 61 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 62 | 63 | # Compute Ra*utarget 64 | rot1 = Diagonal(exp.(im*omega1*T)) 65 | 66 | # target in the rotating frame 67 | vtarget = rot1*utarget 68 | 69 | Nfreq = 2 # number of carrier frequencies 70 | 71 | Random.seed!(2456) 72 | 73 | # setup drift Hamiltonian 74 | number = Diagonal(collect(0:Ntot-1)) 75 | 76 | H0 = -0.5*(2*pi)*xa* (number*number - number) 77 | 78 | # lowering matrix 79 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 80 | # raising matrix 81 | adag = transpose(amat) # raising operator matrix 82 | 83 | Hsym_ops=[ Array(amat+adag), Array(adag*amat) ] 84 | Hanti_ops=[ Array(amat-adag), Array(zeros(Ntot,Ntot)) ] 85 | 86 | H0 = Array(H0) 87 | 88 | Nctrl = length(Hsym_ops) 89 | 90 | # setup carrier frequencies 91 | om = zeros(Nctrl,Nfreq) 92 | 93 | @assert(Nfreq==1 || Nfreq==2 || Nfreq==3) 94 | if Nfreq == 2 95 | om[1:Nctrl,2] .= -2.0*pi*xa # coupling freq for both ctrl funcs (re/im) 96 | elseif Nfreq == 3 97 | om[:,2] .= -2.0*pi*xa # 1st ctrl, re 98 | om[:,3] .= -4.0*pi*xa # coupling freq for both ctrl funcs (re/im) 99 | end 100 | 101 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 102 | if verbose 103 | println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 104 | end 105 | 106 | # max parameter amplitude 107 | maxpar = 0.08 108 | max_flux = 2*pi*5.0 109 | # max_flux = maxpar 110 | 111 | # Initial conditions 112 | Ident = Matrix{Float64}(I, Ntot, Ntot) 113 | U0 = Ident[1:Ntot,1:N] 114 | 115 | # setup the initial parameter vector, either randomized or from file 116 | startFromScratch = true # true 117 | startFile = "flux-pcof-opt-alpha-0.5.dat" 118 | useBarrier = true 119 | 120 | if startFromScratch 121 | # D1 smaller than 3 does not work 122 | D1 = 30 # Number of B-spline coefficients per frequency, sin/cos and real/imag 123 | nCoeff = 2*Nctrl*Nfreq*D1 124 | pcof0 = zeros(nCoeff) 125 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 126 | else 127 | # the data on the startfile must be consistent with the setup! 128 | # use if you want to have initial coefficients read from file 129 | pcof0 = vec(readdlm(startFile)) 130 | nCoeff = length(pcof0) 131 | D1 = div(nCoeff, 2*Nctrl*Nfreq) # factor '2' is for sin/cos 132 | 133 | nCoeff = 2*Nctrl*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 134 | 135 | if verbose 136 | println("*** Starting from B-spline coefficients in file: ", startFile) 137 | end 138 | end 139 | 140 | # Estimate time step for simulation 141 | nsteps = Juqbox.calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxpar, max_flux]) 142 | if verbose 143 | println( "# time steps: ", nsteps) 144 | end 145 | 146 | # setup the simulation parameters 147 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 148 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, use_sparse=use_sparse) 149 | 150 | # Quiet mode for testing 151 | params.quiet = !verbose 152 | 153 | #Tikhonov regularization coefficients 154 | params.tik0 = 0.1 155 | 156 | params.traceInfidelityThreshold = 1e-5 157 | 158 | # Set bounds on coefficients 159 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params,D1,[maxpar, max_flux]) 160 | 161 | 162 | # For ipopt 163 | maxIter = 100 # optional argument 164 | lbfgsMax = 250 # optional argument 165 | 166 | if verbose 167 | println("*** Settings ***") 168 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 169 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 170 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 171 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 172 | println("Max parameter amplitudes: maxpar = ", maxpar) 173 | println("Tikhonov coefficient: tik0 = ", params.tik0) 174 | end 175 | 176 | wa = Juqbox.Working_Arrays(params, nCoeff) 177 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax) 178 | 179 | #uncomment to run the gradient checker for the initial pcof 180 | #= 181 | if @isdefined addOption 182 | addOption( prob, "derivative_test", "first-order"); # for testing the gradient 183 | else 184 | AddIpoptStrOption( prob, "derivative_test", "first-order") 185 | end 186 | =# 187 | 188 | # experiment with scale factors 189 | addOption( prob, "nlp_scaling_method", "user-scaling"); 190 | 191 | if verbose 192 | println("Initial coefficient vector stored in 'pcof0'") 193 | end 194 | -------------------------------------------------------------------------------- /test/cases/cnot3.dat: -------------------------------------------------------------------------------- 1 | 2.8578056023274e-04 2 | 1.6893736898215e-04 3 | 4.6644429748983e-04 4 | 4.4814782769030e-04 5 | 3.0997750499015e-04 6 | 5.2746979080789e-05 7 | 4.3729874150164e-05 8 | 4.9714326878205e-04 9 | 4.4156623410155e-05 10 | 4.1793669518407e-04 11 | 3.8755325589291e-04 12 | 2.9339194889325e-04 13 | 9.1413032134246e-05 14 | 4.4273784540054e-04 15 | 2.0257015246387e-04 16 | 3.1698500212307e-04 17 | 1.9777260876174e-04 18 | 4.1852908033537e-04 19 | 1.4201268748675e-04 20 | 6.2339711907260e-05 21 | 3.9557968182152e-04 22 | 3.1537839901131e-04 23 | 2.2937449936147e-04 24 | 3.1673131194802e-04 25 | 3.9836271534275e-04 26 | 2.3258903017290e-04 27 | 3.6943309026684e-04 28 | 4.2980833937251e-05 29 | 3.3669316848825e-04 30 | 3.4765278227104e-05 31 | 3.1947897279128e-04 32 | 1.0341640040306e-04 33 | 2.4818855853534e-04 34 | 2.3234318043369e-04 35 | 1.5243052229996e-05 36 | 2.3899263391996e-04 37 | 4.6063485563299e-04 38 | 4.8401067670924e-04 39 | 5.5070380184912e-05 40 | 9.9065882470726e-05 41 | 3.5357427816621e-04 42 | 2.7235612641094e-05 43 | 4.1051320095811e-04 44 | 3.5741557525826e-04 45 | 1.6682143862475e-04 46 | 4.3204245225946e-04 47 | 6.0047177457121e-05 48 | 7.7268940094686e-05 49 | 4.6009571702108e-04 50 | 4.2945711728962e-04 51 | 4.6264891155079e-04 52 | 1.5229324925783e-04 53 | 4.4313203208365e-04 54 | 2.8910548682701e-04 55 | 3.0044696337616e-04 56 | 1.1248666411066e-04 57 | 3.2754353698375e-04 58 | 2.4541430668403e-05 59 | 2.9857531008997e-04 60 | 3.9670100752208e-04 61 | 7.9721047675886e-06 62 | 2.7065575571110e-04 63 | 3.2250093276153e-04 64 | 2.5261802724556e-05 65 | 2.8276303189817e-04 66 | 2.0284966051201e-05 67 | 1.6794817739004e-04 68 | 2.7179885392722e-04 69 | 4.9580532918262e-04 70 | 6.4011016475250e-05 71 | 4.4333919342582e-04 72 | 3.9062266776159e-04 73 | 7.4112408681336e-05 74 | 1.3455726070198e-04 75 | 1.5771415987087e-04 76 | 2.7793266930496e-04 77 | 2.5358365728786e-04 78 | 1.0752355990281e-04 79 | 8.2034011507890e-05 80 | 6.8291356582655e-05 81 | 2.0636097223243e-04 82 | 4.3121171083676e-04 83 | 2.1505283443090e-04 84 | 4.0541866639317e-04 85 | 4.6180903672955e-04 86 | 4.8354152789378e-04 87 | 4.7143857352856e-04 88 | 1.0818194761138e-04 89 | 2.6360096927848e-05 90 | 4.5664237397523e-04 91 | 4.6295020420390e-04 92 | 3.7887309291325e-04 93 | 2.5737205065161e-04 94 | 3.4464261555011e-04 95 | 8.3045737927864e-05 96 | 1.9116594231061e-04 97 | 1.6498813952274e-04 98 | 3.5822785164063e-04 99 | 5.6024797543930e-05 100 | 1.0434260732502e-04 101 | 1.5180100097600e-04 102 | 7.2816735757355e-05 103 | 3.3468631039542e-04 104 | 3.6085295700072e-04 105 | 1.3502314520690e-04 106 | 1.7229045395248e-04 107 | 2.6366476993476e-04 108 | 3.7943284993715e-04 109 | 4.2873494582136e-04 110 | 3.8102497083713e-04 111 | 2.7256234548366e-04 112 | 2.0073084083167e-04 113 | 4.2450264644659e-04 114 | 4.2626601376416e-04 115 | 1.0274426738462e-04 116 | 1.2694280474237e-04 117 | 4.6927428935926e-04 118 | 3.5280906395965e-05 119 | 1.3533410973805e-04 120 | 3.1734495262583e-04 121 | 2.9613798651846e-04 122 | 2.8850938147394e-04 123 | 1.4160853649209e-04 124 | 1.9335970125676e-04 125 | 1.1970202977064e-04 126 | 4.6695636683994e-04 127 | 2.6050525434957e-04 128 | 4.2580448499260e-04 129 | 4.6335023484995e-04 130 | 3.2546927297006e-04 131 | 1.7321985452969e-04 132 | 4.8753089520879e-04 133 | 4.7756766315694e-04 134 | 1.1711696678694e-04 135 | 1.8090704225818e-04 136 | 2.7330210214200e-04 137 | 2.7119446877864e-04 138 | 4.5373365603934e-04 139 | 7.3856851403413e-05 140 | 2.7631014216344e-04 141 | 4.5536343178347e-04 142 | 8.7567199756811e-05 143 | 2.9470039797596e-04 144 | 3.7832038159452e-04 145 | 4.9044363590694e-04 146 | 2.6626262778372e-04 147 | 3.2169478550539e-05 148 | 4.7266422896446e-04 149 | 5.7911051318188e-05 150 | 4.8918022232364e-04 151 | 2.6986101562680e-04 152 | 4.0597196074759e-04 153 | 1.1492569237977e-04 154 | 2.4206332563060e-04 155 | 2.1130175300038e-04 156 | 2.2741914617571e-04 157 | 2.7696666111459e-04 158 | 1.6443972155063e-04 159 | 3.3017763701461e-04 160 | 3.9779861953791e-05 161 | 4.8345902969113e-04 162 | 1.8215496653103e-04 163 | 4.2129294665397e-04 164 | 4.6644541234999e-04 165 | 1.9858093314410e-04 166 | 2.5436278404774e-04 167 | 3.1458499144284e-04 168 | 1.8444649942778e-04 169 | 4.6602521282344e-05 170 | 4.9911175357370e-04 171 | 4.8460584318754e-04 172 | 5.2672450350681e-05 173 | 2.4633120112940e-05 174 | 4.6400712595247e-04 175 | 8.8192535347181e-06 176 | 4.1389825073083e-04 177 | 2.3815299366344e-04 178 | 1.2112742985216e-04 179 | 4.1108010483470e-04 180 | 5.4676397450828e-05 181 | 2.3590378830959e-04 182 | 3.2593143199579e-04 183 | 2.3443007210379e-04 184 | 3.6349385851415e-05 185 | 3.4321240108307e-04 186 | 3.4094026703707e-04 187 | 1.1303563422616e-04 188 | 2.0322678474822e-04 189 | 1.3197770003455e-04 190 | 2.7882663493696e-04 191 | 4.2513512220369e-05 192 | 2.4067715270440e-04 193 | 2.4785262858020e-05 194 | 4.2231618804875e-04 195 | 4.0761949854018e-04 196 | 4.1695451341255e-04 197 | 2.6505038515897e-04 198 | 1.1093302893409e-04 199 | 7.8784918762609e-05 200 | 1.9981936311190e-04 201 | 3.3856280968436e-04 202 | 3.8779432529058e-04 203 | 4.3433884420610e-04 204 | 1.8622197040707e-04 205 | 3.2418375595897e-04 206 | 4.6571483055709e-04 207 | 2.4692409023014e-04 208 | 3.4385500668158e-04 209 | 1.3587777483684e-04 210 | 2.0858152845834e-04 211 | 4.1970087325227e-04 212 | 1.1757537690443e-04 213 | 1.3006232151896e-04 214 | 4.5199571425323e-05 215 | 4.4577170374603e-04 216 | 7.2324270414343e-05 217 | 1.8030645537834e-04 218 | 2.9838314233772e-05 219 | 2.9264797137758e-04 220 | 2.3114776396316e-04 221 | 4.9309021883283e-04 222 | 2.4016757023625e-04 223 | 8.8857923132026e-05 224 | 2.3461551891832e-04 225 | 5.5693881370091e-05 226 | 5.4852140713936e-05 227 | 3.5705481608767e-04 228 | 1.0500652305930e-04 229 | 4.9230889946830e-04 230 | 3.3443353873506e-04 231 | 3.8179394565789e-04 232 | 1.9812572295873e-04 233 | 1.4078548556735e-04 234 | 2.6516655483276e-04 235 | 1.7740790580245e-04 236 | 4.6427509017983e-04 237 | 3.9361983929508e-04 238 | 1.7973846037834e-04 239 | 2.7887073093693e-04 240 | 4.3823848117511e-04 241 | 4.2302127135458e-04 242 | 4.4175906653638e-04 243 | 4.2641594777013e-04 244 | 1.5183281692371e-04 245 | 3.9640116194841e-04 246 | 1.0212062899008e-04 247 | 4.5692491917125e-04 248 | 1.3571565271125e-04 249 | 3.8797620468609e-04 250 | 3.8064043125361e-04 251 | 2.0972811588580e-04 252 | 2.0124392369663e-04 253 | 1.3925652770963e-04 254 | 4.0509387797461e-04 255 | 3.1752622687063e-04 256 | 3.4621255085025e-04 257 | 4.0541901715623e-04 258 | 1.8985840289812e-04 259 | 4.9787409735381e-04 260 | 4.1473082765084e-05 261 | 3.2728901174257e-04 262 | 2.6881564856687e-04 263 | 3.5966308733736e-05 264 | 1.4062706220900e-04 265 | 4.0216469819769e-04 266 | 2.7985622728201e-04 267 | 3.7876391281571e-04 268 | 3.3705927259806e-04 269 | 3.5685118894855e-04 270 | 3.2151801436576e-04 271 | -------------------------------------------------------------------------------- /examples/rabi-lab.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem on a 3 | single qubit with 2 energy levels (and no guard states) 4 | where the analytical solution is a constant control 5 | function, i.e. a Rabi oscillator. The drift Hamiltonian in 6 | the rotating frame is 7 | H_0 = - 0.5*ξ_a(a^†a^†aa), 8 | where a is the annihilation operator for the qubit. Here 9 | the control Hamiltonian includes the usual symmetric and 10 | anti-symmetric terms 11 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†), 12 | which come from the rotating frame approximation and hence 13 | we refer to these as "coupled" controls. For this 14 | example we evolve the state forward a full period T=2π. The 15 | parameters for this example are: 16 | ω_a = 2π × 0.0 Grad/s, 17 | ξ_a = 2π × 2(0.1099) Grad/s. 18 | We use the usual Bsplines (no carrier waves) in this 19 | example. 20 | ==========================================================# 21 | using LinearAlgebra 22 | using Plots 23 | #pyplot() 24 | using FFTW 25 | using DelimitedFiles 26 | using Printf 27 | using Ipopt 28 | using Random 29 | 30 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 31 | 32 | using Juqbox 33 | 34 | N = 2 35 | 36 | Nguard = 0 37 | Ntot = N + Nguard 38 | 39 | samplerate = 32 # default number of time steps per unit time 40 | casename = "rabi" # base file name (used in optimize-once.jl) 41 | 42 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 43 | fa = 5.0 44 | xa = 2* 0.1099 45 | rot_freq = [fa] # Rotational frequencies 46 | 47 | # period of oscillation 48 | Tperiod = 100.0 # ns # 2*pi 49 | 50 | # duration 51 | T = Tperiod # Tperiod/4 52 | 53 | utarget = Matrix{ComplexF64}(I, Ntot, N) 54 | vtarget = Matrix{ComplexF64}(I, Ntot, N) 55 | 56 | # Rabi target (one period) 57 | 58 | theta = pi/4 # phase angle 59 | aOmega = pi/Tperiod 60 | Omega = (cos(theta) + 1im*sin(theta)) * aOmega 61 | println("Amplitude |Omega| = ", aOmega, " phase angle theta = ", theta) 62 | 63 | # unitary target matrix 64 | utarget[1,1] = cos(aOmega*T) 65 | utarget[2,1] = -(sin(theta) + 1im*cos(theta))*sin(aOmega*T) 66 | utarget[1,2] = (sin(theta) - 1im*cos(theta))*sin(aOmega*T) 67 | utarget[2,2] = cos(aOmega*T) 68 | 69 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], rot_freq) 70 | 71 | # Compute Ra*utarget 72 | rot1 = Diagonal(exp.(im*omega1*T)) 73 | 74 | # target in the rotating frame 75 | # vtarget = rot1*utarget 76 | 77 | # target in lab frame 78 | vtarget = utarget 79 | 80 | # setup ansatz for control functions 81 | use_bcarrier = true # new Bcarrier allows a constant control function 82 | 83 | Nctrl = 1 84 | Nosc = Nctrl 85 | Nfreq = 1 # number of carrier frequencies 86 | 87 | Random.seed!(2456) 88 | # initial parameter guess 89 | 90 | # setup carrier frequencies 91 | om = zeros(Nctrl,Nfreq) 92 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 93 | println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 94 | 95 | # setup drift Hamiltonian 96 | number = Diagonal(collect(0:Ntot-1)) 97 | 98 | H0 = 2*pi* (fa*number - 0.5*xa* (number*number - number) ) 99 | println("Drift Hamiltonian/2*pi: ", H0) 100 | 101 | # lowering matrix 102 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 103 | # raising matrix 104 | adag = transpose(amat) # raising operator matrix 105 | 106 | # max parameter amplitude 107 | maxpar = 1.0*aOmega/Nfreq 108 | 109 | # dense matrices 110 | Hunc_ops=[Array(amat + adag)] 111 | H0 = Array(H0) 112 | 113 | # Estimate time step 114 | Pmin = 100 115 | nsteps = calculate_timestep(T, H0, Hunc_ops, [maxpar], Pmin) 116 | println("Duration = ", T, " # time steps per min-period, P = ", Pmin, " # time steps: ", nsteps) 117 | 118 | # Initial conditions 119 | Ident = Matrix{Float64}(I, Ntot, Ntot) 120 | U0 = Ident[1:Ntot,1:N] 121 | 122 | 123 | 124 | # setup the simulation parameters 125 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 126 | Hconst=H0, Hunc_ops=Hunc_ops) 127 | 128 | # setup the initial parameter vector, either randomized or from file 129 | startFromScratch = false # true 130 | startFile = "drives/rabi-pcof-opt-t100.jld2" 131 | 132 | if startFromScratch 133 | # D1 smaller than 3 does not work 134 | D1 = 3 # Number of B-spline coefficients per frequency, sin/cos and real/imag 135 | nCoeff = 2*Nosc*Nfreq*D1 # factor '2' is for sin/cos 136 | pcof0 = zeros(nCoeff) 137 | 138 | if Nfreq == 1 # analytical solution 139 | pcof0[1:D1] .= aOmega*cos(theta) # real coefficients 140 | pcof0[D1+1:2*D1] .= aOmega*sin(theta) # imag coefficients 141 | println("*** Starting from constant pcof with (Re/Im) amplitudes: ", aOmega*cos(theta), " ", aOmega*sin(theta) ) 142 | else 143 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 144 | println("*** Starting from random pcof with amplitude ", maxpar*0.1) 145 | end 146 | else 147 | # the data on the startfile must be consistent with the setup! 148 | # use if you want to have initial coefficients read from file 149 | pcof0 = read_pcof(startFile) 150 | nCoeff = length(pcof0) 151 | D1 = div(nCoeff, 2*Nosc*Nfreq) # factor '2' is for sin/cos 152 | nCoeff = 2*Nosc*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 153 | println("*** Starting from B-spline coefficients in file: ", startFile) 154 | end 155 | 156 | # min and max coefficient values 157 | minCoeff = -maxpar*ones(nCoeff); 158 | maxCoeff = maxpar*ones(nCoeff); 159 | 160 | # For ipopt 161 | maxIter = 150 # optional argument 162 | lbfgsMax = 250 # optional argument 163 | 164 | println("*** Settings ***") 165 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 166 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 167 | if use_bcarrier 168 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 169 | else 170 | println("Using regular B-spline basis functions") 171 | end 172 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 173 | println("Max parameter amplitudes: maxpar = ", maxpar) 174 | println("Tikhonov coefficients: tik0 = ", params.tik0) 175 | 176 | # Allocate all working arrays 177 | wa = Juqbox.Working_Arrays(params, nCoeff) 178 | 179 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 180 | 181 | #uncomment to run the gradient checker for the initial pcof 182 | #= 183 | if @isdefined addOption 184 | addOption( prob, "derivative_test", "first-order"); # for testing the gradient 185 | else 186 | AddIpoptStrOption( prob, "derivative_test", "first-order") 187 | end 188 | =# 189 | 190 | println("Initial coefficient vector stored in 'pcof0'") 191 | 192 | # evaluate objective function 193 | objf, uhist, trfid = traceobjgrad(pcof0, params, wa, true, false); 194 | println("Trace fidelity: ", trfid); 195 | -------------------------------------------------------------------------------- /examples/rabi-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem on a 3 | single qubit with 2 energy levels (and no guard states) 4 | where the analytical solution is a constant control 5 | function, i.e. a Rabi oscillator. The drift Hamiltonian in 6 | the rotating frame is 7 | H_0 = - 0.5*ξ_a(a^†a^†aa), 8 | where a is the annihilation operator for the qubit. Here 9 | the control Hamiltonian includes the usual symmetric and 10 | anti-symmetric terms 11 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†), 12 | which come from the rotating frame approximation and hence 13 | we refer to these as "coupled" controls. For this 14 | example we evolve the state forward a full period T=2π. The 15 | parameters for this example are: 16 | ω_a = 2π × 0.0 Grad/s, 17 | ξ_a = 2π × 2(0.1099) Grad/s. 18 | We use the usual Bsplines (no carrier waves) in this 19 | example. 20 | ==========================================================# 21 | using LinearAlgebra 22 | using Plots 23 | using FFTW 24 | using DelimitedFiles 25 | using Printf 26 | using Ipopt 27 | using Random 28 | 29 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 30 | 31 | using Juqbox 32 | 33 | N = 2 34 | 35 | Nguard = 0 36 | Ntot = N + Nguard 37 | 38 | samplerate = 32 # default number of time steps per unit time 39 | casename = "rabi" # base file name (used in optimize-once.jl) 40 | 41 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 42 | fa = 5.0 43 | xa = 2* 0.1099 44 | rot_freq = [fa] # Rotational frequencies 45 | 46 | # period of oscillation 47 | Tperiod = 100.0 # ns # 2*pi 48 | 49 | # duration 50 | T = Tperiod # Tperiod/4 51 | 52 | utarget = Matrix{ComplexF64}(I, Ntot, N) 53 | vtarget = Matrix{ComplexF64}(I, Ntot, N) 54 | 55 | # Rabi target (one period) 56 | 57 | theta = pi/4 # phase angle 58 | aOmega = pi/Tperiod 59 | Omega = (cos(theta) + 1im*sin(theta)) * aOmega 60 | println("Amplitude |Omega| = ", aOmega, " phase angle theta = ", theta) 61 | 62 | # unitary target matrix 63 | utarget[1,1] = cos(aOmega*T) 64 | utarget[2,1] = -(sin(theta) + 1im*cos(theta))*sin(aOmega*T) 65 | utarget[1,2] = (sin(theta) - 1im*cos(theta))*sin(aOmega*T) 66 | utarget[2,2] = cos(aOmega*T) 67 | 68 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], rot_freq) 69 | 70 | # Compute Ra*utarget 71 | rot1 = Diagonal(exp.(im*omega1*T)) 72 | 73 | # target in the rotating frame 74 | vtarget = rot1*utarget 75 | 76 | # target in lab frame 77 | #vtarget = utarget 78 | 79 | Nctrl = 1 80 | Nfreq = 1 # number of carrier frequencies 81 | 82 | Random.seed!(2456) 83 | # initial parameter guess 84 | 85 | # setup carrier frequencies 86 | om = zeros(Nctrl,Nfreq) 87 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 88 | println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 89 | 90 | # setup drift Hamiltonian 91 | number = Diagonal(collect(0:Ntot-1)) 92 | 93 | H0 = -0.5*(2*pi)*xa* (number*number - number) 94 | println("Drift Hamiltonian/2*pi: ", H0) 95 | 96 | # lowering matrix 97 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 98 | # raising matrix 99 | adag = transpose(amat) # raising operator matrix 100 | 101 | # max parameter amplitude 102 | maxpar = 1.0*aOmega/Nfreq 103 | 104 | # package the lowering and raising matrices together into an one-dimensional array of two-dimensional arrays 105 | # Here we choose dense or sparse representation 106 | # NOTE: the above eigenvalue calculation does not work with sparse arrays! 107 | 108 | # sparse matrices 109 | # Hsym_ops=[sparse(amat+adag)] 110 | # Hanti_ops=[sparse(amat-adag)] 111 | # H0 = sparse(H0) 112 | 113 | # dense matrices 114 | Hsym_ops=[Array(amat + adag)] 115 | Hanti_ops=[Array(amat - adag)] 116 | H0 = Array(H0) 117 | 118 | # Estimate time step 119 | Pmin = 80 120 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxpar], Pmin) 121 | println("Duration = ", T, " # time steps per min-period, P = ", Pmin, " # time steps: ", nsteps) 122 | 123 | # Initial conditions 124 | Ident = Matrix{Float64}(I, Ntot, Ntot) 125 | U0 = Ident[1:Ntot,1:N] 126 | 127 | # setup the simulation parameters 128 | Integrator_id = 1 129 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 130 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, Integrator = Integrator_id) 131 | if Integrator_id == 2 132 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 133 | params.linear_solver = linear_solver 134 | end 135 | # setup the initial parameter vector, either randomized or from file 136 | startFromScratch = true # true 137 | startFile = "rabi-pcof-opt-alpha-0.5.dat" 138 | 139 | if startFromScratch 140 | # D1 smaller than 3 does not work 141 | D1 = 3 # Number of B-spline coefficients per frequency, sin/cos and real/imag 142 | nCoeff = 2*Nctrl*Nfreq*D1 # factor '2' is for sin/cos 143 | pcof0 = zeros(nCoeff) 144 | 145 | if Nfreq == 1 # analytical solution 146 | pcof0[1:D1] .= aOmega*cos(theta) # real coefficients 147 | pcof0[D1+1:2*D1] .= aOmega*sin(theta) # imag coefficients 148 | println("*** Starting from constant pcof with (Re/Im) amplitudes: ", aOmega*cos(theta), " ", aOmega*sin(theta) ) 149 | else 150 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 151 | println("*** Starting from random pcof with amplitude ", maxpar*0.1) 152 | end 153 | else 154 | # the data on the startfile must be consistent with the setup! 155 | # use if you want to have initial coefficients read from file 156 | pcof0 = vec(readdlm(startFile)) 157 | nCoeff = length(pcof0) 158 | D1 = div(nCoeff, 2*Nctrl*Nfreq) # factor '2' is for sin/cos 159 | nCoeff = 2*Nctrl*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 160 | println("*** Starting from B-spline coefficients in file: ", startFile) 161 | end 162 | 163 | # min and max coefficient values 164 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params, D1, [maxpar]) 165 | 166 | # For ipopt 167 | maxIter = 150 # optional argument 168 | lbfgsMax = 250 # optional argument 169 | 170 | println("*** Settings ***") 171 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 172 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 173 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 174 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 175 | println("Max parameter amplitudes: maxpar = ", maxpar) 176 | println("Tikhonov coefficients: tik0 = ", params.tik0) 177 | 178 | # Allocate all working arrays 179 | if params.Integrator_id == 1 180 | wa = Juqbox.Working_Arrays(params, nCoeff) 181 | elseif params.Integrator_id == 2 182 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 183 | end 184 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 185 | 186 | #uncomment to run the gradient checker for the initial pcof 187 | #= 188 | if @isdefined addOption 189 | addOption( prob, "derivative_test", "first-order"); # for testing the gradient 190 | else 191 | AddIpoptStrOption( prob, "derivative_test", "first-order") 192 | end 193 | =# 194 | 195 | println("Initial coefficient vector stored in 'pcof0'") 196 | -------------------------------------------------------------------------------- /test/cases/swap02-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a |0⟩ to |2⟩ swap gate on a single qudit with 3 energy 4 | levels (and 1 guard state). The drift Hamiltonian in the 5 | rotating frame is 6 | H0 = 2π*diagm([0 0 -2.2538e-1 -7.0425e-1]). 7 | Here the control Hamiltonian includes the usual symmetric 8 | and anti-symmetric terms 9 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†), 10 | where a is the annihilation operator for the qudit. 11 | The problem parameters for this example are: 12 | ω_a = 2π × 4.09947 Grad/s, 13 | ξ_a = 2π × 2.2538e-01 Grad/s. 14 | We use Bsplines with carrier waves with frequencies 15 | 0, ξ_a Grad/s. 16 | ==========================================================# 17 | using LinearAlgebra 18 | #using Plots 19 | #pyplot() 20 | #using FFTW 21 | using DelimitedFiles 22 | using Printf 23 | #using Ipopt 24 | using Random 25 | 26 | #Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 27 | 28 | #import Juqbox 29 | 30 | verbose = false 31 | Nosc=1 # Number of oscillators 32 | 33 | N = 3 # 4 # Number of essential energy levels 34 | Nguard = 1 # 0 # Number of guard/forbidden energy levels 35 | Ntot = N + Nguard # Total number of energy levels 36 | 37 | samplerate = 32 # for output files 38 | casename = "swap02" # naming output files" 39 | 40 | T = 150.0 # 150.0 # 200.0 # 100.0 # Duration of gate 41 | 42 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 43 | freq_alice=[0, 4.09947, 3.87409, 3.6206] # GHz 44 | fa = freq_alice[2] 45 | @assert(Ntot <= 4) # we don't know any higher frequencies 46 | 47 | utarget = zeros(ComplexF64,Ntot,N) 48 | 49 | # 0> to |2> swap gate 50 | if N >= 3 51 | utarget[1,1] = 0 #1/sqrt(2) 52 | utarget[2,1] = 0 53 | utarget[3,1] = 1 #1/sqrt(2) 54 | # 55 | utarget[1,2] = 0 56 | utarget[2,2] = 1 57 | utarget[3,2] = 0 58 | # 59 | utarget[1,3] = 1 #1/sqrt(2) 60 | utarget[2,3] = 0 61 | utarget[3,3] = 0 #-1/sqrt(2) 62 | # 63 | end 64 | 65 | if N==4 66 | utarget[4,4] = 1 67 | end 68 | 69 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [freq_alice[2]]) 70 | 71 | # Compute Ra*utarget 72 | rot1 = Diagonal(exp.(im*omega1*T)) 73 | 74 | # target in the rotating frame 75 | vtarget = rot1*utarget 76 | 77 | startFromScratch = false # false 78 | startFile = "cases/swap02.dat" 79 | use_bcarrier = true 80 | 81 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 82 | fa = 4.10595 83 | xa = 2* 0.1099 84 | 85 | number = Diagonal(collect(0:Ntot-1)) 86 | 87 | H0 = -0.5*(2*pi)*xa* (number*number - number) # xa is in GHz 88 | 89 | # lowering matrix 90 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard loweing matrix 91 | adag = Array(transpose(amat)); 92 | Hsym_ops=[Array(amat + adag)] 93 | Hanti_ops=[Array(amat - adag)] 94 | H0 = Array(H0) 95 | 96 | # END NEW SETUP 97 | 98 | Ncoupled = length(Hsym_ops) # Number of paired Hamiltonians 99 | Nfreq= 2 # number of carrier frequencies 3 gives a cleaner sol than 2 100 | 101 | # setup carrier frequencies 102 | om = zeros(Ncoupled,Nfreq) 103 | # seg=1 is the real, seg=2 is the imaginary. Use the same frequencies for both 104 | if Nfreq == 3 105 | om[1:Ncoupled,2] .= H0[3,3] 106 | om[1:Ncoupled,3] .= H0[4,4] - H0[3,3] 107 | elseif Nfreq == 2 108 | om[1:Ncoupled,2] .= H0[3,3] 109 | end 110 | #println("Carrier frequencies [GHz]: ", om[1,:]./(2*pi)) 111 | 112 | #max amplitude (in angular frequency) 2*pi*GHz 113 | # NOTE: optimize-once.jl uses maxpar to generate filenames 114 | maxpar =2*pi*0.0132/Nfreq/2 # attempt to limit lab frame amplitude to 0.0132 = 0.5/6/2/pi 115 | 116 | # Estimate time step 117 | K1 = H0 + maxpar.*( amat + amat') + 1im* maxpar.*(amat - amat') 118 | lamb = eigvals(Array(K1)) 119 | maxeig = maximum(abs.(lamb)) 120 | 121 | Pmin = 80 122 | samplerate1 = maxeig*Pmin/(2*pi) 123 | nsteps = ceil(Int64, T*samplerate1) 124 | #println("Duration T = ", T, " number of time-steps = ", nsteps) 125 | 126 | # Initial conditions 127 | Ident = Matrix{Float64}(I, Ntot, Ntot) 128 | U0 = Ident[1:Ntot,1:N] 129 | 130 | # try the new Juqbox2 module 131 | Integrator_id = 1 132 | 133 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=[freq_alice[2]], 134 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, Integrator = Integrator_id) 135 | 136 | # Quiet mode for testing 137 | params.quiet = true 138 | 139 | if params.Integrator_id == 2 140 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 141 | params.linear_solver = linear_solver 142 | end 143 | # set random number seed to make algorithm deterministic 144 | Random.seed!(2456) 145 | 146 | # initial parameter guess 147 | if startFromScratch 148 | D1 = 10 # Number of B-spline coefficients per frequency, sin/cos and real/imag 149 | nCoeff = 2*Ncoupled*Nfreq*D1 # factor '2' is for sin/cos 150 | #pcof0 = zeros(nCoeff) 151 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 152 | # println("*** Starting from random pcof with amplitude ", maxpar*0.1) 153 | else 154 | # use if you want to have initial coefficients read from file 155 | pcof0 = vec(readdlm(startFile)) 156 | # println("*** Starting from B-spline coefficients in file: ", startFile) 157 | nCoeff = length(pcof0) 158 | D1 = div(nCoeff, 2*Ncoupled*Nfreq) # number of B-spline coeff per control function 159 | end 160 | 161 | # min and max coefficient values 162 | useBarrier = true 163 | minCoeff = -maxpar*ones(nCoeff); 164 | maxCoeff = maxpar*ones(nCoeff); 165 | 166 | if verbose 167 | println("*** Settings ***") 168 | println("Resonant frequencies [GHz] = ", freq_alice[1:Ntot]) 169 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 170 | if use_bcarrier 171 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 172 | else 173 | println("Using regular B-spline basis functions") 174 | end 175 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 176 | println("Max parameter amplitudes: maxpar = ", maxpar) 177 | println("Tikhonov coefficients: tik0 = ", params.tik0) 178 | end 179 | 180 | # optional arguments to setup_ipopt_problem() 181 | maxIter = 50 182 | lbfgsMax = 250 183 | 184 | 185 | # Estimate number of terms in Neumann series for time stepping (Default 3) 186 | tol = eps(1.0); # machine precision 187 | Juqbox.estimate_Neumann!(tol, params, [maxpar]) 188 | 189 | # Allocate all working arrays 190 | if params.Integrator_id == 1 191 | wa = Juqbox.Working_Arrays(params, nCoeff) 192 | elseif params.Integrator_id == 2 193 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 194 | end 195 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax) 196 | 197 | # uncomment to run the gradient checker for the initial pcof 198 | # if @isdefined addOption 199 | # addOption( prob, "derivative_test", "first-order"); # for testing the gradient 200 | # else 201 | # AddIpoptStrOption( prob, "derivative_test", "first-order") 202 | # end 203 | 204 | 205 | #println("Initial coefficient vector stored in 'pcof0'") 206 | # grad_storage = zeros(size(pcof0)) 207 | 208 | 209 | # for i = 1:length(pcof0) 210 | # println("Finite difference for parameter: ", i) 211 | # perturb = zeros(size(pcof0)) 212 | # perturb[i] = 0.0000001 213 | # objfv, _, _ = traceobjgrad(pcof0, params, wa, false, false) 214 | # objfv2, _, _ = traceobjgrad(pcof0 + perturb, params, wa, false, false) 215 | # grad_storage[i] = (objfv2 - objfv)/0.0000001 216 | # end 217 | 218 | # save_object("swap02-ref-IMR.jld2", grad_storage) -------------------------------------------------------------------------------- /test/cases/flux-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a single qudit with 4 energy levels (and 2 4 | guard states) and showcases the use of uncoupled controls. 5 | The drift Hamiltonian in the rotating frame is 6 | H_0 = - 0.5*ξ_a(a^†a^†aa), 7 | where a is the annihilation operator for the qudit. Here 8 | the control Hamiltonian includes the usual symmetric and 9 | anti-symmetric terms 10 | H_s = p(t)(a + a^†), H_a = q(t)(a - a^†), 11 | which come from the rotating frame approximation and hence 12 | we refer to these as "coupled" controls. In 13 | addition, we consider a magnetic flux-tuning control term 14 | H_f = f(t) a^†a, 15 | which we refer to as an "uncoupled" control as it is 16 | invariant to the rotating frame approximation. The problem 17 | parameters for this example are from Jonathan and Pranav at 18 | UChicago: 19 | ω_a/2π = 5.0 GHz, 20 | ξ_a/2π = 0.2 GHz. 21 | We use Bsplines with carrier waves with carrier frequencies (rotating frame) 22 | 0, -ξ_a rad/ns. 23 | ==========================================================# 24 | using LinearAlgebra 25 | #using Plots 26 | #pyplot() 27 | #using FFTW 28 | using DelimitedFiles 29 | using Printf 30 | using Ipopt 31 | using Random 32 | using SparseArrays 33 | using JLD2 34 | 35 | #Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 36 | 37 | using Juqbox 38 | 39 | verbose = false 40 | N = 4 41 | Nguard = 2 42 | Ntot = N + Nguard 43 | 44 | samplerate = 64 # default number of time steps per unit time (plotting only) 45 | casename = "flux" # base file name (used in optimize-once.jl) 46 | 47 | # Set to false for dense matrix operations 48 | use_sparse = true 49 | 50 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 51 | fa = 5.0 # 4.1 52 | xa = 0.2 53 | rot_freq = [fa, fa] # Rotational frequencies for each control Hamiltonian 54 | 55 | # duration 56 | T = 11.0 # Tperiod/4 57 | 58 | Ident = Matrix{Float64}(I, Ntot, Ntot) 59 | utarget = Matrix{ComplexF64}(I, Ntot, N) 60 | vtarget = Matrix{ComplexF64}(I, Ntot, N) 61 | 62 | # CNOT target 63 | utarget[:,4] = Ident[:,3] 64 | utarget[:,3] = Ident[:,4] 65 | 66 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 67 | 68 | # Compute Ra*utarget 69 | rot1 = Diagonal(exp.(im*omega1*T)) 70 | 71 | # target in the rotating frame 72 | vtarget = rot1*utarget 73 | 74 | Nosc = 1 75 | Nfreq = 2 # number of carrier frequencies 76 | 77 | Random.seed!(2456) 78 | 79 | # setup drift Hamiltonian 80 | number = Diagonal(collect(0:Ntot-1)) 81 | 82 | H0 = -0.5*(2*pi)*xa* (number*number - number) 83 | 84 | # lowering matrix 85 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 86 | # raising matrix 87 | adag = transpose(amat) # raising operator matrix 88 | 89 | Hsym_ops=[ Array(amat+adag), Array(adag*amat) ] 90 | Hanti_ops=[ Array(amat-adag), Array(zeros(Ntot,Ntot)) ] 91 | 92 | H0 = Array(H0) 93 | 94 | Nctrl = length(Hsym_ops) 95 | 96 | # setup carrier frequencies 97 | om = zeros(Nctrl,Nfreq) 98 | 99 | @assert(Nfreq==1 || Nfreq==2 || Nfreq==3) 100 | if Nfreq == 2 101 | om[1:Nctrl,2] .= -2.0*pi*xa # coupling freq for both ctrl funcs (re/im) 102 | elseif Nfreq == 3 103 | om[:,2] .= -2.0*pi*xa # 1st ctrl, re 104 | om[:,3] .= -4.0*pi*xa # coupling freq for both ctrl funcs (re/im) 105 | end 106 | 107 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 108 | if verbose 109 | println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 110 | end 111 | 112 | # max parameter amplitude 113 | maxpar = 0.08 114 | max_flux = 2*pi*5.0 115 | # max_flux = maxpar 116 | 117 | # Initial conditions 118 | Ident = Matrix{Float64}(I, Ntot, Ntot) 119 | U0 = Ident[1:Ntot,1:N] 120 | 121 | # setup the initial parameter vector, either randomized or from file 122 | startFromScratch = false # true 123 | startFile = "cases/flux.dat" 124 | useBarrier = true 125 | 126 | if startFromScratch 127 | # D1 smaller than 3 does not work 128 | D1 = 30 # Number of B-spline coefficients per frequency, sin/cos and real/imag 129 | nCoeff = 2*Nctrl*Nfreq*D1 130 | pcof0 = zeros(nCoeff) 131 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 132 | else 133 | # the data on the startfile must be consistent with the setup! 134 | # use if you want to have initial coefficients read from file 135 | pcof0 = vec(readdlm(startFile)) 136 | nCoeff = length(pcof0) 137 | D1 = div(nCoeff, 2*Nctrl*Nfreq) # factor '2' is for sin/cos 138 | 139 | nCoeff = 2*Nctrl*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 140 | 141 | if verbose 142 | println("*** Starting from B-spline coefficients in file: ", startFile) 143 | end 144 | end 145 | 146 | # Estimate time step for simulation 147 | nsteps = Juqbox.calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxpar, max_flux]) 148 | if verbose 149 | println( "# time steps: ", nsteps) 150 | end 151 | 152 | Integrator_id = 1 153 | # setup the simulation parameters 154 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 155 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, use_sparse=use_sparse, Integrator = Integrator_id) 156 | # params = Juqbox.objparams([N], [Nguard], T, nsteps, U0, vtarget, om, H0, Hunc_ops) 157 | params.saveConvHist = true 158 | if Integrator_id == 2 159 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 160 | params.linear_solver = linear_solver 161 | 162 | end 163 | # Quiet mode for testing 164 | params.quiet = !verbose 165 | 166 | #Tikhonov regularization coefficients 167 | params.tik0 = 0.1 168 | 169 | params.traceInfidelityThreshold = 1e-5 170 | 171 | # Set bounds on coefficients 172 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params,D1,[maxpar, max_flux]) 173 | 174 | 175 | # For ipopt 176 | maxIter = 100 # optional argument 177 | lbfgsMax = 250 # optional argument 178 | 179 | if verbose 180 | println("*** Settings ***") 181 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 182 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 183 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 184 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 185 | println("Max parameter amplitudes: maxpar = ", maxpar) 186 | println("Tikhonov coefficient: tik0 = ", params.tik0) 187 | end 188 | 189 | if params.Integrator_id == 1 190 | wa = Juqbox.Working_Arrays(params, nCoeff) 191 | elseif params.Integrator_id == 2 192 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 193 | end 194 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax) 195 | 196 | # uncomment to run the gradient checker for the initial pcof 197 | #addOption( prob, "derivative_test", "first-order"); # for testing the gradient 198 | 199 | # experiment with scale factors 200 | if @isdefined addOption 201 | addOption( prob, "nlp_scaling_method", "user-scaling"); # for testing the gradient 202 | else 203 | AddIpoptStrOption( prob, "nlp_scaling_method", "user-scaling") 204 | end 205 | 206 | 207 | if verbose 208 | println("Initial coefficient vector stored in 'pcof0'") 209 | end 210 | 211 | # grad_storage = zeros(size(pcof0)) 212 | 213 | 214 | # for i = 1:length(pcof0) 215 | # println("Finite difference for parameter: ", i) 216 | # perturb = zeros(size(pcof0)) 217 | # perturb[i] = 0.0000001 218 | # objfv, _, _ = traceobjgrad(pcof0, params, wa, false, false) 219 | # objfv2, _, _ = traceobjgrad(pcof0 + perturb, params, wa, false, false) 220 | # grad_storage[i] = (objfv2 - objfv)/0.0000001 221 | # end 222 | 223 | # save_object("flux-ref-IMR.jld2", grad_storage) -------------------------------------------------------------------------------- /examples/Risk_Neutral/swap-02-risk-neutral.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a |0⟩ to |2⟩ swap gate on a single qudit with 3 energy 4 | levels (and 1 guard state). The drift Hamiltonian in the 5 | rotating frame is 6 | H0 = 2π*diagm([0 0 -2.2538e-1 -7.0425e-1]). 7 | Here the control Hamiltonian includes the usual symmetric 8 | and anti-symmetric terms 9 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†), 10 | where a is the annihilation operator for the qudit. 11 | The problem parameters for this example are: 12 | ω_a = 2π × 4.09947 Grad/s, 13 | ξ_a = 2π × 2.2538e-01 Grad/s. 14 | We use Bsplines with carrier waves with frequencies 15 | 0, ξ_a Grad/s. 16 | ==========================================================# 17 | using LinearAlgebra 18 | using Plots 19 | using FFTW 20 | using DelimitedFiles 21 | using Printf 22 | using Ipopt 23 | using Random 24 | using JLD2 25 | using FastGaussQuadrature 26 | 27 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 28 | 29 | using Juqbox # quantum control module 30 | 31 | # Default values for uniform distribution on [-ϵ/2,ϵ/2] 32 | if(!@isdefined(ep_max)) 33 | ep_max = 2*pi*3e-2 34 | end 35 | 36 | # Default number of quadrature nodes to evaluate the 37 | # expected value of the objective function via 38 | # Gaussian quadrature, i.e. 39 | # E[J] = ∑ w[k] J[x[k]] 40 | # where w,x are the weights and nodes on [-ϵ/2,ϵ/2] 41 | if(!@isdefined(nquad)) 42 | nquad = 20 43 | nquad = 1 44 | end 45 | nodes, weights = gausslegendre(nquad) 46 | 47 | # Map nodes to [-ϵ/2,ϵ/2] 48 | nodes .*= 0.5*ep_max 49 | weights .*= 0.5 50 | 51 | N = 3 # Number of essential energy levels 52 | Nguard = 1 # Number of guard/forbidden energy levels 53 | Ntot = N + Nguard # Total number of energy levels 54 | 55 | samplerate = 32 # for output files 56 | 57 | T = 300.0 # Duration of gate 58 | 59 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 60 | fa = 4.10336 61 | xa = 0.2198 62 | rot_freq = [fa] # Used to calculate the lab frame ctrl function 63 | 64 | # setup drift Hamiltonian 65 | number = Diagonal(collect(0:Ntot-1)) 66 | 67 | H0 = -0.5*(2*pi)*xa* (number*number - number) # xa is in GHz 68 | 69 | utarget = zeros(ComplexF64,Ntot,N) 70 | 71 | # 0> to |2> swap gate 72 | if N >= 3 73 | utarget[1,1] = 0 #1/sqrt(2) 74 | utarget[2,1] = 0 75 | utarget[3,1] = 1 #1/sqrt(2) 76 | # 77 | utarget[1,2] = 0 78 | utarget[2,2] = 1 79 | utarget[3,2] = 0 80 | # 81 | utarget[1,3] = 1 #1/sqrt(2) 82 | utarget[2,3] = 0 83 | utarget[3,3] = 0 #-1/sqrt(2) 84 | # 85 | end 86 | 87 | if N==4 88 | utarget[4,4] = 1 89 | end 90 | 91 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 92 | 93 | # Compute Ra*utarget 94 | rot1 = Diagonal(exp.(im*omega1*T)) 95 | 96 | # target in the lab frame 97 | vtarget = utarget 98 | 99 | startFromScratch = true 100 | # startFile is used when startFromScratch = false 101 | startFile="swap02-baseline-pcof-opt.jld2" # "swap02-pert-4em1-pcof-opt.jld2" 102 | 103 | usePrior = false # true 104 | priorFileName = startFile # Usually makes sense to also use the start file as the prior, but not required 105 | 106 | # setup drift Hamiltonian 107 | number = Diagonal(collect(0:Ntot-1)) 108 | 109 | # lowering matrix 110 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering matrix 111 | adag = Array(transpose(amat)); 112 | Hsym_ops=[Array(amat + adag)] 113 | Hanti_ops=[Array(amat - adag)] 114 | H0 = Array(H0) 115 | 116 | Ncoupled = length(Hsym_ops) # Number of paired Hamiltonians 117 | Nfreq= 2 # number of carrier frequencies 3 gives a cleaner sol than 2 118 | 119 | # setup carrier frequencies 120 | use_bcarrier = true 121 | om = zeros(Ncoupled,Nfreq) 122 | if use_bcarrier 123 | om[1:Ncoupled,2] .= -2.0*pi *xa # Note negative sign 124 | end 125 | println("Carrier frequencies [GHz]: ", om[1,:]./(2*pi)) 126 | println("H0: ", H0) 127 | 128 | maxctrl = 2*pi*1.2e-2 129 | 130 | #max amplitude (in angular frequency) 2*pi*GHz 131 | maxamp = zeros(Nfreq) 132 | if Nfreq >= 5 133 | const_fact = 1.0/(Nfreq-2) 134 | maxamp[1] = maxctrl*const_fact 135 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # max B-spline coefficient amplitude, factor 3.0 is ad hoc 136 | elseif Nfreq >= 4 137 | const_fact = 0.4 138 | maxamp[1] = maxctrl*const_fact 139 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # max B-spline coefficient amplitude, factor 3.0 is ad hoc 140 | elseif Nfreq >= 3 141 | const_fact = 0.45 142 | maxamp[1] = maxctrl*const_fact 143 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # max B-spline coefficient amplitude, factor 3.0 is ad hoc 144 | else 145 | maxamp .= maxctrl/Nfreq 146 | end 147 | 148 | maxpar = maximum(maxamp) 149 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxctrl]) 150 | println("# time steps: ", nsteps) 151 | 152 | # Initial conditions for basis 153 | U0 = initial_cond([N], [Nguard]) 154 | 155 | # params = Juqbox.parameters([N], [Nguard], T, nsteps, U0, vtarget, om, H0, Hsym_ops, Hanti_ops) 156 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 157 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, wmatScale=1.0) 158 | 159 | if usePrior 160 | Juqbox.setup_prior!(params, priorFileName) 161 | params.tik0 = 100.0 # increase Tikhonov regularization coefficient 162 | end 163 | 164 | Random.seed!(2456) 165 | 166 | # initial parameter guess 167 | if startFromScratch 168 | D1 = 12 # Number of B-spline coefficients per frequency, sin/cos and real/imag 169 | nCoeff = 2*Ncoupled*Nfreq*D1 # factor '2' is for sin/cos 170 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 171 | 172 | if(nquad == 1) 173 | D1 = 12 # Number of B-spline coefficients per frequency, sin/cos and real/imag 174 | nCoeff = 2*Ncoupled*Nfreq*D1 # factor '2' is for sin/cos 175 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 176 | end 177 | println("*** Starting from random pcof with amplitude ", maxpar*0.1) 178 | else 179 | # use if you want to have initial coefficients read from file 180 | @load startFile pcof 181 | pcof0 = pcof 182 | println("*** Starting from B-spline coefficients in file: ", startFile) 183 | nCoeff = length(pcof0) 184 | D1 = div(nCoeff, 2*Ncoupled*Nfreq) # number of B-spline coeff per control function 185 | end 186 | 187 | 188 | # min and max coefficient values (set first and last two to zero) 189 | useBarrier = true 190 | minCoeff, maxCoeff = Juqbox.assign_thresholds_freq(maxamp, Ncoupled, Nfreq, D1) 191 | zero_start_end!(params, D1, minCoeff, maxCoeff) 192 | 193 | println("*** Settings ***") 194 | println("System Hamiltonian coefficients [GHz]: (fa, xa) = ", fa, xa) 195 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 196 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 197 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 198 | println("Max parameter amplitudes: maxpar = ", maxpar) 199 | println("Target rotating frame control amplitude, maxctrl = ", maxctrl, " [rad/ns], ", maxctrl*0.5/pi, " [GHz]") 200 | # params.tik0 = 0 201 | println("Tikhonov coefficients: tik0 = ", params.tik0) 202 | 203 | # optional arguments to setup_ipopt_problem() 204 | maxIter = 150 205 | lbfgsMax = 5 206 | ipTol = 1e-5 207 | acceptTol = 1e-5 208 | acceptIter = 15 209 | 210 | # Estimate number of terms in Neumann series for time stepping (Default 3) 211 | tol = eps(1.0); # machine precision 212 | Juqbox.estimate_Neumann!(tol, params, [maxpar]) 213 | 214 | # Allocate all working arrays 215 | wa = Juqbox.Working_Arrays(params,nCoeff) 216 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch, ipTol=ipTol,acceptTol=acceptTol, acceptIter=acceptIter, nodes=nodes, weights=weights) 217 | 218 | println("Initial coefficient vector stored in 'pcof0'") -------------------------------------------------------------------------------- /test/cases/rabi-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem on a 3 | single qubit with 2 energy levels (and no guard states) 4 | where the analytical solution is a constant control 5 | function, i.e. a Rabi oscillator. The drift Hamiltonian in 6 | the rotating frame is 7 | H_0 = - 0.5*ξ_a(a^†a^†aa), 8 | where a is the annihilation operator for the qubit. Here 9 | the control Hamiltonian includes the usual symmetric and 10 | anti-symmetric terms 11 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†), 12 | which come from the rotating frame approximation and hence 13 | we refer to these as "coupled" controls. For this 14 | example we evolve the state forward a full period T=2π. The 15 | parameters for this example are: 16 | ω_a = 2π × 0.0 Grad/s, 17 | ξ_a = 2π × 2(0.1099) Grad/s. 18 | We use the usual Bsplines (no carrier waves) in this 19 | example. 20 | ==========================================================# 21 | #import Juqbox 22 | 23 | verbose = false 24 | N = 2 25 | 26 | Nguard = 0 27 | Ntot = N + Nguard 28 | 29 | samplerate = 32 # default number of time steps per unit time 30 | casename = "rabi" # base file name (used in optimize-once.jl) 31 | 32 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 33 | fa = 0.0 34 | xa = 2* 0.1099 35 | rot_freq = [fa] 36 | 37 | # period of oscillation 38 | Tperiod = 2*pi 39 | 40 | # duration 41 | T = Tperiod # Tperiod/4 42 | 43 | utarget = Matrix{ComplexF64}(I, Ntot, N) 44 | vtarget = Matrix{ComplexF64}(I, Ntot, N) 45 | 46 | # Rabi target (one period) 47 | 48 | theta = pi/2 # phase angle 49 | aOmega = pi/Tperiod 50 | Omega = (cos(theta) + 1im*sin(theta)) * aOmega 51 | #println("Amplitude |Omega| = ", aOmega, " phase angle theta = ", theta) 52 | 53 | # unitary target matrix 54 | utarget[1,1] = cos(aOmega*T) 55 | utarget[2,1] = -(sin(theta) + 1im*cos(theta))*sin(aOmega*T) 56 | utarget[1,2] = (sin(theta) - 1im*cos(theta))*sin(aOmega*T) 57 | utarget[2,2] = cos(aOmega*T) 58 | 59 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], rot_freq) 60 | 61 | # Compute Ra*utarget 62 | rot1 = Diagonal(exp.(im*omega1*T)) 63 | 64 | # target in the rotating frame 65 | vtarget = rot1*utarget 66 | 67 | # setup ansatz for control functions 68 | use_bcarrier = true # new Bcarrier allows a constant control function 69 | 70 | Nctrl = 1 71 | Nosc = Nctrl 72 | Nfreq = 1 # number of carrier frequencies 73 | 74 | Random.seed!(2456) 75 | # initial parameter guess 76 | 77 | # setup carrier frequencies 78 | om = zeros(Nosc,Nfreq) 79 | # Note: same frequencies for each p(t) (x-drive) and q(t) (y-drive) 80 | #println("Carrier frequencies [GHz]: ", om[:,:]./(2*pi)) 81 | 82 | # setup drift Hamiltonian 83 | number = Diagonal(collect(0:Ntot-1)) 84 | 85 | H0 = -0.5*(2*pi)*xa* (number*number - number) 86 | #println("Drift Hamiltonian/2*pi: ", H0) 87 | 88 | # lowering matrix 89 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering operator matrix 90 | # raising matrix 91 | adag = transpose(amat) # raising operator matrix 92 | 93 | # max parameter amplitude 94 | maxpar = 1.0*aOmega/Nfreq 95 | 96 | K1 = H0 + maxpar.*( amat + amat') + 1im* maxpar.*(amat - amat') 97 | lamb = eigvals(Array(K1)) 98 | maxeig = maximum(abs.(lamb)) 99 | 100 | # Estimate time step 101 | Pmin = 80 102 | samplerate1 = maxeig*Pmin/(2*pi) 103 | nsteps = ceil(Int64, T*samplerate1) 104 | 105 | #println("Max est. eigenvalue = ", maxeig, " Min period = ", 2*pi/maxeig, " # time steps per min-period, P = ", Pmin, " # time steps: ", nsteps) 106 | 107 | # package the lowering and raising matrices together into an one-dimensional array of two-dimensional arrays 108 | # Here we choose dense or sparse representation 109 | # NOTE: the above eigenvalue calculation does not work with sparse arrays! 110 | 111 | # sparse matrices 112 | # Hsym_ops=[sparse(amat+adag)] 113 | # Hanti_ops=[sparse(amat-adag)] 114 | # H0 = sparse(H0) 115 | 116 | # dense matrices 117 | Hsym_ops=[Array(amat + adag)] 118 | Hanti_ops=[Array(amat - adag)] 119 | H0 = Array(H0) 120 | 121 | # Weights in the W matrix for discouraging population of guarded states 122 | wmatScale = 1.0 123 | wmat = wmatScale .* Juqbox.wmatsetup([N], [Nguard]) 124 | 125 | # Initial conditions 126 | Ident = Matrix{Float64}(I, Ntot, Ntot) 127 | U0 = Ident[1:Ntot,1:N] 128 | 129 | # setup the simulation parameters 130 | Integrator_id = 1 131 | 132 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 133 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, Integrator = Integrator_id) 134 | params.saveConvHist = true 135 | params.use_bcarrier = true 136 | 137 | if params.Integrator_id == 2 138 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 139 | params.linear_solver = linear_solver 140 | end 141 | 142 | # Quiet mode for testing 143 | params.quiet = true 144 | 145 | # setup the initial parameter vector, either randomized or from file 146 | startFromScratch = true # true 147 | startFile = "cases/rabi.dat" 148 | 149 | if startFromScratch 150 | # D1 smaller than 3 does not work 151 | D1 = 3 # Number of B-spline coefficients per frequency, sin/cos and real/imag 152 | nCoeff = 2*Nosc*Nfreq*D1 # factor '2' is for sin/cos 153 | pcof0 = zeros(nCoeff) 154 | 155 | if Nfreq == 1 # analytical solution 156 | pcof0[1:D1] .= aOmega*cos(theta) # real coefficients 157 | pcof0[D1+1:2*D1] .= aOmega*sin(theta) # imag coefficients 158 | # println("*** Starting from constant pcof with (Re/Im) amplitudes: ", aOmega*cos(theta), " ", aOmega*sin(theta) ) 159 | else 160 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 161 | # println("*** Starting from random pcof with amplitude ", maxpar*0.1) 162 | end 163 | else 164 | # the data on the startfile must be consistent with the setup! 165 | # use if you want to have initial coefficients read from file 166 | pcof0 = vec(readdlm(startFile)) 167 | nCoeff = length(pcof0) 168 | D1 = div(nCoeff, 2*Nosc*Nfreq) # factor '2' is for sin/cos 169 | nCoeff = 2*Nosc*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 170 | # println("*** Starting from B-spline coefficients in file: ", startFile) 171 | end 172 | 173 | # min and max coefficient values 174 | useBarrier = true 175 | minCoeff = -maxpar*ones(nCoeff); 176 | maxCoeff = maxpar*ones(nCoeff); 177 | 178 | # For ipopt 179 | maxIter = 150 # optional argument 180 | lbfgsMax = 250 # optional argument 181 | 182 | if verbose 183 | println("*** Settings ***") 184 | println("System Hamiltonian coefficients: (fa, xa) = ", fa, xa) 185 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 186 | if use_bcarrier 187 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 188 | else 189 | println("Using regular B-spline basis functions") 190 | end 191 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 192 | println("Max parameter amplitudes: maxpar = ", maxpar) 193 | println("Tikhonov coefficients: tik0 = ", params.tik0) 194 | end 195 | 196 | # Estimate number of terms in Neumann series for time stepping (Default 3) 197 | tol = eps(1.0); # machine precision 198 | Juqbox.estimate_Neumann!(tol, params, [maxpar]) 199 | 200 | # Allocate all working arrays 201 | if params.Integrator_id == 1 202 | wa = Juqbox.Working_Arrays(params, nCoeff) 203 | elseif params.Integrator_id == 2 204 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 205 | end 206 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax) 207 | 208 | # uncomment to run the gradient checker for the initial pcof 209 | # if @isdefined addOption 210 | # addOption( prob, "derivative_test", "first-order"); # for testing the gradient 211 | # else 212 | # AddIpoptStrOption( prob, "derivative_test", "first-order") 213 | # end 214 | #println("Initial coefficient vector stored in 'pcof0'") 215 | 216 | # grad_storage = zeros(size(pcof0)) 217 | 218 | 219 | # for i = 1:length(pcof0) 220 | # println("Finite difference for parameter: ", i) 221 | # perturb = zeros(size(pcof0)) 222 | # perturb[i] = 0.0000000001 223 | # objfv, _, _ = traceobjgrad(pcof0, params, wa, false, false) 224 | # objfv2, _, _ = traceobjgrad(pcof0 + perturb, params, wa, false, false) 225 | # grad_storage[i] = (objfv2 - objfv)/0.0000000001 226 | # end 227 | 228 | # save_object("rabi-ref-IMR.jld2", grad_storage) -------------------------------------------------------------------------------- /examples/Risk_Neutral/Bimodal_Gaussian/swap-02-risk-neutral.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a |0⟩ to |2⟩ swap gate on a single qudit with 3 energy 4 | levels (and 1 guard state). The drift Hamiltonian in the 5 | rotating frame is 6 | H0 = 2π*diagm([0 0 -2.2538e-1 -7.0425e-1]). 7 | Here the control Hamiltonian includes the usual symmetric 8 | and anti-symmetric terms 9 | H_{sym} = p(t)(a + a^†), H_{asym} = q(t)(a - a^†), 10 | where a is the annihilation operator for the qudit. 11 | The problem parameters for this example are: 12 | ω_a = 2π × 4.09947 Grad/s, 13 | ξ_a = 2π × 2.2538e-01 Grad/s. 14 | We use Bsplines with carrier waves with frequencies 15 | 0, ξ_a Grad/s. 16 | ==========================================================# 17 | using LinearAlgebra 18 | using Plots 19 | using FFTW 20 | using DelimitedFiles 21 | using Printf 22 | using Ipopt 23 | using Random 24 | using JLD2 25 | using FastGaussQuadrature 26 | 27 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 28 | 29 | using Juqbox # quantum control module 30 | 31 | # Default values for uniform distribution on [-ϵ/2,ϵ/2] 32 | if(!@isdefined(ep_max)) 33 | ep_max = 2*pi*3e-2 34 | end 35 | 36 | # Default number of quadrature nodes to evaluate the 37 | # expected value of the objective function via 38 | # Gaussian quadrature, i.e. 39 | # E[J] = ∑ w[k] J[x[k]] 40 | # where w,x are the weights and nodes on [-ϵ/2,ϵ/2] 41 | if(!@isdefined(nquad)) 42 | nquad = 10 43 | nquad = 1 44 | end 45 | 46 | if nquad == 1 47 | nodes, weights = gausslegendre(nquad) 48 | weights = weights./2.0 49 | else 50 | nodes_tmp, weights_tmp = gausshermite(nquad) 51 | 52 | # Make a larger node list for full distribution 53 | n_modes = length(mean_vec) 54 | nodes = zeros(n_modes*nquad) 55 | weights = copy(nodes) 56 | inv_n = 1.0/(n_modes*sqrt(pi)) 57 | 58 | # Mapping to reference weighting function 59 | for i = 1:n_modes 60 | μ = mean_vec[i] 61 | σ = sig_vec[i] 62 | 63 | offset = (i-1)*nquad 64 | for j = 1:length(nodes_tmp) 65 | nodes[j + offset] = sqrt(2)*σ*nodes_tmp[j] + μ 66 | weights[j+offset] = weights_tmp[j]*inv_n 67 | end 68 | end 69 | end 70 | 71 | N = 3 # Number of essential energy levels 72 | Nguard = 1 # Number of guard/forbidden energy levels 73 | Ntot = N + Nguard # Total number of energy levels 74 | 75 | samplerate = 32 # for output files 76 | 77 | T = 275.0 # Duration of gate 78 | 79 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 80 | fa = 4.10336 81 | xa = 0.2198 82 | rot_freq = [fa] # Used to calculate the lab frame ctrl function 83 | 84 | # setup drift Hamiltonian 85 | number = Diagonal(collect(0:Ntot-1)) 86 | 87 | H0 = -0.5*(2*pi)*xa* (number*number - number) # xa is in GHz 88 | 89 | utarget = zeros(ComplexF64,Ntot,N) 90 | 91 | if N == 2 92 | utarget[2,1] = 1.0 93 | utarget[1,2] = 1.0 94 | end 95 | 96 | # 0> to |2> swap gate 97 | if N >= 3 98 | utarget[1,1] = 0 #1/sqrt(2) 99 | utarget[2,1] = 0 100 | utarget[3,1] = 1 #1/sqrt(2) 101 | # 102 | utarget[1,2] = 0 103 | utarget[2,2] = 1 104 | utarget[3,2] = 0 105 | # 106 | utarget[1,3] = 1 #1/sqrt(2) 107 | utarget[2,3] = 0 108 | utarget[3,3] = 0 #-1/sqrt(2) 109 | # 110 | end 111 | 112 | if N==4 113 | utarget[4,4] = 1 114 | end 115 | 116 | omega1 = Juqbox.setup_rotmatrices([N], [Nguard], [fa]) 117 | 118 | # Compute Ra*utarget 119 | rot1 = Diagonal(exp.(im*omega1*T)) 120 | 121 | # target in the lab frame 122 | vtarget = utarget 123 | 124 | startFromScratch = true 125 | # startFile is used when startFromScratch = false 126 | startFile="swap02-baseline-pcof-opt.jld2" # "swap02-pert-4em1-pcof-opt.jld2" 127 | 128 | usePrior = false # true 129 | priorFileName = startFile # Usually makes sense to also use the start file as the prior, but not required 130 | 131 | # setup drift Hamiltonian 132 | number = Diagonal(collect(0:Ntot-1)) 133 | 134 | # lowering matrix 135 | amat = Bidiagonal(zeros(Ntot),sqrt.(collect(1:Ntot-1)),:U) # standard lowering matrix 136 | adag = Array(transpose(amat)); 137 | Hsym_ops=[Array(amat + adag)] 138 | Hanti_ops=[Array(amat - adag)] 139 | H0 = Array(H0) 140 | 141 | Ncoupled = length(Hsym_ops) # Number of paired Hamiltonians 142 | Nfreq= 2 # number of carrier frequencies 3 gives a cleaner sol than 2 143 | 144 | # setup carrier frequencies 145 | use_bcarrier = true 146 | om = zeros(Ncoupled,Nfreq) 147 | if use_bcarrier 148 | om[1:Ncoupled,2] .= -2.0*pi *xa # Note negative sign 149 | end 150 | println("Carrier frequencies [GHz]: ", om[1,:]./(2*pi)) 151 | println("H0: ", H0) 152 | 153 | maxctrl = 2*pi*1.2e-2 154 | 155 | #max amplitude (in angular frequency) 2*pi*GHz 156 | maxamp = zeros(Nfreq) 157 | if Nfreq >= 5 158 | const_fact = 1.0/(Nfreq-2) 159 | maxamp[1] = maxctrl*const_fact 160 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # max B-spline coefficient amplitude, factor 3.0 is ad hoc 161 | elseif Nfreq >= 4 162 | const_fact = 0.4 163 | maxamp[1] = maxctrl*const_fact 164 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # max B-spline coefficient amplitude, factor 3.0 is ad hoc 165 | elseif Nfreq >= 3 166 | const_fact = 0.45 167 | maxamp[1] = maxctrl*const_fact 168 | maxamp[2:Nfreq] .= maxctrl*(1.0-const_fact)/(Nfreq-1) # max B-spline coefficient amplitude, factor 3.0 is ad hoc 169 | else 170 | maxamp .= maxctrl/Nfreq 171 | end 172 | 173 | maxpar = maximum(maxamp) 174 | nsteps = calculate_timestep(T, H0, Hsym_ops, Hanti_ops, [maxctrl]) 175 | println("# time steps: ", nsteps) 176 | 177 | # Initial conditions for basis 178 | U0 = initial_cond([N], [Nguard]) 179 | 180 | 181 | # params = Juqbox.parameters([N], [Nguard], T, nsteps, U0, vtarget, om, H0, Hsym_ops, Hanti_ops) 182 | params = Juqbox.objparams([N], [Nguard], T, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 183 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, wmatScale=1.0) 184 | 185 | if usePrior 186 | Juqbox.setup_prior!(params, priorFileName) 187 | params.tik0 = 100.0 # increase Tikhonov regularization coefficient 188 | end 189 | 190 | Random.seed!(2456) 191 | 192 | # initial parameter guess 193 | if startFromScratch 194 | D1 = 15 # Number of B-spline coefficients per frequency, sin/cos and real/imag 195 | nCoeff = 2*Ncoupled*Nfreq*D1 # factor '2' is for sin/cos 196 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 197 | 198 | if(nquad == 1) 199 | D1 = 15 # Number of B-spline coefficients per frequency, sin/cos and real/imag 200 | nCoeff = 2*Ncoupled*Nfreq*D1 # factor '2' is for sin/cos 201 | pcof0 = (rand(nCoeff) .- 0.5).*maxpar*0.1 202 | end 203 | println("*** Starting from random pcof with amplitude ", maxpar*0.1) 204 | else 205 | # use if you want to have initial coefficients read from file 206 | @load startFile pcof 207 | pcof0 = pcof 208 | println("*** Starting from B-spline coefficients in file: ", startFile) 209 | nCoeff = length(pcof0) 210 | D1 = div(nCoeff, 2*Ncoupled*Nfreq) # number of B-spline coeff per control function 211 | end 212 | 213 | 214 | # min and max coefficient values (set first and last two to zero) 215 | useBarrier = true 216 | minCoeff, maxCoeff = Juqbox.assign_thresholds_freq(maxamp, Ncoupled, Nfreq, D1) 217 | zero_start_end!(params, D1, minCoeff, maxCoeff) 218 | 219 | println("*** Settings ***") 220 | println("System Hamiltonian coefficients [GHz]: (fa, xa) = ", fa, xa) 221 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 222 | println("Using B-spline basis functions with carrier wave, # freq = ", Nfreq) 223 | println("Number of coefficients per spline = ", D1, " Total number of parameters = ", nCoeff) 224 | println("Max parameter amplitudes: maxpar = ", maxpar) 225 | println("Target rotating frame control amplitude, maxctrl = ", maxctrl, " [rad/ns], ", maxctrl*0.5/pi, " [GHz]") 226 | # params.tik0 = 0 227 | println("Tikhonov coefficients: tik0 = ", params.tik0) 228 | 229 | # optional arguments to setup_ipopt_problem() 230 | maxIter = 100 231 | lbfgsMax = 5 232 | ipTol = 1e-5 233 | acceptTol = 1e-5 234 | acceptIter = 15 235 | 236 | # Estimate number of terms in Neumann series for time stepping (Default 3) 237 | tol = eps(1.0); # machine precision 238 | Juqbox.estimate_Neumann!(tol, params, [maxpar]) 239 | 240 | # Allocate all working arrays 241 | wa = Juqbox.Working_Arrays(params,nCoeff) 242 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch, ipTol=ipTol,acceptTol=acceptTol, acceptIter=acceptIter, nodes=nodes, weights=weights) 243 | 244 | println("Initial coefficient vector stored in 'pcof0'") -------------------------------------------------------------------------------- /src/linear_solvers.jl: -------------------------------------------------------------------------------- 1 | using SparseArrays 2 | using LinearAlgebra 3 | 4 | 5 | const NEUMANN_SOLVER = 1 6 | const JACOBI_SOLVER = 2 7 | const GAUSSIAN_ELIM_SOLVER = 3 8 | const JACOBI_SOLVER_M = 4 9 | 10 | 11 | """ 12 | linear_solver = lsolver_object(; tol = tol, 13 | max_iter = max_iter, 14 | nrhs = nrhs, 15 | solver = NEUMANN_SOLVER) 16 | 17 | Constructor for the mutable struct lsolver_object. That allcoates arrays and sets up the function pointers 18 | for the different linear solvers supported. 19 | 20 | # Arguments 21 | - `tol::Float64 = 1e-10` : Convergence tolerance of the iterative solver (only needed for Jacobi) 22 | - `max_iter::Int64 = 3` : Max number of iterations for the linear solver 23 | - `nrhs::Int64 = 1` : Number of right-hand sides (used for tolerance scaling) 24 | - `solver::Int64 = NEUMANN_SOLVER` : (keyword) ID of the iterative solver. 25 | Can take the value of NEUMANN_SOLVER (i.e. 1) or JACOBI_SOLVER (i.e. 2) 26 | See examples/cnot2-jacobi-setup.jl 27 | """ 28 | mutable struct lsolver_object 29 | 30 | tol ::Float64 31 | max_iter ::Int64 32 | solver_id ::Int64 33 | solver_name ::String 34 | solve ::Function 35 | print_info ::Function 36 | 37 | function lsolver_object(;tol::Float64 = 1e-10, max_iter::Int64 = 3, nrhs::Int64=1, solver::Int64 = NEUMANN_SOLVER) 38 | 39 | if solver == JACOBI_SOLVER 40 | tol *= sqrt(nrhs) 41 | solve = (a,b,c,d,e) -> jacobi!(a,b,c,d,e,max_iter,tol) 42 | solver_name = "Jacobi" 43 | print_info = () -> println("*** Using linear solver: ", solver_name," with max_iter = ", max_iter, ", tol = ", tol) 44 | 45 | elseif solver == NEUMANN_SOLVER 46 | solve = (a,b,c,d,e) -> neumann!(a,b,c,d,e,max_iter) 47 | solver_name = "Neumann" 48 | print_info = () -> println("*** Using linear solver: ", solver_name," with max_iter = ", max_iter) 49 | elseif solver == GAUSSIAN_ELIM_SOLVER 50 | solve = (a,b,c,d,e) -> gaussian_elim_solve!(a,b,c,d) 51 | solver_name = "Built-in" 52 | print_info = () -> println("*** Using linear solver: ", solver_name," with max_iter = ", max_iter) 53 | 54 | elseif solver == JACOBI_SOLVER_M 55 | solve = (a, b, c, d, e, f, g, h, i, j, k) -> jacobi_midpoint(a, b, c, d, e, f, g, h, i, j, k, max_iter, tol) 56 | solver_name = "Jacobi from Implicit Midpoint" 57 | print_info = () -> println("***Using linear solver: ", solver_name," with max_iter = ", max_iter, ",tol = ", tol) 58 | else 59 | error("Please specify a supported linear solver") 60 | end 61 | 62 | new(tol,max_iter,solver,solver_name,solve,print_info) 63 | end 64 | 65 | end #mutable struct lsolver_object 66 | 67 | #Routine to recreate closured for updated max_iter and tol values 68 | function recreate_linear_solver_closure!(lsolver::lsolver_object) 69 | 70 | if lsolver.solver_id == JACOBI_SOLVER 71 | lsolver.solve = (a,b,c,d,e) -> jacobi!(a,b,c,d,e,lsolver.max_iter,lsolver.tol) 72 | elseif lsolver.solver_id == NEUMANN_SOLVER 73 | lsolver.solve = (a,b,c,d,e) -> neumann!(a,b,c,d,e,lsolver.max_iter) 74 | elseif lsolver.solver_id == GAUSSIAN_ELIM_SOLVER 75 | lsolver.solve = (a,b,c,d,e) -> gaussian_elim_solve!(a,b,c,e) 76 | end 77 | 78 | end 79 | 80 | 81 | @inline function neumann!(h::Float64, S::SparseMatrixCSC{Float64,Int64}, 82 | B::Array{Float64,N}, T::Array{Float64,N}, X::Array{Float64,N},nterms::Int64) where N 83 | copy!(X,B) 84 | copy!(T,B) 85 | coeff = 1.0 86 | for j = 1:nterms 87 | LinearAlgebra.mul!(T,S,B) 88 | coeff *= (0.5*h) 89 | LinearAlgebra.axpy!(coeff, T, X) 90 | copy!(B,T) 91 | end 92 | end 93 | 94 | @inline function neumann!(h::Float64, S::Array{Float64,N}, B::Array{Float64,N}, 95 | T::Array{Float64,N}, X::Array{Float64,N}, nterms::Int64,) where N 96 | 97 | copy!(X,B) 98 | copy!(T,B) 99 | coeff = 1.0 100 | for j = 1:nterms 101 | LinearAlgebra.mul!(T,S,B) 102 | coeff *= (0.5*h) 103 | LinearAlgebra.axpy!(coeff, T, X) 104 | copy!(B,T) 105 | end 106 | end 107 | 108 | 109 | 110 | @inline function jacobi!(h::Float64, S::Array{Float64,N}, B::Array{Float64,N}, 111 | T::Array{Float64,N}, X::Array{Float64,N}, max_iter::Int64,tol::Float64) where N 112 | 113 | X .= B #copy!(X,B) 114 | coeff = -0.5*h 115 | o_coeff = 1.0/coeff 116 | S .*= coeff 117 | err = 0.0 118 | for j = 1:max_iter 119 | LinearAlgebra.mul!(T,S,X) 120 | T = B - T 121 | err = norm(T - X) 122 | X .= T #copy!(X,T) 123 | if err < tol 124 | S .*= o_coeff 125 | return 126 | end 127 | end 128 | S .*= o_coeff 129 | @warn "WARNING: Jacobi solver reached maximum number of iterations ($max_iter), final norm(residual) = $(err)" 130 | end 131 | 132 | 133 | @inline function jacobi!(h::Float64, S::SparseMatrixCSC{Float64,Int64}, B::Array{Float64,N}, 134 | T::Array{Float64,N}, X::Array{Float64,N}, max_iter::Int64,tol::Float64) where N 135 | 136 | X .= B #copy!(X,B) 137 | coeff = -0.5*h 138 | o_coeff = 1.0/coeff 139 | S .*= coeff 140 | err = 0.0 141 | for j = 1:max_iter 142 | LinearAlgebra.mul!(T,S,X) 143 | T = B - T 144 | err = norm(T - X) 145 | X .= T #copy!(X,T) 146 | if err < tol 147 | S .*= o_coeff 148 | return 149 | end 150 | end 151 | S .*= o_coeff 152 | @warn "WARNING: Jacobi solver reached maximum number of iterations ($max_iter), final norm(residual) = $(err)" 153 | end 154 | 155 | """ 156 | For comparing other solvers with the built-in linear solver using Gaussian 157 | elimination. 158 | """ 159 | @inline function gaussian_elim_solve!(h::Float64, S::Array{Float64,N}, B::Array{Float64,N}, 160 | X::Array{Float64,N}) where N 161 | X .= (LinearAlgebra.I - (0.5h.*S))\B 162 | end 163 | 164 | 165 | @inline function jacobi_midpoint(h::Float64, rhs_u::Array{Float64,N}, rhs_v::Array{Float64,N}, S::SparseMatrixCSC{Float64,Int64}, K::SparseMatrixCSC{Float64,Int64} 166 | ,u_init::Array{Float64,N}, v_init::Array{Float64,N}, x0_u, x0_v, norm_matrix_u, norm_matrix_v, max_iter::Int64, tol::Float64) where N 167 | 168 | 169 | #Assign values to the pre-allocated matrices 170 | x0_u .= u_init 171 | x0_v .= v_init 172 | norm_matrix_u .= 0 173 | norm_matrix_v .= 0 174 | 175 | 176 | for i in 1:max_iter 177 | 178 | #Run Jacobi's iteration using the right hand sides of the real and imaginary part 179 | 180 | #Jacobi's iteration for the real part 181 | mul!(u_init, S, x0_u, h/2, 0) 182 | axpy!(1, rhs_u, u_init) 183 | mul!(u_init, K, x0_v, -h/2, 1) 184 | 185 | #Jacobi's iteration for the imaginary part 186 | mul!(v_init, K, x0_u, h/2, 0) 187 | axpy!(1, rhs_v, v_init) 188 | mul!(v_init, S, x0_v, h/2, 1) 189 | 190 | #Store values to be used in next iteration 191 | x0_u .= u_init 192 | x0_v .= v_init 193 | 194 | #Create residual Matrices 195 | 196 | #Residual matrix for real part 197 | mul!(norm_matrix_u, K, x0_v, h/2, 0) 198 | mul!(norm_matrix_u, S, x0_u, -h/2, 1) 199 | axpy!(1, x0_u, norm_matrix_u) 200 | axpy!(-1, rhs_u, norm_matrix_u) 201 | 202 | #Residual matrix for imaginary part 203 | mul!(norm_matrix_v, S, x0_v, -h/2, 0) 204 | mul!(norm_matrix_v, K, x0_u, -h/2, 1) 205 | axpy!(1, x0_v, norm_matrix_v) 206 | axpy!(-1, rhs_v, norm_matrix_v) 207 | 208 | #Break loop if norm of residal matrices have reached tolerance 209 | if (norm(norm_matrix_u) < tol) && (norm(norm_matrix_v) < tol) 210 | #println("Tolerance Reached!") 211 | # println("Norm_u: ", norm_u) 212 | # println("Norm_v: ", norm_v) 213 | break 214 | else 215 | #println("Tolerance not reached") 216 | end 217 | 218 | #println((I - h/2*S)*x0_u + h/2*K*x0_v) 219 | end 220 | 221 | end 222 | 223 | #Dense version of above function 224 | @inline function jacobi_midpoint(h::Float64, rhs_u::Array{Float64,N}, rhs_v::Array{Float64,N}, S::Array{Float64, N},K::Array{Float64, N}, 225 | u_init::Array{Float64,N}, v_init::Array{Float64,N}, x0_u, x0_v, norm_matrix_u, norm_matrix_v, 226 | max_iter::Int64, tol::Float64) where N 227 | 228 | x0_u .= u_init 229 | x0_v .= v_init 230 | norm_matrix_u .= 0 231 | norm_matrix_v .= 0 232 | 233 | for i in 1:max_iter 234 | 235 | mul!(u_init, S, x0_u, h/2, 0) 236 | axpy!(1, rhs_u, u_init) 237 | mul!(u_init, K, x0_v, -h/2, 1) 238 | 239 | mul!(v_init, K, x0_u, h/2, 0) 240 | axpy!(1, rhs_v, v_init) 241 | mul!(v_init, S, x0_v, h/2, 1) 242 | 243 | 244 | x0_u .= u_init 245 | x0_v .= v_init 246 | mul!(norm_matrix_u, K, x0_v, h/2, 0) 247 | mul!(norm_matrix_u, S, x0_u, -h/2, 1) 248 | axpy!(1, x0_u, norm_matrix_u) 249 | axpy!(-1, rhs_u, norm_matrix_u) 250 | 251 | mul!(norm_matrix_v, S, x0_v, -h/2, 0) 252 | mul!(norm_matrix_v, K, x0_u, -h/2, 1) 253 | axpy!(1, x0_v, norm_matrix_v) 254 | axpy!(-1, rhs_v, norm_matrix_v) 255 | 256 | if (norm(norm_matrix_u) < tol) && (norm(norm_matrix_v) < tol) 257 | #println("Tolerance Reached!") 258 | # println("Norm_u: ", norm_u) 259 | # println("Norm_v: ", norm_v) 260 | break 261 | else 262 | #println("Tolerance not reached") 263 | end 264 | 265 | #println((I - h/2*S)*x0_u + h/2*K*x0_v) 266 | end 267 | end -------------------------------------------------------------------------------- /examples/cnot2-lab.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a coupled 2-qubit system with 2 energy 4 | levels on each oscillator (with 1 guard state on one and 5 | 2 guard states on the other). The drift Hamiltonian in 6 | the rotating frame is 7 | H_0 = - 0.5*ξ_a(a^†a^†aa) 8 | - 0.5*ξ_b(b^†b^†bb) 9 | - ξ_{ab}(a^†ab^†b), 10 | where a,b are the annihilation operators for each qubit. 11 | Here the control Hamiltonian in the rotating frame 12 | includes the usual symmetric and anti-symmetric terms 13 | H_{sym,1} = p_1(t)(a + a^†), H_{asym,1} = q_1(t)(a - a^†), 14 | H_{sym,2} = p_2(t)(b + b^†), H_{asym,2} = q_2(t)(b - b^†). 15 | The problem parameters for this example are, 16 | ω_a = 2π × 4.10595 Grad/s, 17 | ξ_a = 2π × 2(0.1099) Grad/s, 18 | ω_b = 2π × 4.81526 Grad/s, 19 | ξ_b = 2π × 2(0.1126) Grad/s, 20 | ξ_{ab} = 2π × 0.1 Grad/s, 21 | We use Bsplines with carrier waves with frequencies 22 | 0, ξ_a, 2ξ_a Grad/s for each oscillator. 23 | ==========================================================# 24 | using LinearAlgebra 25 | using Ipopt 26 | using Base.Threads 27 | using Random 28 | using DelimitedFiles 29 | using Printf 30 | using FFTW 31 | using Plots 32 | #pyplot() 33 | using SparseArrays 34 | 35 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 36 | 37 | using Juqbox 38 | 39 | function initial_cond(Ntot, N, Ne, Ng) 40 | Ident = Matrix{Float64}(I, Ntot, Ntot) 41 | U0 = Ident[1:Ntot, 1:N] # Rectangular subset of identity 42 | # adjust the initial guess 43 | if Ng[1] + Ng[2] > 0 44 | Nt = Ne + Ng 45 | # build up a basis for the essential states 46 | col = 0 47 | m = 0 48 | for k2 in 1:Nt[2] 49 | for k1 in 1:Nt[1] 50 | m += 1 51 | # is this a guard level? 52 | guard = (k1 > Ne[1]) || (k2 > Ne[2]) 53 | if !guard 54 | col += 1 55 | U0[:,col] = Ident[:,m] 56 | end # if ! guard 57 | end # for 58 | end # for 59 | end # if 60 | return U0 61 | end 62 | 63 | println("***This case ONLY evaluates the accuracy of a control pulse in the laboratory frame***") 64 | Nosc = 2 # number of coupled oscillators 65 | 66 | Ne1 = 2 # essential energy levels per oscillator 67 | Ne2 = 2 68 | Ng1 = 1 # 0 # Osc-1, number of guard states 69 | Ng2 = 1 # 0 # Osc-2, number of guard states 70 | 71 | Ne = [Ne1, Ne2] 72 | Ng = [Ng1, Ng2] 73 | 74 | N = Ne1*Ne2; # Total number of nonpenalized energy levels 75 | Ntot = (Ne1+Ng1)*(Ne2+Ng2) 76 | Nguard = Ntot - N # total number of guard states 77 | 78 | Nt1 = Ne1 + Ng1 79 | Nt2 = Ne2 + Ng2 80 | 81 | Tmax = 50.0 # 100.0 # Duration of gate 82 | 83 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 84 | fa = 4.10595 # official 85 | fb = 4.81526 # official 86 | rot_freq = [fa, fb] # rotational frequencies 87 | x1 = 2* 0.1099 # official 88 | x2 = 2* 0.1126 # official 89 | x12 = 0.1 # Artificially large to allow coupling. Actual value: 1e-6 90 | 91 | # construct the lowering and raising matricies: amat, bmat, cmat 92 | # and the system Hamiltonian: H0 93 | # 94 | 95 | # Note: The ket psi = ji> = e_j kron e_i. 96 | # We order the elements in the vector psi such that i varies the fastest with i in [1,Nt1] and j in [1,Nt2] 97 | # The matrix amat = I kron a1 acts on alpha in psi = beta kron alpha 98 | # The matrix bmat = a2 kron I acts on beta in psi = beta kron alpha 99 | a1 = Array(Bidiagonal(zeros(Nt1),sqrt.(collect(1:Nt1-1)),:U)) 100 | a2 = Array(Bidiagonal(zeros(Nt2),sqrt.(collect(1:Nt2-1)),:U)) 101 | 102 | I1 = Array{Float64, 2}(I, Nt1, Nt1) 103 | I2 = Array{Float64, 2}(I, Nt2, Nt2) 104 | 105 | # create the a, a^\dag, b and b^\dag vectors 106 | amat = kron(I2, a1) 107 | bmat = kron(a2, I1) 108 | 109 | adag = Array(transpose(amat)) 110 | bdag = Array(transpose(bmat)) 111 | 112 | # number ops 113 | num1 = Diagonal(collect(0:Nt1-1)) 114 | num2 = Diagonal(collect(0:Nt2-1)) 115 | 116 | # number operators 117 | N1 = Diagonal(kron(I2, num1) ) 118 | N2 = Diagonal(kron(num2, I1) ) 119 | 120 | # System Hamiltonian in the lab frame 121 | H0 = 2*pi*( fa*N1 + fb*N2 -x1/2*(N1*N1-N1) - x2/2*(N2*N2-N2) - x12*(N1*N2) ) 122 | 123 | # dense matrices run faster, but take more memory 124 | Hunc_ops=[Array(amat+adag), Array(bmat+bdag)] 125 | H0 = Array(H0) 126 | 127 | # max coefficients, rotating frame 128 | amax = 0.014 # max amplitude ctrl func for Hamiltonian #1 129 | bmax = 0.020 # max amplitude ctrl func for Hamiltonian #2 130 | maxpar = [amax, bmax] 131 | 132 | # Estimate time step 133 | Pmin = 200 # should be 20 or higher 134 | nsteps = calculate_timestep(Tmax, H0, Hunc_ops, maxpar, Pmin) 135 | 136 | println("Number of time steps = ", nsteps) 137 | 138 | # package the lowering and raising matrices together into an one-dimensional array of two-dimensional arrays 139 | # Here we choose dense or sparse representation 140 | #use_sparse = true 141 | use_sparse = false 142 | 143 | Nfreq = 2 # number of carrier frequencies 144 | 145 | om = zeros(Nosc, Nfreq) # Allocate space for the carrier wave frequencies 146 | 147 | @assert(Nfreq==1 || Nfreq==2 || Nfreq==3) 148 | if Nfreq == 2 149 | om[1:Nosc,2] .= -2.0*pi*x12 # coupling freq for both ctrl funcs (re/im) 150 | elseif Nfreq == 3 151 | om[1,2] = -2.0*pi*x1 # 1st ctrl, re 152 | om[2,2] = -2.0*pi*x2 # 2nd ctrl, re 153 | om[1:Nosc,3] .= -2.0*pi*x12 # coupling freq for both ctrl funcs (re/im) 154 | end 155 | println("Carrier frequencies 1st ctrl Hamiltonian [GHz]: ", om[1,:]./(2*pi)) 156 | println("Carrier frequencies 2nd ctrl Hamiltonian [GHz]: ", om[2,:]./(2*pi)) 157 | 158 | # specify target gate 159 | # target for CNOT gate N=2, Ng = 1 coupled 160 | utarget = zeros(ComplexF64, Ntot, N) 161 | @assert(Ng1 == 0 || Ng1 == 1 || Ng1 == 2) 162 | if Ng1 == 0 163 | utarget[1,1] = 1.0 164 | utarget[2,2] = 1.0 165 | utarget[3,4] = 1.0 166 | utarget[4,3] = 1.0 167 | elseif Ng1 == 1 168 | utarget[1,1] = 1.0 169 | utarget[2,2] = 1.0 170 | utarget[4,4] = 1.0 171 | utarget[5,3] = 1.0 172 | elseif Ng1 == 2 173 | utarget[1,1] = 1.0 174 | utarget[2,2] = 1.0 175 | utarget[5,4] = 1.0 176 | utarget[6,3] = 1.0 177 | end 178 | 179 | # rotation matrices 180 | omega1, omega2 = Juqbox.setup_rotmatrices(Ne, Ng, rot_freq) 181 | 182 | # Compute Ra*Rb*utarget 183 | rot1 = Diagonal(exp.(im*omega1*Tmax)) 184 | rot2 = Diagonal(exp.(im*omega2*Tmax)) 185 | 186 | # target in the lab frame 187 | vtarget = utarget 188 | 189 | U0 = initial_cond(Ntot, N, Ne, Ng) 190 | 191 | # assemble problem description for the optimization 192 | params = Juqbox.objparams(Ne, Ng, Tmax, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 193 | Hconst=H0, Hunc_ops=Hunc_ops) 194 | 195 | # initial parameter guess 196 | startFromScratch = false # true 197 | startFile = "drives/cnot2-pcof-opt-t50.jld2" 198 | 199 | # dimensions for the parameter vector 200 | D1 = 10 # number of B-spline coeff per oscillator, freq and sin/cos 201 | 202 | nCoeff = 2*Nosc*Nfreq*D1 # Total number of parameters. 203 | 204 | Random.seed!(2456) 205 | if startFromScratch 206 | pcof0 = amax*0.01 * rand(nCoeff) 207 | println("*** Starting from pcof with random amplitude ", amax*0.01) 208 | else 209 | # the data on the startfile must be consistent with the setup! 210 | # use if you want to have initial coefficients read from file 211 | pcof0 = read_pcof(startFile) 212 | nCoeff = length(pcof0) 213 | D1 = div(nCoeff, 2*Nosc*Nfreq) # factor '2' is for sin/cos 214 | nCoeff = 2*Nosc*Nfreq*D1 # just to be safe if the file doesn't contain the right number of elements 215 | println("*** Starting from B-spline coefficients in file: ", startFile) 216 | end 217 | 218 | samplerate = 32 # for plotting 219 | casename = "cnot2" # for constructing file names 220 | 221 | # min and max B-spline coefficient values 222 | useBarrier = true 223 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params,D1,maxpar) 224 | println("Number of min coeff: ", length(minCoeff), "Max Coeff: ", length(maxCoeff)) 225 | 226 | maxIter = 150 # 0 #250 #50 # optional argument 227 | lbfgsMax = 250 # optional argument 228 | 229 | println("*** Settings ***") 230 | # output run information 231 | println("Frequencies: fa = ", fa, " fb = ", fb) 232 | println("Coefficients in the Hamiltonian: x1 = ", x1, " x2 = ", x2, " x12 = ", x12) 233 | println("Essential states in osc = ", [Ne1, Ne2], " Guard states in osc = ", [Ng1, Ng2]) 234 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 235 | println("Number of B-spline parameters per spline = ", D1, " Total number of parameters = ", nCoeff) 236 | println("Max parameter amplitudes: maxpar = ", maxpar) 237 | println("Tikhonov regularization tik0 (L2) = ", params.tik0) 238 | if use_sparse 239 | println("Using a sparse representation of the Hamiltonian matrices") 240 | else 241 | println("Using a dense representation of the Hamiltonian matrices") 242 | end 243 | 244 | # Allocate all working arrays 245 | wa = Juqbox.Working_Arrays(params, nCoeff) 246 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 247 | 248 | #uncomment to run the gradient checker for the initial pcof 249 | #= 250 | if @isdefined addOption 251 | addOption( prob, "derivative_test", "first-order"); # for testing the gradient 252 | else 253 | AddIpoptStrOption( prob, "derivative_test", "first-order") 254 | end 255 | =# 256 | 257 | #uncomment to change print level 258 | #= 259 | if @isdefined addOption 260 | addOption(prob, "print_level", 0); # for testing the gradient 261 | else 262 | AddIpoptIntOption(prob, "print_level", 0) 263 | end 264 | =# 265 | println("Initial coefficient vector stored in 'pcof0'") 266 | 267 | # evaluate objective function 268 | objf, uhist, trfid = traceobjgrad(pcof0, params, wa, true, false); 269 | println("Trace fidelity: ", trfid); 270 | 271 | -------------------------------------------------------------------------------- /examples/swap2-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a coupled 2-qubit system with 2 energy 4 | levels on each oscillator (with 1 guard state on one and 5 | 2 guard states on the other). The drift Hamiltonian in 6 | the rotating frame is 7 | H_0 = - 0.5*ξ_a(a^†a^†aa) 8 | - 0.5*ξ_b(b^†b^†bb) 9 | - ξ_{ab}(a^†ab^†b), 10 | where a,b are the annihilation operators for each qubit. 11 | Here the control Hamiltonian in the rotating frame 12 | includes the usual symmetric and anti-symmetric terms 13 | H_{sym,1} = p_1(t)(a + a^†), H_{asym,1} = q_1(t)(a - a^†), 14 | H_{sym,2} = p_2(t)(b + b^†), H_{asym,2} = q_2(t)(b - b^†). 15 | The problem parameters for this example are, 16 | ω_a = 2π × 4.10595 Grad/s, 17 | ξ_a = 2π × 2(0.1099) Grad/s, 18 | ω_b = 2π × 4.81526 Grad/s, 19 | ξ_b = 2π × 2(0.1126) Grad/s, 20 | ξ_{ab} = 2π × 0.1 Grad/s, 21 | We use Bsplines with carrier waves with frequencies 22 | 0, ξ_a, 2ξ_a Grad/s for each oscillator. 23 | ==========================================================# 24 | using LinearAlgebra 25 | using Ipopt 26 | using Base.Threads 27 | using Random 28 | using DelimitedFiles 29 | using Printf 30 | using FFTW 31 | using Plots 32 | using SparseArrays 33 | 34 | Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 35 | 36 | using Juqbox 37 | 38 | # function my_initial_cond(Ntot, N, Ne, Ng) 39 | # Ident = Matrix{Float64}(I, Ntot, Ntot) 40 | # U0 = Ident[1:Ntot, 1:N] # Rectangular subset of identity 41 | # # adjust the initial guess 42 | # if Ng[1] + Ng[2] > 0 43 | # Nt = Ne + Ng 44 | # # build up a basis for the essential states 45 | # col = 0 46 | # m = 0 47 | # for k2 in 1:Nt[2] 48 | # for k1 in 1:Nt[1] 49 | # m += 1 50 | # # is this a guard level? 51 | # guard = (k1 > Ne[1]) || (k2 > Ne[2]) 52 | # if !guard 53 | # col += 1 54 | # U0[:,col] = Ident[:,m] 55 | # end # if ! guard 56 | # end # for 57 | # end # for 58 | # end # if 59 | # return U0 60 | # end 61 | 62 | Ne1 = 2 # essential energy levels per oscillator 63 | Ne2 = 2 64 | Ng1 = 0 # 0 # Osc-1, number of guard states 65 | Ng2 = 0 # 0 # Osc-2, number of guard states 66 | 67 | Ne = [Ne1, Ne2] 68 | Ng = [Ng1, Ng2] 69 | 70 | N = Ne1*Ne2; # Total number of nonpenalized energy levels 71 | Ntot = (Ne1+Ng1)*(Ne2+Ng2) 72 | Nguard = Ntot - N # total number of guard states 73 | 74 | Nt1 = Ne1 + Ng1 75 | Nt2 = Ne2 + Ng2 76 | 77 | Tmax = 100.0 # Duration of gate 78 | 79 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 80 | fa = 4.10595 # official 81 | fb = 4.81526 # official 82 | rot_freq = [fa, fb] # rotational frequencies 83 | x1 = 2* 0.1099 # official 84 | x2 = 2* 0.1126 # official 85 | x12 = 0.1 # Artificially large to allow coupling. Actual value: 1e-6 86 | 87 | # construct the lowering and raising matricies: amat, bmat, cmat 88 | # and the system Hamiltonian: H0 89 | # 90 | 91 | # Note: The ket psi = ji> = e_j kron e_i. 92 | # We order the elements in the vector psi such that i varies the fastest with i in [1,Nt1] and j in [1,Nt2] 93 | # The matrix amat = I kron a1 acts on alpha in psi = beta kron alpha 94 | # The matrix bmat = a2 kron I acts on beta in psi = beta kron alpha 95 | a1 = Array(Bidiagonal(zeros(Nt1),sqrt.(collect(1:Nt1-1)),:U)) 96 | a2 = Array(Bidiagonal(zeros(Nt2),sqrt.(collect(1:Nt2-1)),:U)) 97 | 98 | I1 = Array{Float64, 2}(I, Nt1, Nt1) 99 | I2 = Array{Float64, 2}(I, Nt2, Nt2) 100 | 101 | # create the a, a^\dag, b and b^\dag vectors 102 | amat = kron(I2, a1) 103 | bmat = kron(a2, I1) 104 | 105 | adag = Array(transpose(amat)) 106 | bdag = Array(transpose(bmat)) 107 | 108 | # number ops 109 | num1 = Diagonal(collect(0:Nt1-1)) 110 | num2 = Diagonal(collect(0:Nt2-1)) 111 | 112 | # number operators 113 | N1 = Diagonal(kron(I2, num1) ) 114 | N2 = Diagonal(kron(num2, I1) ) 115 | 116 | # System Hamiltonian 117 | H0 = -2*pi*( x1/2*(N1*N1-N1) + x2/2*(N2*N2-N2) + x12*(N1*N2) ) 118 | 119 | # max coefficients, rotating frame 120 | amax = 0.02 # max amplitude ctrl func for Hamiltonian #1 121 | bmax = 0.025 # max amplitude ctrl func for Hamiltonian #2 122 | maxpar = [amax, bmax] 123 | 124 | # estimate max magnitude of eigenvalue 125 | K1 = H0 + 126 | (amax.*(amat + amat') + 1im*amax.*(amat - amat') + 127 | bmax.*(bmat + bmat') + 1im*bmax.*(bmat - bmat')) 128 | lamb = eigvals(K1) 129 | maxeig = maximum(abs.(lamb)) 130 | 131 | # Estimate time step 132 | Pmin = 40 # should be 20 or higher 133 | samplerate1 = maxeig*Pmin/(2*pi) 134 | nsteps = ceil(Int64,Tmax*samplerate1) 135 | 136 | println("Number of time steps = ", nsteps) 137 | 138 | # package the lowering and raising matrices together into an one-dimensional array of two-dimensional arrays 139 | # Here we choose dense or sparse representation 140 | #use_sparse = true 141 | use_sparse = false 142 | 143 | # dense matrices run faster, but take more memory 144 | Hsym_ops=[Array(amat+adag), Array(bmat+bdag)] 145 | Hanti_ops=[Array(amat-adag), Array(bmat-bdag)] 146 | H0 = Array(H0) 147 | 148 | Nfreq = 2 # number of carrier frequencies 149 | 150 | Ncoupled = length(Hsym_ops) 151 | om = zeros(Ncoupled, Nfreq) # Allocate space for the carrier wave frequencies 152 | 153 | @assert(Nfreq==1 || Nfreq==2 || Nfreq==3) 154 | if Nfreq == 2 155 | om[1:Ncoupled,2] .= -2.0*pi*x12 # coupling freq for both ctrl funcs (re/im) 156 | elseif Nfreq == 3 157 | om[1,2] = -2.0*pi*x1 # 1st ctrl, re 158 | om[2,2] = -2.0*pi*x2 # 2nd ctrl, re 159 | om[1:Ncoupled,3] .= -2.0*pi*x12 # coupling freq for both ctrl funcs (re/im) 160 | end 161 | 162 | println("Carrier frequencies 1st ctrl Hamiltonian [GHz]: ", om[1,:]./(2*pi)) 163 | println("Carrier frequencies 2nd ctrl Hamiltonian [GHz]: ", om[2,:]./(2*pi)) 164 | 165 | #U0 = my_initial_cond(Ntot, N, Ne, Ng) 166 | U0 = initial_cond(Ne, Ng) 167 | 168 | # SWAP target for the essential levels 169 | gate_swap = zeros(ComplexF64, N, N) 170 | gate_swap[1,1] = 1.0 171 | gate_swap[2,3] = 1.0 172 | gate_swap[3,2] = 1.0 173 | gate_swap[4,4] = 1.0 174 | 175 | utarget = U0 * gate_swap 176 | 177 | # rotation matrices 178 | omega1, omega2 = Juqbox.setup_rotmatrices(Ne, Ng, rot_freq) 179 | 180 | # Compute Ra*Rb*utarget 181 | rot1 = Diagonal(exp.(im*omega1*Tmax)) 182 | rot2 = Diagonal(exp.(im*omega2*Tmax)) 183 | 184 | vtarget = rot1*rot2*utarget # target in the rotating frame 185 | 186 | # assemble problem description for the optimization 187 | Integrator_id = 2 188 | params = Juqbox.objparams(Ne, Ng, Tmax, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 189 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, use_sparse=use_sparse, Integrator = Integrator_id) 190 | 191 | if Integrator_id == 2 192 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 193 | params.linear_solver = linear_solver 194 | end 195 | 196 | # initial parameter guess 197 | startFromScratch = true # false 198 | startFile = "swap2-pcof-opt.dat" 199 | 200 | # dimensions for the parameter vector 201 | D1 = 10 # number of B-spline coeff per oscillator, freq and sin/cos 202 | 203 | nCoeff = 2*Ncoupled*Nfreq*D1 # Total number of parameters. 204 | 205 | Random.seed!(2456) 206 | if startFromScratch 207 | pcof0 = amax*0.01 * rand(nCoeff) 208 | println("*** Starting from pcof with random amplitude ", amax*0.01) 209 | else 210 | pcof0 = vec(readdlm(startFile)) 211 | println("*** Starting from B-spline coefficients in file: ", startFile) 212 | 213 | nCoeff = length(pcof0) 214 | D1 = div(nCoeff, 2*Ncoupled*Nfreq) # number of B-spline coeff per control function 215 | end 216 | 217 | samplerate = 32 # for plotting 218 | casename = "cnot2" # for constructing file names 219 | 220 | # min and max B-spline coefficient values 221 | useBarrier = true 222 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params,D1,maxpar) 223 | println("Number of min coeff: ", length(minCoeff), "Max Coeff: ", length(maxCoeff)) 224 | 225 | maxIter = 150 # 0 #250 #50 # optional argument 226 | lbfgsMax = 250 # optional argument 227 | 228 | println("*** Settings ***") 229 | # output run information 230 | println("Frequencies: fa = ", fa, " fb = ", fb) 231 | println("Coefficients in the Hamiltonian: x1 = ", x1, " x2 = ", x2, " x12 = ", x12) 232 | println("Essential states in osc = ", [Ne1, Ne2], " Guard states in osc = ", [Ng1, Ng2]) 233 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 234 | println("Number of B-spline parameters per spline = ", D1, " Total number of parameters = ", nCoeff) 235 | println("Max parameter amplitudes: maxpar = ", maxpar) 236 | println("Tikhonov regularization tik0 (L2) = ", params.tik0) 237 | if use_sparse 238 | println("Using a sparse representation of the Hamiltonian matrices") 239 | else 240 | println("Using a dense representation of the Hamiltonian matrices") 241 | end 242 | 243 | params.save_pcof_hist = true 244 | 245 | # Allocate all working arrays 246 | if params.Integrator_id == 1 247 | wa = Juqbox.Working_Arrays(params, nCoeff) 248 | elseif params.Integrator_id == 2 249 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 250 | end 251 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax, startFromScratch=startFromScratch) 252 | 253 | #uncomment to run the gradient checker for the initial pcof 254 | #= 255 | if @isdefined addOption 256 | addOption( prob, "derivative_test", "first-order"); # for testing the gradient 257 | else 258 | AddIpoptStrOption( prob, "derivative_test", "first-order") 259 | end 260 | =# 261 | 262 | #uncomment to change print level 263 | #= 264 | if @isdefined addOption 265 | addOption(prob, "print_level", 0); # for testing the gradient 266 | else 267 | AddIpoptIntOption(prob, "print_level", 0) 268 | end 269 | =# 270 | println("Initial coefficient vector stored in 'pcof0'") 271 | 272 | @time traceobjgrad(pcof0, params, wa, false, true) 273 | -------------------------------------------------------------------------------- /test/cases/cnot2-jacobi-setup.jl: -------------------------------------------------------------------------------- 1 | #========================================================== 2 | This routine initializes an optimization problem to recover 3 | a CNOT gate on a coupled 2-qubit system with 2 energy 4 | levels on each oscillator (with 1 guard state on one and 5 | 2 guard states on the other). The drift Hamiltonian in 6 | the rotating frame is 7 | H_0 = - 0.5*ξ_a(a^†a^†aa) 8 | - 0.5*ξ_b(b^†b^†bb) 9 | - ξ_{ab}(a^†ab^†b), 10 | where a,b are the annihilation operators for each qubit. 11 | Here the control Hamiltonian in the rotating frame 12 | includes the usual symmetric and anti-symmetric terms 13 | H_{sym,1} = p_1(t)(a + a^†), H_{asym,1} = q_1(t)(a - a^†), 14 | H_{sym,2} = p_2(t)(b + b^†), H_{asym,2} = q_2(t)(b - b^†). 15 | The problem parameters for this example are, 16 | ω_a = 2π × 4.10595 Grad/s, 17 | ξ_a = 2π × 2(0.1099) Grad/s, 18 | ω_b = 2π × 4.81526 Grad/s, 19 | ξ_b = 2π × 2(0.1126) Grad/s, 20 | ξ_{ab} = 2π × 0.1 Grad/s, 21 | We use Bsplines with carrier waves with frequencies 22 | 0, ξ_a, 2ξ_a Grad/s for each oscillator. 23 | ==========================================================# 24 | using LinearAlgebra 25 | #using Plots 26 | #pyplot() 27 | #using Base.Threads 28 | #using FFTW 29 | using DelimitedFiles 30 | using Printf 31 | #using Ipopt 32 | using Random 33 | using SparseArrays 34 | 35 | #Base.show(io::IO, f::Float64) = @printf(io, "%20.13e", f) 36 | 37 | #import Juqbox 38 | 39 | verbose = false 40 | 41 | Nosc = 2 # number of coupled oscillators 42 | 43 | Ne1 = 2 # essential energy levels per oscillator 44 | Ne2 = 2 45 | Ng1 = 1 # 0 # Osc-1, number of guard states 46 | Ng2 = 2 # 0 # Osc-2, number of guard states 47 | 48 | Ne = [Ne1, Ne2] 49 | Ng = [Ng1, Ng2] 50 | 51 | N = Ne1*Ne2; # Total number of nonpenalized energy levels 52 | Ntot = (Ne1+Ng1)*(Ne2+Ng2) 53 | Nguard = Ntot - N # total number of guard states 54 | 55 | Nt1 = Ne1 + Ng1 56 | Nt2 = Ne2 + Ng2 57 | 58 | Tmax = 100.0 # Duration of gate 59 | 60 | # frequencies (in GHz, will be multiplied by 2*pi to get angular frequencies in the Hamiltonian matrix) 61 | fa = 4.10595 # official 62 | fb = 4.81526 # official 63 | rot_freq = [fa, fb] 64 | x1 = 2* 0.1099 # official 65 | x2 = 2* 0.1126 # official 66 | x12 = 0.1 # Artificially large to allow coupling. Actual value: 1e-6 67 | 68 | # construct the lowering and raising matricies: amat, bmat, cmat 69 | # and the system Hamiltonian: H0 70 | # 71 | 72 | # Note: The ket psi = ji> = e_j kron e_i. 73 | # We order the elements in the vector psi such that i varies the fastest with i in [1,Nt1] and j in [1,Nt2] 74 | # The matrix amat = I kron a1 acts on alpha in psi = beta kron alpha 75 | # The matrix bmat = a2 kron I acts on beta in psi = beta kron alpha 76 | a1 = Array(Bidiagonal(zeros(Nt1),sqrt.(collect(1:Nt1-1)),:U)) 77 | a2 = Array(Bidiagonal(zeros(Nt2),sqrt.(collect(1:Nt2-1)),:U)) 78 | 79 | I1 = Array{Float64, 2}(I, Nt1, Nt1) 80 | I2 = Array{Float64, 2}(I, Nt2, Nt2) 81 | 82 | # create the a, a^\dag, b and b^\dag vectors 83 | amat = kron(I2, a1) 84 | bmat = kron(a2, I1) 85 | 86 | adag = Array(transpose(amat)) 87 | bdag = Array(transpose(bmat)) 88 | 89 | # number ops 90 | num1 = Diagonal(collect(0:Nt1-1)) 91 | num2 = Diagonal(collect(0:Nt2-1)) 92 | 93 | # number operators 94 | N1 = Diagonal(kron(I2, num1) ) 95 | N2 = Diagonal(kron(num2, I1) ) 96 | 97 | # System Hamiltonian 98 | H0 = -2*pi*( x1/2*(N1*N1-N1) + x2/2*(N2*N2-N2) + x12*(N1*N2) ) 99 | 100 | # max coefficients, rotating frame 101 | amax = 0.02 # max amplitude ctrl func for Hamiltonian #1 102 | bmax = 0.05 # max amplitude ctrl func for Hamiltonian #2 103 | maxpar = [amax, bmax] 104 | 105 | # estimate max magnitude of eigenvalue 106 | K1 = H0 + 107 | (amax.*(amat + amat') + 1im*amax.*(amat - amat') + 108 | bmax.*(bmat + bmat') + 1im*bmax.*(bmat - bmat')) 109 | lamb = eigvals(K1) 110 | maxeig = maximum(abs.(lamb)) 111 | 112 | # Estimate time step 113 | Pmin = 40 # should be 20 or higher 114 | samplerate1 = maxeig*Pmin/(2*pi) 115 | nsteps = ceil(Int64,Tmax*samplerate1) 116 | 117 | #println("Number of time steps = ", nsteps) 118 | 119 | # package the lowering and raising matrices together into an one-dimensional array of two-dimensional arrays 120 | # Here we choose dense or sparse representation 121 | use_sparse = false 122 | 123 | # dense matrices run faster, but take more memory 124 | Hsym_ops=[Array(amat+adag), Array(bmat+bdag)] 125 | Hanti_ops=[Array(amat-adag), Array(bmat-bdag)] 126 | H0 = Array(H0) 127 | 128 | use_bcarrier = true # Use carrier waves in the control pulses? 129 | 130 | if use_bcarrier 131 | Nfreq = 2 # number of carrier frequencies 132 | else 133 | Nfreq = 1 134 | end 135 | 136 | Ncoupled = length(Hsym_ops) 137 | om = zeros(Ncoupled, Nfreq) # Allocate space for the carrier wave frequencies 138 | 139 | if use_bcarrier 140 | @assert(Nfreq==1 || Nfreq==2 || Nfreq==3) 141 | if Nfreq == 2 142 | om[1:Ncoupled,2] .= -2.0*pi*x12 # coupling freq for both ctrl funcs (re/im) 143 | elseif Nfreq == 3 144 | om[1,2] = -2.0*pi*x1 # 1st ctrl, re 145 | om[2,2] = -2.0*pi*x2 # 2nd ctrl, re 146 | om[1:Ncoupled,3] .= -2.0*pi*x12 # coupling freq for both ctrl funcs (re/im) 147 | end 148 | end 149 | #println("Carrier frequencies 1st ctrl Hamiltonian [GHz]: ", om[1,:]./(2*pi)) 150 | #println("Carrier frequencies 2nd ctrl Hamiltonian [GHz]: ", om[2,:]./(2*pi)) 151 | 152 | # specify target gate 153 | # target for CNOT gate N=2, Ng = 1 coupled 154 | utarget = zeros(ComplexF64, Ntot, N) 155 | @assert(Ng1 == 0 || Ng1 == 1 || Ng1 == 2) 156 | if Ng1 == 0 157 | utarget[1,1] = 1.0 158 | utarget[2,2] = 1.0 159 | utarget[3,4] = 1.0 160 | utarget[4,3] = 1.0 161 | elseif Ng1 == 1 162 | utarget[1,1] = 1.0 163 | utarget[2,2] = 1.0 164 | utarget[4,4] = 1.0 165 | utarget[5,3] = 1.0 166 | elseif Ng1 == 2 167 | utarget[1,1] = 1.0 168 | utarget[2,2] = 1.0 169 | utarget[5,4] = 1.0 170 | utarget[6,3] = 1.0 171 | end 172 | 173 | # rotation matrices 174 | omega1, omega2 = Juqbox.setup_rotmatrices(Ne, Ng, rot_freq) 175 | 176 | # Compute Ra*Rb*utarget 177 | rot1 = Diagonal(exp.(im*omega1*Tmax)) 178 | rot2 = Diagonal(exp.(im*omega2*Tmax)) 179 | 180 | # target in the rotating frame 181 | vtarget = rot1*rot2*utarget 182 | 183 | U0 = Juqbox.initial_cond(Ne, Ng) 184 | 185 | #Build jacobi solver 186 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER, max_iter=100, tol=1e-15, nrhs=prod(Ne)) 187 | 188 | Integrator_id = 1 189 | # assemble problem description for the optimization 190 | params = Juqbox.objparams(Ne, Ng, Tmax, nsteps, Uinit=U0, Utarget=vtarget, Cfreq=om, Rfreq=rot_freq, 191 | Hconst=H0, Hsym_ops=Hsym_ops, Hanti_ops=Hanti_ops, use_sparse=use_sparse,linear_solver=linear_solver, Integrator = Integrator_id) 192 | 193 | # overwrite default wmat with the old style 194 | params.wmat_real = orig_wmatsetup(Ne, Ng) 195 | if params.Integrator_id == 2 196 | linear_solver = Juqbox.lsolver_object(solver=Juqbox.JACOBI_SOLVER_M,max_iter=100,tol=1e-12,nrhs=prod(N)) 197 | params.linear_solver = linear_solver 198 | end 199 | # Quiet mode for testing 200 | params.quiet = true 201 | 202 | # test 203 | # custom = 0 204 | # guardlev = Juqbox.identify_guard_levels(params, custom) 205 | # forbiddenlev = Juqbox.identify_forbidden_levels(params, custom) 206 | # end 207 | 208 | # initial parameter guess 209 | startFromScratch = false # false 210 | startFile = "cases/cnot2-jacobi.dat" 211 | 212 | # dimensions for the parameter vector 213 | D1 = 10 # number of B-spline coeff per oscillator, freq and sin/cos 214 | 215 | nCoeff = 2*Ncoupled*Nfreq*D1 # Total number of parameters. 216 | 217 | Random.seed!(2456) 218 | if startFromScratch 219 | pcof0 = amax*0.01 * rand(nCoeff) 220 | # println("*** Starting from pcof with random amplitude ", amax*0.01) 221 | else 222 | pcof0 = vec(readdlm(startFile)) 223 | # println("*** Starting from B-spline coefficients in file: ", startFile) 224 | 225 | nCoeff = length(pcof0) 226 | D1 = div(nCoeff, 2*Ncoupled*Nfreq) # number of B-spline coeff per control function 227 | end 228 | 229 | samplerate = 32 # for plotting 230 | casename = "cnot2" # for constructing file names 231 | 232 | # min and max B-spline coefficient values 233 | useBarrier = true 234 | minCoeff, maxCoeff = Juqbox.assign_thresholds(params,D1,maxpar) 235 | #println("Number of min coeff: ", length(minCoeff), "Max Coeff: ", length(maxCoeff)) 236 | 237 | maxIter = 150 # 0 #250 #50 # optional argument 238 | lbfgsMax = 250 # optional argument 239 | 240 | if verbose 241 | println("*** Settings ***") 242 | # output run information 243 | println("Frequencies: fa = ", fa, " fb = ", fb) 244 | println("Coefficients in the Hamiltonian: x1 = ", x1, " x2 = ", x2, " x12 = ", x12) 245 | println("Essential states in osc = ", [Ne1, Ne2], " Guard states in osc = ", [Ng1, Ng2]) 246 | println("Total number of states, Ntot = ", Ntot, " Total number of guard states, Nguard = ", Nguard) 247 | println("Number of B-spline parameters per spline = ", D1, " Total number of parameters = ", nCoeff) 248 | println("Max parameter amplitudes: maxpar = ", maxpar) 249 | println("Tikhonov regularization tik0 (L2) = ", params.tik0) 250 | if use_sparse 251 | println("Using a sparse representation of the Hamiltonian matrices") 252 | else 253 | println("Using a dense representation of the Hamiltonian matrices") 254 | end 255 | end 256 | 257 | # Estimate number of terms in Neumann series for time stepping (Default 3) 258 | tol = eps(1.0); # machine precision 259 | Juqbox.estimate_Neumann!(tol, params, maxpar) 260 | 261 | # Allocate all working arrays 262 | if params.Integrator_id == 1 263 | wa = Juqbox.Working_Arrays(params, nCoeff) 264 | elseif params.Integrator_id == 2 265 | wa = Juqbox.Working_Arrays_M(params, nCoeff) 266 | end 267 | prob = Juqbox.setup_ipopt_problem(params, wa, nCoeff, minCoeff, maxCoeff, maxIter=maxIter, lbfgsMax=lbfgsMax) 268 | 269 | # uncomment to run the gradient checker for the initial pcof 270 | # if @isdefined addOption 271 | # addOption( prob, "derivative_test", "first-order"); # for testing the gradient 272 | # else 273 | # AddIpoptStrOption( prob, "derivative_test", "first-order") 274 | # end 275 | # uncomment to run the gradient checker for the initial pcof 276 | # if @isdefined addOption 277 | # addOption( prob, "print_level", 0); # for testing the gradient 278 | # else 279 | # AddIpoptIntOption( prob, "print_level", 0) 280 | # end 281 | 282 | #println("Initial coefficient vector stored in 'pcof0'") 283 | 284 | --------------------------------------------------------------------------------