Pulses

Pulses in Piccolo.jl parameterize how control signals vary over time. The choice of pulse type affects both the optimization problem structure and the resulting control smoothness.

Overview

Pulse TypeDescriptionUse With
ZeroOrderPulsePiecewise constantSmoothPulseProblem
LinearSplinePulseLinear interpolationSplinePulseProblem
CubicSplinePulseCubic Hermite splinesSplinePulseProblem
GaussianPulseParametric GaussianAnalytical evaluation
CompositePulseCombination of pulsesVarious

ZeroOrderPulse

Piecewise constant (zero-order hold) controls. The most common choice for initial optimization.

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.060146131100994864 -0.047992691017512225 … 0.05893789810987802 0.26367027649393154; 0.16549540383962968 -0.15227029372710413 … -0.048301245927034525 -0.09116904627749467], [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 control 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.060146131100994864 -0.047992691017512225 … 0.05893789810987802 0.26367027649393154; 0.16549540383962968 -0.15227029372710413 … -0.048301245927034525 -0.09116904627749467], [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.05563346685587807
  0.07406621275850972

Visualization

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

CubicSplinePulse

Cubic Hermite spline interpolation with independent tangents at each knot.

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.060146131100994864 -0.047992691017512225 … 0.05893789810987802 0.26367027649393154; 0.16549540383962968 -0.15227029372710413 … -0.048301245927034525 -0.09116904627749467], [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.05563346685587807
  0.07406621275850972

GaussianPulse

Parametric Gaussian envelope with analytical form.

Construction

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

pulse_gauss = GaussianPulse(amplitudes, sigmas, centers, T)
GaussianPulse{Piccolo.Quantum.Pulses.var"#GaussianPulse##0#GaussianPulse##1"{Vector{Float64}, Vector{Float64}, Vector{Float64}, Int64}}(Piccolo.Quantum.Pulses.var"#GaussianPulse##0#GaussianPulse##1"{Vector{Float64}, Vector{Float64}, Vector{Float64}, Int64}([5.0, 5.0], [1.0, 1.5], [0.5, 0.3], 2), [0.5, 0.3], [1.0, 1.5], [5.0, 5.0], 10.0, 2)

Mathematical Form

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

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

CompositePulse

Combine multiple pulses.

Construction

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

# Interleave the pulses (each pulse contributes different drives)
composite = CompositePulse([pulse1, pulse2])
n_drives(composite)
2

Choosing a Pulse Type

Decision Guide

Start
  │
  ▼
Is this your first optimization attempt?
  │
  ├── Yes → ZeroOrderPulse + SmoothPulseProblem
  │
  └── No → Do you have a previous solution?
              │
              ├── Yes → CubicSplinePulse + SplinePulseProblem (warm-start)
              │
              └── No → Do you need smooth pulses?
                          │
                          ├── Yes → CubicSplinePulse
                          │
                          └── No → ZeroOrderPulse

Practical Recommendations

ScenarioRecommended Pulse
Starting freshZeroOrderPulse
Refining a solutionCubicSplinePulse
Hardware requires smooth pulsesCubicSplinePulse
Simple continuous pulsesLinearSplinePulse
Analytical pulse designGaussianPulse

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

# Scale by drive bounds
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.