Skip to content

Commit cd012a4

Browse files
Merge pull request #243 from harmoniqs/feat/global-params-in-problems
Feat/global params in problems
2 parents 1b49b41 + 819c486 commit cd012a4

7 files changed

Lines changed: 383 additions & 39 deletions

File tree

CONTEXT.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,47 @@ QuantumCollocation.jl **uses** these types and provides problem templates that b
193193
- Adds derivative variables `:du`, `:ddu` for smoothness
194194
- Creates `DerivativeIntegrator` constraints enforcing `u[k+1] - u[k] = Δt * du[k]`
195195
- Applies quadratic regularization on `u`, `du`, `ddu`
196+
- Supports `global_bounds` for time-invariant parameters (requires custom integrator)
196197

197198
**SplinePulseProblem** (for spline pulses):
198199
- For `LinearSplinePulse`: `:du` represents slopes (added automatically)
199200
- For `CubicSplinePulse`: `:du` represents Hermite tangents (built into pulse)
200201
- Uses `DerivativeIntegrator` with spline semantics
202+
- Supports `global_bounds` for time-invariant parameters (requires custom integrator)
203+
204+
### Global Variable Bounds
205+
206+
Both `SmoothPulseProblem` and `SplinePulseProblem` support bounds on global (time-invariant) optimization variables:
207+
208+
```julia
209+
using Piccolissimo # For HermitianExponentialIntegrator or SplineIntegrator
210+
211+
# System with global parameter
212+
H = (u, t) -> u[2] * GATES.Z + u[1] * GATES.X # δ = u[2] is global
213+
sys = QuantumSystem(H, [1.0]; time_dependent=true, global_params==0.1,))
214+
qtraj = UnitaryTrajectory(sys, pulse, U_goal)
215+
216+
# Integrator that supports globals
217+
integrator = HermitianExponentialIntegrator(qtraj, N)
218+
219+
# Symmetric bounds: -0.5 ≤ δ ≤ 0.5
220+
qcp = SmoothPulseProblem(qtraj, N;
221+
integrator=integrator,
222+
global_bounds=Dict( => 0.5))
223+
224+
# Asymmetric bounds: 0.1 ≤ δ ≤ 0.8
225+
qcp = SmoothPulseProblem(qtraj, N;
226+
integrator=integrator,
227+
global_bounds=Dict( => (0.1, 0.8)))
228+
229+
# Multiple globals with mixed bound types
230+
qcp = SplinePulseProblem(qtraj, N;
231+
integrator=SplineIntegrator(qtraj, N; global_names=[, ]),
232+
global_bounds=Dict{Symbol, Union{Float64, Tuple{Float64,Float64}}}(
233+
=> 0.5, # Symmetric
234+
=> (0.001, 0.5) # Asymmetric
235+
))
236+
```
201237

202238
**Adding constraints to SplinePulseProblem:** When working with `SplinePulseProblem`, especially with dynamical timesteps (`Δt_bounds`), add constraints to the existing problem rather than recreating it:
203239

@@ -276,6 +312,7 @@ Run tests with `TestItemRunner.@run_package_tests`.
276312
## Recent Changes (Update This!)
277313

278314
### January 2026
315+
- Added `global_bounds` parameter to `SmoothPulseProblem` and `SplinePulseProblem` for constraining time-invariant optimization variables
279316
- Removed `time_dependent=true` from test QuantumSystem constructions (`:t` is now always in trajectories)
280317
- Removed `adapt_trajectory`/`unadapt_trajectory` usage (control scaling removed)
281318
- Updated `BilinearIntegrator` signatures from `(qtraj, traj)` to `(qtraj, N)`

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "QuantumCollocation"
22
uuid = "0dc23a59-5ffb-49af-b6bd-932a8ae77adf"
3-
version = "0.10.1"
3+
version = "0.10.2"
44
authors = ["Aaron Trowbridge <aaron.j.trowbridge@gmail.com> and contributors"]
55

66
[deps]

docs/literate/man/problem_templates_overview.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# |:---------|:-----------|:-----|:---------|
99
# | [`SmoothPulseProblem`](@ref) | Minimize control effort + infidelity | Fixed | Standard gate/state synthesis with smooth pulses |
1010
# | [`MinimumTimeProblem`](@ref) | Minimize duration | Variable | Fastest gate/state synthesis given fidelity constraint |
11-
# | [`SplinePulseProblem`](@ref) | Minimize control effort + infidelity | Fixed | Gate/state synthesis with spline-based pulses where the derivative variables (`du`) are the actual spline coefficients or slopes. |
11+
# | [`SplinePulseProblem`](@ref) | Minimize control effort + infidelity | Fixed | Gate/state synthesis with spline-based pulses (linear or cubic Hermite) |
1212
# | [`SamplingProblem`](@ref) | Minimize control effort + weighted sum of infidelity objectives | Fixed | Robust gate/state synthesis where the controls are shared across all systems, with differing dynamics. |
1313

1414
# ### Smooth Pulse vs Minimum Time

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ That's it! You've optimized control pulses for a quantum gate.
5252
## What Can QuantumCollocation Do?
5353

5454
- **Unitary gate optimization** - Find pulses to implement quantum gates
55-
- **Open quantum systems** - Find pulses for lindladian dynamics
55+
- **Open quantum systems** - Find pulses for Lindbladian dynamics
5656
- **State transfer** - Drive quantum states to target states
5757
- **Minimum time control** - Optimize gate duration
5858
- **Robust control** - Account for system uncertainties

src/problem_templates/_problem_templates.jl

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,118 @@ function apply_piccolo_options!(
8282
return J
8383
end
8484

85+
"""
86+
add_global_bounds_constraints!(constraints, global_bounds, traj; verbose=false)
87+
88+
Add GlobalBoundsConstraint entries for each global variable specified in `global_bounds`.
89+
90+
Converts bounds from user-friendly formats to the format expected by GlobalBoundsConstraint:
91+
- `Float64`: Symmetric scalar bounds (applied symmetrically to all dimensions)
92+
- `Tuple{Float64, Float64}`: Asymmetric scalar bounds (expanded to vectors)
93+
- `Vector` or `Tuple{Vector, Vector}`: Already in correct format (passed through)
94+
95+
Modifies `constraints` in place.
96+
"""
97+
function add_global_bounds_constraints!(
98+
constraints::AbstractVector{<:AbstractConstraint},
99+
global_bounds,
100+
traj::NamedTrajectory;
101+
verbose::Bool=false
102+
)
103+
if isnothing(global_bounds)
104+
return
105+
end
106+
107+
for (name, bounds) in global_bounds
108+
if !haskey(traj.global_components, name)
109+
error("Global variable :$name not found in trajectory. Available: $(keys(traj.global_components))")
110+
end
111+
global_dim = length(traj.global_components[name])
112+
# Convert bounds to format expected by GlobalBoundsConstraint
113+
if bounds isa Float64
114+
# Symmetric scalar bounds
115+
bounds_value = bounds
116+
elseif bounds isa Tuple{Float64, Float64}
117+
# Asymmetric scalar bounds -> convert to vector tuple
118+
bounds_value = (fill(bounds[1], global_dim), fill(bounds[2], global_dim))
119+
else
120+
# Already in correct format (Vector or Tuple of Vectors)
121+
bounds_value = bounds
122+
end
123+
push!(constraints, GlobalBoundsConstraint(name, bounds_value))
124+
if verbose
125+
println(" added GlobalBoundsConstraint for :$name with bounds $bounds_value")
126+
end
127+
end
128+
end
129+
130+
@testitem "add_global_bounds_constraints! helper function" begin
131+
using QuantumCollocation
132+
using NamedTrajectories
133+
using DirectTrajOpt
134+
135+
# Create a trajectory with global components for testing
136+
# global_data is a flat vector, global_components maps names to index ranges
137+
N = 5
138+
traj = NamedTrajectory(
139+
(x = rand(2, N), u = rand(1, N), Δt = fill(0.1, N));
140+
timestep=:Δt,
141+
controls=:u,
142+
global_data=[0.1, 0.5, 0.3], # flat vector
143+
global_components== 1:1, ω = 2:3) # δ is scalar, ω is 2D
144+
)
145+
146+
# Test 1: nothing global_bounds is a no-op
147+
constraints1 = AbstractConstraint[]
148+
QuantumCollocation.ProblemTemplates.add_global_bounds_constraints!(
149+
constraints1, nothing, traj
150+
)
151+
@test isempty(constraints1)
152+
153+
# Test 2: Float64 symmetric scalar bounds
154+
constraints2 = AbstractConstraint[]
155+
QuantumCollocation.ProblemTemplates.add_global_bounds_constraints!(
156+
constraints2, Dict( => 0.5), traj
157+
)
158+
@test length(constraints2) == 1
159+
@test constraints2[1] isa BoundsConstraint
160+
@test constraints2[1].is_global
161+
162+
# Test 3: Tuple{Float64, Float64} asymmetric scalar bounds (expanded to vectors)
163+
constraints3 = AbstractConstraint[]
164+
QuantumCollocation.ProblemTemplates.add_global_bounds_constraints!(
165+
constraints3, Dict( => (-0.2, 0.8)), traj
166+
)
167+
@test length(constraints3) == 1
168+
@test constraints3[1] isa BoundsConstraint
169+
@test constraints3[1].is_global
170+
171+
# Test 4: Multiple globals with mixed bound types
172+
constraints4 = AbstractConstraint[]
173+
global_bounds = Dict{Symbol, Union{Float64, Tuple{Float64, Float64}}}(
174+
=> 0.5, # symmetric
175+
=> (-0.2, 0.8) # asymmetric
176+
)
177+
QuantumCollocation.ProblemTemplates.add_global_bounds_constraints!(
178+
constraints4, global_bounds, traj
179+
)
180+
@test length(constraints4) == 2
181+
@test all(c -> c isa BoundsConstraint && c.is_global, constraints4)
182+
183+
# Test 5: Error when global variable doesn't exist
184+
constraints5 = AbstractConstraint[]
185+
@test_throws "Global variable :nonexistent not found" begin
186+
QuantumCollocation.ProblemTemplates.add_global_bounds_constraints!(
187+
constraints5, Dict(:nonexistent => 0.5), traj
188+
)
189+
end
190+
191+
# Test 6: Verbose output (just ensure it doesn't error)
192+
constraints6 = AbstractConstraint[]
193+
QuantumCollocation.ProblemTemplates.add_global_bounds_constraints!(
194+
constraints6, Dict( => 0.5), traj; verbose=true
195+
)
196+
@test length(constraints6) == 1
197+
end
198+
85199
end

0 commit comments

Comments
 (0)