Pulses

Pulses parameterize how control amplitudes $\boldsymbol{u}(t)$ vary over time. The choice of pulse type determines both the NLP structure and the smoothness class of the resulting controls.

Overview

Pulse TypeContinuityDecision VariablesUse With
ZeroOrderPulse$C^{-1}$ (piecewise constant)$\boldsymbol{u}_k$SmoothPulseProblem
LinearSplinePulse$C^0$$\boldsymbol{u}_k$ (knot values)SplinePulseProblem
CubicSplinePulse$C^1$$\boldsymbol{u}_k,\, \dot{\boldsymbol{u}}_k$ (values + tangents)SplinePulseProblem
GaussianPulse$C^\infty$$A_i, \sigma_i, \mu_i$ (parametric)Analytical
CompositePulsevariesunion of sub-pulse variablesVarious

ZeroOrderPulse

Piecewise constant (zero-order hold) controls — the most common choice. On the interval $[t_k, t_{k+1})$, the control is constant:

\[\boldsymbol{u}(t) = \boldsymbol{u}_k, \qquad t \in [t_k,\, t_{k+1})\]

Smoothness is enforced indirectly via regularization of the discrete differences $\Delta\boldsymbol{u}_k$ and $\Delta^2\boldsymbol{u}_k$ (see Objectives).

Construction

using Piccolo

n_dr = 2
N = 100
T = 10.0

# Time points
times = collect(range(0, T, length = N))

# Control values: n_drives × N matrix
controls = 0.1 * randn(n_dr, N)

pulse_zop = ZeroOrderPulse(controls, times)
ZeroOrderPulse{DataInterpolations.ConstantInterpolation{Matrix{Float64}, Vector{Float64}, Vector{Union{}}, Float64}}(DataInterpolations.ConstantInterpolation{Matrix{Float64}, Vector{Float64}, Vector{Union{}}, Float64}([0.08861190830899232 -0.18448401928779687 … 0.01571513644239231 0.026151869889780133; -0.042510746202708095 0.014436288071769793 … -0.10347924666673816 0.0601304058084216], [0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091  …  9.090909090909092, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0], Union{}[], nothing, :left, DataInterpolations.ExtrapolationType.None, DataInterpolations.ExtrapolationType.None, FindFirstFunctions.Guesser{Vector{Float64}}([0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091  …  9.090909090909092, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0], Base.RefValue{Int64}(1), true), false, true), 10.0, 2, :u, [0.0, 0.0], [0.0, 0.0])

Properties

# Evaluate at time t
u = pulse_zop(T / 2)
u

# Duration
duration(pulse_zop)

# Number of drives
n_drives(pulse_zop)
2

Visualization

Control Value
    │    ┌──┐
    │    │  │  ┌──┐
    │────┘  │  │  └───
    │       └──┘
    └─────────────────── Time

Use Case

  • Primary use: SmoothPulseProblem
  • Characteristics: Simple structure, smoothness via derivative regularization
  • Best for: Initial optimization, most quantum control problems

LinearSplinePulse

Linear interpolation between knot values. On $[t_k, t_{k+1}]$:

\[\boldsymbol{u}(t) = \boldsymbol{u}_k + \frac{t - t_k}{t_{k+1} - t_k}\,(\boldsymbol{u}_{k+1} - \boldsymbol{u}_k)\]

This gives $C^0$ continuity (continuous values, discontinuous first derivative at knots).

Construction

pulse_linear = LinearSplinePulse(controls, times)
LinearSplinePulse{DataInterpolations.LinearInterpolation{Matrix{Float64}, Vector{Float64}, Vector{Union{}}, Vector{Vector{Float64}}, Float64}}(DataInterpolations.LinearInterpolation{Matrix{Float64}, Vector{Float64}, Vector{Union{}}, Vector{Vector{Float64}}, Float64}([0.08861190830899232 -0.18448401928779687 … 0.01571513644239231 0.026151869889780133; -0.042510746202708095 0.014436288071769793 … -0.10347924666673816 0.0601304058084216], [0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091  …  9.090909090909092, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0], Union{}[], DataInterpolations.LinearParameterCache{Vector{Vector{Float64}}}(Vector{Float64}[]), DataInterpolations.ExtrapolationType.None, DataInterpolations.ExtrapolationType.None, FindFirstFunctions.Guesser{Vector{Float64}}([0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091  …  9.090909090909092, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0], Base.RefValue{Int64}(1), true), false, true), 10.0, 2, :u, [0.0, 0.0], [0.0, 0.0])

Properties

  • Continuous control values
  • Discontinuous first derivative (at knots)
  • Derivative = slope between knots
u_linear = pulse_linear(T / 2)
u_linear
2-element Vector{Float64}:
 -0.019906481094511007
  0.02857639801089064

Visualization

Control Value
    │      /\
    │     /  \    /
    │    /    \  /
    │───/      \/
    └─────────────────── Time

CubicSplinePulse

Cubic Hermite spline interpolation with independent tangents $\dot{\boldsymbol{u}}_k$ at each knot. The Hermite basis gives $C^1$ continuity (continuous values and first derivatives).

On $[t_k, t_{k+1}]$ with $s = (t - t_k) / (t_{k+1} - t_k) \in [0, 1]$ and $h = t_{k+1} - t_k$:

\[\boldsymbol{u}(t) = (2s^3 - 3s^2 + 1)\,\boldsymbol{u}_k + (s^3 - 2s^2 + s)\,h\,\dot{\boldsymbol{u}}_k + (-2s^3 + 3s^2)\,\boldsymbol{u}_{k+1} + (s^3 - s^2)\,h\,\dot{\boldsymbol{u}}_{k+1}\]

Both $\boldsymbol{u}_k$ and $\dot{\boldsymbol{u}}_k$ are decision variables in SplinePulseProblem.

Construction

tangents = zeros(n_dr, N)  # Initial tangents (slopes)
pulse_cubic = CubicSplinePulse(controls, tangents, times)
CubicSplinePulse{DataInterpolations.CubicHermiteSpline{Matrix{Float64}, Vector{Float64}, Vector{Union{}}, Matrix{Float64}, Vector{Vector{Float64}}, Float64}}(DataInterpolations.CubicHermiteSpline{Matrix{Float64}, Vector{Float64}, Vector{Union{}}, Matrix{Float64}, Vector{Vector{Float64}}, Float64}([0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0], [0.08861190830899232 -0.18448401928779687 … 0.01571513644239231 0.026151869889780133; -0.042510746202708095 0.014436288071769793 … -0.10347924666673816 0.0601304058084216], [0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091  …  9.090909090909092, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0], Union{}[], DataInterpolations.CubicHermiteParameterCache{Vector{Vector{Float64}}}(Vector{Float64}[], Vector{Float64}[]), DataInterpolations.ExtrapolationType.None, DataInterpolations.ExtrapolationType.None, FindFirstFunctions.Guesser{Vector{Float64}}([0.0, 0.10101010101010101, 0.20202020202020202, 0.30303030303030304, 0.40404040404040403, 0.5050505050505051, 0.6060606060606061, 0.7070707070707071, 0.8080808080808081, 0.9090909090909091  …  9.090909090909092, 9.191919191919192, 9.292929292929292, 9.393939393939394, 9.494949494949495, 9.595959595959595, 9.696969696969697, 9.797979797979798, 9.8989898989899, 10.0], Base.RefValue{Int64}(1), true), false, true), 10.0, 2, :u, [0.0, 0.0], [0.0, 0.0])

Properties

  • Continuous control values AND first derivatives
  • Tangents are independent optimization variables
  • Smooth C¹ continuous curves
u_cubic = pulse_cubic(T / 2)
u_cubic
2-element Vector{Float64}:
 -0.01990648109451101
  0.02857639801089064

GaussianPulse

Parametric Gaussian envelope:

\[u_i(t) = A_i \exp\!\left(-\frac{(t - \mu_i)^2}{2\sigma_i^2}\right)\]

Useful for analytical pulse design and warm-starting.

Construction

amplitudes = [0.5, 0.3]
sigmas = [1.0, 1.5]
centers = [5.0, 5.0]

pulse_gauss = GaussianPulse(amplitudes, sigmas, centers, T)

u_gauss = pulse_gauss(5.0)
u_gauss
2-element Vector{Float64}:
 0.5
 0.3

CompositePulse

Combine multiple pulses (e.g., different drives from different sources):

pulse1 = GaussianPulse([0.5], [0.5], [2.0], T)
pulse2 = GaussianPulse([0.3], [0.5], [8.0], T)

composite = CompositePulse([pulse1, pulse2])
n_drives(composite)
2

Choosing a Pulse Type

ScenarioRecommended PulseSmoothness
Starting freshZeroOrderPulse + SmoothPulseProblem$C^{-1}$ (regularized)
Refining a solutionCubicSplinePulse + SplinePulseProblem$C^1$
Hardware requires smooth pulsesCubicSplinePulse$C^1$
Simple continuous pulsesLinearSplinePulse$C^0$
Analytical pulse designGaussianPulse$C^\infty$

Converting Between Pulse Types

ZeroOrderPulse → CubicSplinePulse

# Sample control values from the zero-order pulse
ctrl = hcat([pulse_zop(t) for t in times]...)

# Estimate tangents (finite differences)
tgts = similar(ctrl)
for k = 1:(N-1)
    tgts[:, k] = (ctrl[:, k+1] - ctrl[:, k]) / (times[k+1] - times[k])
end
tgts[:, N] = tgts[:, N-1]

# Create cubic spline
cubic_from_zop = CubicSplinePulse(ctrl, tgts, times)
duration(cubic_from_zop)
10.0

Arbitrary → ZeroOrderPulse

# Sample any pulse type to create a ZeroOrderPulse
new_times = collect(range(0, T, length = 200))
new_ctrl = hcat([pulse_cubic(t) for t in new_times]...)
resampled = ZeroOrderPulse(new_ctrl, new_times)
length(new_times)
200

Best Practices

1. Initialize Appropriately

max_amp = 0.1 * maximum(drive_bounds)
controls = max_amp * randn(n_drives, N)

2. Use Enough Time Points

# Rule of thumb: ~10 points per characteristic time scale
T = 10.0  # Total time
τ = 1.0   # Shortest feature you want to capture
N = ceil(Int, 10 * T / τ)

3. Start with ZeroOrderPulse

Even if you need smooth pulses, optimize with ZeroOrderPulse first, then convert to CubicSplinePulse for refinement.

See Also


This page was generated using Literate.jl.