Repository navigation
Solution: keep the switching structure (switching times, left/right limits, phase labels); reject duplicate grid times #460
Copy link
Copy link
Closed
Labels
featureNew feature or requestNew feature or request
Description
Summary
An indirect method fixes the structure of the solution: switching times, jumps of the state
or costate, and changes of control law. That is what sets it apart from a direct method.
Today a Solution built from a multi-phase flow (CTFlows.Flow(ocp, law)) loses all of
it:
x,panduare functions oft, so they cannot return two values attᵢ;- the grid passed in contains duplicates, which
issorted(T; lt=<)lets through without
any warning; - the plot draws a slanted segment instead of a vertical jump.
Part of the switching-structure work tracked in control-toolbox/CTFlows.jl#443 (detailed
plan there).
Proposal
abstract type AbstractSwitchingModel end
struct EmptySwitchingModel <: AbstractSwitchingModel end
struct SwitchingModel <: AbstractSwitchingModel # built through a keyword constructor
times::Vector{Float64} # strictly increasing, in (t0, tf); may be empty
labels::Vector{String} # one per phase (length(times) + 1); "" = no label
state::NTuple{2,Matrix{Float64}} # (left, right), one row per tᵢ
costate::NTuple{2,Matrix{Float64}}
control::NTuple{2,Matrix{Float64}}
endSolution. New last fieldswitching, which is also the last type parameter.build_solution. Gains the keyword
switching::AbstractSwitchingModel=EmptySwitchingModel(), in both the matrix and the
function variants. It is validated:tᵢin(t0, tf)and strictly increasing, the
number of labels, and the matrix dimensions.- Left values are stored too. The grid
Tneed not contain thetᵢ, and an imported
solution can rebuild both limits. - Keyword constructor. It lets fields be added later without breaking anything:
constraint duals attᵢ, seeFlow(ocp, law)Solution: reconstruct the constraint duals from the multipliers CTFlows.jl#472. - Getters.
switching_times(sol), empty if there are none;phase_labels(sol);has_switching(sol);- the left and right limits per component.
- Strict grid (breaking → 0.20.0). A duplicate time in a grid raises
IncorrectArgument. A non-sorted grid keeps the current behaviour (sort +@warn).
Check the CTDirect and OptimalControl suites before the release. - Display.
- New row:
├─ Switching times : [0.31, 0.72]. - New row:
├─ Phases : bang → singular → bang, shown only if at least one label is
non-empty.
- New row:
- Plot (
PlotCase, so Plots and Makie are both covered).- Jumps: after sampling on
T, splice in the rows(tᵢ, left)then(tᵢ, right).
This applies to the state, costate, control, control norm, and to the path
constraints evaluated at the limits. - Vertical lines at
tᵢ: new keywordswitching_style.- Its default has the same look as the existing time lines,
_TIME_STYLE, merged with
the user'stime_style. - A
NamedTupleis merged on top;:nonehides the lines. - The lines appear in every cell, including when
time=:normalize.
- Its default has the same look as the existing time lines,
- Phase labels:
phase_labels ∈ (:control, :all, :none)(default:control), plus
phase_labels_style.- Each label is an
Annotation(Plotting: text annotations in the IR (Annotationdecoration) CTBase.jl#612) centred on its
phase. - When the control is not drawn,
:controlfalls back to the last panel.
- Each label is an
- Jumps: after sampling on
- Serialization (JSON and JLD).
- New keys:
switching_times,phase_labels,switching_{state,costate,control}_{left,right}. - A file without these keys loads with
EmptySwitchingModel, so old files still load. - Reconstruction interpolates phase by phase, starting each interval
(tᵢ, tᵢ₊₁]from
the right limit, so an imported solution keeps its jumps.
- New keys:
Tests
- Construction. Nominal cases with 0, 1 and n switching times, with and without
labels. Each validation error with its exception type. Duplicate in a grid →
IncorrectArgument. Unsorted grid → warning. DefaultEmptySwitchingModel.
Getters, including the empty case.@inferredon the getters. - Show. Reference strings with and without switching, with and without labels, and with
some labels empty. - Plot (Plots and Makie).
- Panel data have the vertical rows; a
tᵢalready inTis not duplicated. VLines attᵢ, including withtime=:normalize.switching_style: the default,:none, and a mergedNamedTuple.phase_labelsset to:control,:alland:none, plus the fallback.- Path constraints at the limits.
- Panel data have the vertical rows; a
- Serialization. JSON and JLD round trips with jumps and labels. Import of an old
file. The reconstructed value just aftertᵢis close to the right limit.
Depends on / consumers
- Depends on Plotting: text annotations in the IR (
Annotationdecoration) CTBase.jl#612 (Annotation). - Filled by Plot jumps at switching times as vertical segments (left and right limits) — raw trajectories, Solutions, multi-phase CTFlows.jl#443 (
Flow(ocp, law)). - Extended later by
Flow(ocp, law)Solution: reconstruct the constraint duals from the multipliers CTFlows.jl#472 (constraint duals).
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
featureNew feature or requestNew feature or request
Activity
ocots commentedon Oct 18, 2026
Implemented in #463 (draft). Points to validate:
phase_labels = :control. When the control is not drawn, the labels go to the first drawn group (usually the state, at the top of the figure). The plan in CTFlows#443 said the last panel. I chose the top because it reads better. OK?has_switching(sol)isfalsefor a labelled single phase. Such a solution has no switching time, whilephase_labels(sol) == ["bang"]. OK, or should a single phase with a label count as "having a switching structure"?DOCPtime) and derives all its grids from that one, so it never passes a repeated time. The OptimalControl suite has not been run against this branch.format_versionis now 2. Files without the switching keys still load, with anEmptySwitchingModel.iterations/constraints_violation(NotProvided) does not survive export + import:Float64(NotProvided)fails in JLD2 andFloat64(::String)fails in JSON. Every solution ofCTFlows.Flow(ocp, law)is affected. Should it go in a separate issue?gray30at font size 9.Local status: the full suite passes (no regression), and the docs build cleanly (two passes).