Trajectories
A trajectory bundles a quantum system, a control pulse, and a goal into the central object that problem templates consume.
Overview
Every trajectory type defines:
- The state$x_k$ and its dynamics $x_{k+1} = \exp(\Delta t_k\, G(\boldsymbol{u}_k))\, x_k$
- The initial condition$x_1 = x_{\text{init}}$
- The goal$x_{\text{goal}}$ and associated fidelity metric
Trajectory Types
| Type | Dynamics | State $x_k$ | Fidelity $F$ | $\dim(x_k)$ |
|---|---|---|---|---|
UnitaryTrajectory | $\dot{U} = -iH\,U$ | $\tilde{U} \in \mathbb{R}^{2d^2}$ | $\lvert\operatorname{tr}(U_g^\dagger U)\rvert^2 / d^2$ | $2d^2$ |
KetTrajectory | $\dot{\psi} = -iH\,\psi$ | $\tilde{\psi} \in \mathbb{R}^{2d}$ | $\lvert\langle\psi_g\mid\psi\rangle\rvert^2$ | $2d$ |
DensityTrajectory | $\dot{\rho} = \mathcal{G}\,\text{vec}(\rho)$ | $\tilde{\rho} \in \mathbb{R}^{d^2}$ (compact) | $\operatorname{tr}(\rho_g\,\rho)$ | $d^2$ |
MultiKetTrajectory | $\dot{\psi}_j = -iH\,\psi_j$ | multiple $\tilde{\psi}_j$ | coherent (see below) | $2d \times n_{\text{states}}$ |
MultiDensityTrajectory | $\dot{\rho}_j = \mathcal{G}\,\text{vec}(\rho_j)$ | multiple $\tilde{\rho}_j$ | weighted average (see below) | $d^2 \times M$ |
SamplingTrajectory | per-system dynamics | per-system states | average fidelity | varies |
The isomorphic state $x_k$ is always a real vector; see Isomorphisms for the conversions.
UnitaryTrajectory
For synthesizing quantum gates. The propagator satisfies $\dot{U} = G(\boldsymbol{u})\,U$ with $U(0) = I$.
Construction
using Piccolo
# Define system
H_drift = PAULIS[:Z]
H_drives = [PAULIS[:X], PAULIS[:Y]]
sys = QuantumSystem(H_drift, H_drives, [1.0, 1.0])
# Create pulse
T, N = 10.0, 100
times = collect(range(0, T, length = N))
pulse = ZeroOrderPulse(0.1 * randn(2, N), times)
# Create trajectory with goal
U_goal = GATES[:X]
qtraj = UnitaryTrajectory(sys, pulse, U_goal)UnitaryTrajectory
system: 2-level QuantumSystem
drives: ZeroOrderPulse with 2 drives
duration: 10.0Solve and Analyze
qcp = SmoothPulseProblem(qtraj, N; Q = 100.0)
solve!(qcp; max_iter = 50)
fidelity(qcp)0.9997914029235871Extracting the Pulse
optimized_pulse = get_pulse(qcp.qtraj)
duration(optimized_pulse)14.262180172049334KetTrajectory
For state preparation: find controls that map $|\psi_{\text{init}}\rangle \to |\psi_{\text{goal}}\rangle$ (up to global phase). The fidelity is $F = |\langle\psi_{\text{goal}}|\psi(T)\rangle|^2$.
Construction
# Initial and goal states
ψ_init = ComplexF64[1, 0] # |0⟩
ψ_goal = ComplexF64[0, 1] # |1⟩
qtraj_ket = KetTrajectory(sys, pulse, ψ_init, ψ_goal)KetTrajectory
system: 2-level QuantumSystem
drives: ZeroOrderPulse with 2 drives
duration: 10.0Solve
qcp_ket = SmoothPulseProblem(qtraj_ket, N; Q = 100.0)
solve!(qcp_ket; max_iter = 50)
fidelity(qcp_ket)0.9999980412691498MultiKetTrajectory
For gates defined by multiple state mappings with coherent phases. The fidelity enforces phase alignment across all state pairs:
\[F = \left| \frac{1}{n} \sum_{j=1}^{n} \langle \psi_{\text{goal},j} | \psi_j(T) \rangle \right|^2\]
This is strictly harder than per-state fidelity because relative phases must be correct for the mapping to represent a valid gate.
Construction
# Define state pairs: X gate maps |0⟩ → |1⟩ and |1⟩ → |0⟩
ψ0 = ComplexF64[1, 0]
ψ1 = ComplexF64[0, 1]
initial_states = [ψ0, ψ1]
goal_states = [ψ1, ψ0]
qtraj_multi = MultiKetTrajectory(sys, pulse, initial_states, goal_states)MultiKetTrajectory
system: 2-level QuantumSystem
drives: ZeroOrderPulse with 2 drives
duration: 10.0Solve
qcp_multi = SmoothPulseProblem(qtraj_multi, N; Q = 100.0)
solve!(qcp_multi; max_iter = 50)
fidelity(qcp_multi)0.9999654067440766DensityTrajectory
For open quantum systems governed by the Lindblad master equation. The state $\rho(t)$ evolves as $\dot{\vec\rho} = \mathcal{G}(\boldsymbol{u})\,\vec\rho$ where $\mathcal{G}$ is the Lindbladian superoperator (see Systems).
Internally the state is stored in the compact isomorphism: a real vector of dimension $d^2$ (not $2d^2$) that exploits Hermiticity. The Lindbladian generators are also compacted via $\mathcal{G}_c = P\,\mathcal{G}\,L$ (size $d^2 \times d^2$ instead of $2d^2 \times 2d^2$), giving roughly a 4× speedup in integration. See Isomorphisms for details.
Construction
# Open system with a weak dissipation operator
L = ComplexF64[0.1 0.0; 0.0 0.0]
open_sys = OpenQuantumSystem(
PAULIS[:Z],
[PAULIS[:X], PAULIS[:Y]],
[1.0, 1.0];
dissipation_operators = [L],
)
# Initial and goal density matrices
ρ_init = ComplexF64[1.0 0.0; 0.0 0.0] # |0⟩⟨0|
ρ_goal = ComplexF64[0.0 0.0; 0.0 1.0] # |1⟩⟨1|
T_density, N_density = 10.0, 50
times_density = collect(range(0, T_density, length = N_density))
pulse_density = ZeroOrderPulse(0.1 * randn(2, N_density), times_density)
qtraj_density = DensityTrajectory(open_sys, pulse_density, ρ_init, ρ_goal)DensityTrajectory
system: 2-level OpenQuantumSystem
drives: ZeroOrderPulse with 2 drives
duration: 10.0Solve and Analyze
The fidelity for density matrices is $F = \operatorname{tr}(\rho_{\text{goal}}\,\rho(T))$.
qcp_density = SmoothPulseProblem(qtraj_density, N_density; Q = 100.0, R = 1e-2)
solve!(qcp_density; max_iter = 150)
fidelity(qcp_density)0.9948951115506884MultiDensityTrajectory
For optimizing open quantum systems that must simultaneously steer multiple initial density matrices to their respective targets — e.g. process tomography or channel certification. All density matrices share a single control pulse and Lindblad generator. The fidelity is a weighted average:
\[F = \sum_{j=1}^{M} w_j\, \operatorname{tr}(\rho_{\text{goal},j}\,\rho_j(T)), \qquad \sum_j w_j = 1\]
Internally, M Lindblad ODEs are solved in parallel via DifferentialEquations.EnsembleProblem.
Construction
# Open system: 2-level atom with T₁ decay
L_decay = ComplexF64[0 0; 1 0] ## |0⟩⟨1| collapse operator (decay rate = 1)
open_sys2 = OpenQuantumSystem(
PAULIS[:Z],
[PAULIS[:X], PAULIS[:Y]],
[1.0, 1.0];
dissipation_operators = [L_decay],
)
# Prepare two initial → goal pairs (e.g., two rows of a process matrix)
ρ0_a = ComplexF64[1 0; 0 0] ## |0⟩⟨0|
ρ0_b = ComplexF64[0 0; 0 1] ## |1⟩⟨1|
ρg_a = ComplexF64[0 0; 0 1] ## |1⟩⟨1|
ρg_b = ComplexF64[1 0; 0 0] ## |0⟩⟨0|
T_md, N_md = 10.0, 50
times_md = collect(range(0, T_md, length = N_md))
pulse_md = ZeroOrderPulse(0.1 * randn(2, N_md), times_md)
qtraj_md = MultiDensityTrajectory(
open_sys2,
pulse_md,
[ρ0_a, ρ0_b],
[ρg_a, ρg_b];
weights = [0.5, 0.5],
)MultiDensityTrajectory
system: 2-level OpenQuantumSystem
drives: ZeroOrderPulse with 2 drives
duration: 10.0Rollout and Fidelity
qtraj_md_out = rollout(qtraj_md)
fidelity(qtraj_md_out)0.4999661785377583Built-in optimization support for MultiDensityTrajectory (via SmoothPulseProblem) requires a Piccolissimo integrator that provides per-density Lindbladian dynamics. Rollout and fidelity evaluation work out of the box with any pulse type.
When to Use vs DensityTrajectory
| Scenario | Use |
|---|---|
| Single initial state | DensityTrajectory |
| Process tomography / multiple initial states | MultiDensityTrajectory |
| Weighted importance across initial states | MultiDensityTrajectory with custom weights |
SamplingTrajectory
For robust optimization over parameter variations. Created internally by SamplingProblem, this trajectory type stores multiple system variants sharing a single control pulse. The objective averages fidelity across all samples:
\[\bar{\ell} = \frac{1}{S}\sum_{s=1}^{S} \ell\!\bigl(x_N^{(s)},\, x_{\text{goal}}\bigr)\]
systems = [sys_nominal, sys_high, sys_low]
qcp_robust = SamplingProblem(qcp_base, systems)Common Operations
Extracting and Saving Pulses
After solving, extract the optimized pulse with get_pulse and persist it with save:
optimized_pulse = get_pulse(qcp.qtraj)
duration(optimized_pulse)14.262180172049334Save and reload using the pulse-aware JLD2 helpers:
save("my_gate.jld2", optimized_pulse)
saved_pulse = load_pulse("my_gate.jld2")
qtraj_warm = UnitaryTrajectory(sys, saved_pulse, U_goal)
qcp_warm = SmoothPulseProblem(qtraj_warm, N; Q=1000.0)
solve!(qcp_warm; max_iter=50) # converges faster from a good initial guessSee Saving and Loading Pulses for the full guide.
Named Trajectory Integration
After solving, the NamedTrajectory stores the NLP decision vector at each of the $N$ knot points:
traj = get_trajectory(qcp)
# Controls at timestep k
u_1 = traj[1][:u]
u_1
# All timesteps
Δts = get_timesteps(traj)
length(Δts)100Internal Representation
States in the trajectory are real isomorphic vectors. Convert back to complex form with:
U = iso_vec_to_operator(traj[:Ũ⃗][:, end]) # unitary
ψ = iso_to_ket(traj[:ψ̃][:, end]) # ket
ρ = compact_iso_to_density(traj[:ρ⃗̃][:, end]) # density matrixSee Isomorphisms for details.
Best Practices
1. Match Pulse Type to Problem
# For SmoothPulseProblem
pulse = ZeroOrderPulse(controls, times)
qtraj = UnitaryTrajectory(sys, pulse, U_goal)
qcp = SmoothPulseProblem(qtraj, N) # ✓
# For SplinePulseProblem
pulse = CubicSplinePulse(controls, tangents, times)
qtraj = UnitaryTrajectory(sys, pulse, U_goal)
qcp = SplinePulseProblem(qtraj) # ✓2. Initialize with Reasonable Controls
# Scale by drive bounds
max_amp = 0.1 * 1.0
initial_controls = max_amp * randn(2, N)
extrema(initial_controls)(-0.24976391118628466, 0.2827190729912068)See Also
- Quantum Systems - System definitions
- Pulses - Control parameterizations
- Problem Templates - Using trajectories in optimization
This page was generated using Literate.jl.