Skip to content

feat: Add support for zero control dimension (NLP_u = 0)#578

Merged
PierreMartinon merged 3 commits intomainfrom
dim-zero-control
Mar 25, 2026
Merged

feat: Add support for zero control dimension (NLP_u = 0)#578
PierreMartinon merged 3 commits intomainfrom
dim-zero-control

Conversation

@ocots
Copy link
Member

@ocots ocots commented Mar 21, 2026

  • Add efficiency guards in DOCP_variables.jl and ode/common.jl
  • Fix reshape bug in collocation.jl for zero control dimension
  • Add parameter estimation test problems (estimate_initial_condition, estimate_rotation_rate, least_squares_with_constraint)
  • Add comprehensive unit tests (49 tests) for zero control allocations
  • Add integration tests (118 tests) for zero control dimension
  • All tests pass successfully

- Add efficiency guards in DOCP_variables.jl and ode/common.jl
- Fix reshape bug in collocation.jl for zero control dimension
- Add parameter estimation test problems (estimate_initial_condition, estimate_rotation_rate, least_squares_with_constraint)
- Add comprehensive unit tests (49 tests) for zero control allocations
- Add integration tests (118 tests) for zero control dimension
- All tests pass successfully
@ocots
Copy link
Member Author

ocots commented Mar 21, 2026

Add support for zero control dimension (NLP_u = 0)

This PR implements full support for optimal control problems with zero control dimension in CTDirect.jl, enabling parameter estimation.

Problem Statement

Previously, CTDirect.jl did not properly handle problems where NLP_u = 0 (no control variables). This caused:

  • Unnecessary loops over empty control arrays
  • Allocation issues in initial guess construction
  • Reshape errors in collocation methods
  • Missing test coverage for this edge case

Solution

1. Efficiency Guards in Source Code

src/DOCP_variables.jl

Added guard to skip control bounds loops when NLP_u = 0:

if docp.dims.NLP_u > 0
    for i in 1:(docp.time.steps + 1)
        for j in 1:docp.time.control_steps
            set_control_at_time_step!(var_l, u_lb, docp, i; j=j)
            set_control_at_time_step!(var_u, u_ub, docp, i; j=j)
        end
    end
end

Similar guard added in __initial_guess to avoid iterating over control steps.

src/ode/common.jl

Added early return in control getter when dimension is zero:

function get_control_at_time_step(xu, docp::DOCP, time_step::Int)
    docp.dims.NLP_u == 0 && return view(xu, 1:0)  # Empty view
    # ... rest of implementation
end

src/collocation.jl

Fixed reshape bug for ExaModel initial guess when m = 0:

m = CTModels.control_dimension(ocp)
if m > 0
    control = hcat([x0[(n + 1 + i * (n + m)):(n + 1 + i * (n + m) + m - 1)] for i in 0:(N - 1)]...,)
    control = [control control[:, end]]
else
    control = zeros(0, N + 1)  # Empty control array
end

2. Parameter Estimation Test Problems

Created three well-posed parameter estimation problems in test/problems/autonomous_system.jl:

  • estimate_initial_condition(): Estimate initial condition of harmonic oscillator to reach target final state
  • estimate_rotation_rate(): Estimate rotation rate parameter in dynamics
  • least_squares_with_constraint(): Least squares fit with path constraint

All problems use proper CTModels syntax:

  • Mutating dynamics: dynamics!(r, t, x, u, v) = r .= ...
  • Complete signatures with u and v parameters even when unused
  • Proper definition! and time_dependence! calls

3. Comprehensive Test Coverage

Unit Tests (test/ci/test_zero_control_allocations.jl) - 49 tests

  • DOCP creation and dimension verification
  • Control getters return empty views
  • Control setters are no-ops
  • All schemes support zero control
  • Variables bounds with zero control
  • Initial guess construction
  • Sparsity patterns

Integration Tests (test/ci/test_zero_control.jl) - 118 tests

  • All discretization schemes (euler, midpoint, trapeze)
  • Parameter estimation problems
  • Solution dimensions verification
  • Initial guess with empty control
  • ADNLP manual backend

4. Test Suite Restoration

Restored full test suite in test/runtests.jl - all 167+ tests pass successfully.

Testing Results

✅ :param_estimation :all_schemes        6/6 tests passed
✅ :param_estimation :rotation_rate      3/3 tests passed  
✅ :param_estimation :with_constraint    2/2 tests passed
✅ :param_estimation :solution_dimensions 105/105 tests passed
✅ :zero_control_allocations            49/49 tests passed
✅ All CTDirect tests passed

Benefits

  • ✅ Enables parameter estimation problems (Kaplan-style)
  • ✅ Supports autonomous systems without control
  • ✅ Improves performance by avoiding unnecessary iterations
  • ✅ Prevents allocation bugs in edge cases
  • ✅ Comprehensive test coverage for zero control dimension
  • ✅ Maintains backward compatibility with existing code

Breaking Changes

None - this is a pure addition of functionality.

@ocots
Copy link
Member Author

ocots commented Mar 21, 2026

Implementation Notes

CTParser Limitation

Currently, CTParser does not support zero control dimension problems. Therefore, all test problems must be defined using CTModels.PreModel API instead of the @def macro syntax.

Why CTModels is required:

  • CTParser assumes at least one control variable in its parsing logic
  • The @def macro generates code that expects NLP_u > 0
  • No control! call is possible for zero control dimension problems

Test Implementation:
All test problems in test/problems/autonomous_system.jl use:

pre = CTModels.PreModel()
CTModels.time!(pre; t0=0.0, tf=1.0)
CTModels.state!(pre, 2)
CTModels.variable!(pre, n)  # For parameter estimation
# ... dynamics, objective, constraints ...
CTModels.definition!(pre, quote ... end)
CTModels.time_dependence!(pre; autonomous=true/false)
ocp = CTModels.build(pre)

Future Work:
Enhancing CTParser to support zero control dimension would allow users to write:

@def begin
    t  [0, 1], time
    x  R², state
    v  Rⁿ, variable  # parameters only, no control
    x(0) == v
    (t) == f(t, x, v)
    objective  min
end

For now, CTModels provides the necessary flexibility to define parameter estimation problems with zero control dimension.

@ocots
Copy link
Member Author

ocots commented Mar 21, 2026

@PierreMartinon please review and merge

ps: @jbcaillau waiting for a new beta release of CTParser

@github-actions
Copy link
Contributor

github-actions bot commented Mar 21, 2026

Breakage test results
Date: 2026-03-25 15:30:09

Name Latest Stable
OptimalControl compat: v1.0.5 compat: v1.0.5

@ocots ocots mentioned this pull request Mar 22, 2026
@PierreMartinon
Copy link
Member

Note: for now I have commented the inclusion of ode/variable.jl to remove the dependency on DifferentialEquations. This will be reintroduced when the difficulties with AD over variable step ODE are solved.

@PierreMartinon PierreMartinon merged commit 857ba69 into main Mar 25, 2026
@ocots ocots deleted the dim-zero-control branch March 25, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants