Private API

This page lists non-exported (internal) symbols of CTModels.Solutions.


From CTModels.Solutions

BoxDualDiff [Struct]

CTModels.Solutions.BoxDualDiffType
BoxDualDiff{DL,DU,I} <: Function

Callable struct computing the net dual of a box constraint: f(t) = lb(t)[idx] - ub(t)[idx].

I = Int gives a scalar; I = Vector{Int} gives a vector.

Replaces anonymous closures t -> duals_lb(t)[i] - duals_ub(t)[i] (and the vector variant) produced by dual(sol, model, label) for state and control box constraints.

DualSlice [Struct]

CTModels.Solutions.DualSliceType
DualSlice{D,I} <: Function

Callable struct extracting a slice of a time-dependent dual vector: f(t) = duals(t)[idx].

I = Int gives a scalar; I = Vector{Int} gives a vector. The scalar/vector distinction is encoded in the type parameter so the call method is fully specialised (no runtime branch).

Replaces anonymous closures t -> duals(t)[indices[1]] and t -> duals(t)[indices] produced by dual(sol, model, label).

_discretize_all_components [Function]

CTModels.Solutions._discretize_all_componentsFunction
_discretize_all_components(
    sol::CTModels.Solutions.Solution,
    T_state::Vector{Float64},
    T_control::Vector{Float64},
    T_costate::Vector{Float64},
    T_path::Vector{Float64},
    dim_x::Int64,
    dim_u::Int64
) -> Dict{String, Any}

Discretize all solution components on their respective time grids for serialization.

This internal helper function extracts the common discretization logic shared by both UnifiedTimeGridModel and MultipleTimeGridModel serialization. It evaluates all trajectory functions on their associated time grids and assembles them into a dictionary.

Grid-Component Association

Each component is discretized on its semantically correct time grid:

  • State trajectoryT_state grid
  • Control trajectoryT_control grid
  • Costate trajectoryT_costate grid
  • Path constraint dualsT_path grid
  • State box constraint duals (lb/ub) → T_state grid
  • Control box constraint duals (lb/ub) → T_control grid
  • Boundary/variable duals → Time-independent (vectors, not discretized)

Arguments

  • sol::Solution: Solution object containing trajectory functions
  • T_state::Vector{Float64}: Time grid for state discretization
  • T_control::Vector{Float64}: Time grid for control discretization
  • T_costate::Vector{Float64}: Time grid for costate discretization
  • T_path::Vector{Float64}: Time grid for path constraint dual discretization
  • dim_x::Int: State dimension (for validation)
  • dim_u::Int: Control dimension (for validation)

Returns

  • Dict{String, Any}: Dictionary with all discretized components (grids not included)

Notes

This function does NOT include time grid data in the returned dictionary. The calling function (_serialize_solution for UnifiedTimeGridModel or MultipleTimeGridModel) is responsible for adding the appropriate grid keys.

See also: _serialize_solution, _discretize_function, _discretize_dual

_discretize_dual [Function]

CTModels.Solutions._discretize_dualFunction
_discretize_dual(
    dual_func::Union{Nothing, Function},
    T
) -> Union{Nothing, Matrix{Float64}}
_discretize_dual(
    dual_func::Union{Nothing, Function},
    T,
    dim::Int64
) -> Union{Nothing, Matrix{Float64}}

Discretize a dual function, returning nothing if the input is nothing.

Arguments

  • dual_func::Union{Function,Nothing}: Dual function or nothing.
  • T: Time grid.
  • dim::Int: Dimension (auto-detected if -1).

Returns

  • Matrix{Float64} if dual_func is a function.
  • nothing if dual_func is nothing.

See also: CTModels.Solutions._discretize_function.

_discretize_function [Function]

CTModels.Solutions._discretize_functionFunction
_discretize_function(
    f::Function,
    T::AbstractVector
) -> Matrix{Float64}
_discretize_function(
    f::Function,
    T::AbstractVector,
    dim::Int64
) -> Matrix{Float64}

Discretize a function on a time grid.

Evaluates f at each point in T and collects the results into a matrix. If dim is -1, the output dimension is auto-detected from the first evaluation of f.

Arguments

  • f::Function: Function to discretize (can return a scalar or a vector).
  • T::AbstractVector: Time grid.
  • dim::Int: Expected dimension of the result. If -1, auto-detected from first evaluation.

Returns

  • Matrix{Float64}: n×dim matrix where n = length(T).

Examples

# Scalar function
f_scalar = t -> 2.0 * t
result = _discretize_function(f_scalar, [0.0, 0.5, 1.0], 1)
# result = [0.0; 1.0; 2.0]

# Vector function
f_vec = t -> [t, 2*t]
result = _discretize_function(f_vec, [0.0, 0.5, 1.0], 2)
# result = [0.0 0.0; 0.5 1.0; 1.0 2.0]

# Auto-detect dimension
result = _discretize_function(f_vec, [0.0, 0.5, 1.0])
# result = [0.0 0.0; 0.5 1.0; 1.0 2.0]

See also: CTModels.Solutions._discretize_dual.

_discretize_function(
    f::Function,
    T::CTModels.Solutions.UnifiedTimeGridModel
) -> Matrix{Float64}
_discretize_function(
    f::Function,
    T::CTModels.Solutions.UnifiedTimeGridModel,
    dim::Int64
) -> Matrix{Float64}

Discretize a function on a TimeGridModel by extracting the underlying time grid.

See also: CTModels.Solutions._discretize_function.

_dual_dimension [Function]

_extend_grid_to_match [Function]

CTModels.Solutions._extend_grid_to_matchFunction
_extend_grid_to_match(
    T_target::Vector{Float64},
    T_reference::Vector{Float64},
    component_name::String
) -> Vector{Float64}

Extend a target time grid to match a reference grid if the target is a strict prefix.

This function checks if T_target is missing only the last element of T_reference (i.e., T_target == T_reference[1:end-1]). If so, it returns T_reference to enable grid unification. Otherwise, it returns T_target unchanged.

Arguments

  • T_target::Vector{Float64}: Time grid to potentially extend
  • T_reference::Vector{Float64}: Reference time grid (typically the longest grid)
  • component_name::String: Name of the component for logging purposes

Returns

  • Vector{Float64}: Extended grid if extension condition met, otherwise original T_target

Notes

  • Extension condition: length(T_target) == length(T_reference) - 1 AND T_target == T_reference[1:end-1]
  • Emits @info log when extension is performed for transparency
  • Does not modify trajectory data matrices (interpolation handles this via T[1:N])

See also: _validate_and_fix_time_grid, build_solution

_interpolate_from_data [Function]

CTModels.Solutions._interpolate_from_dataFunction
_interpolate_from_data(data, T, dim, type_param; allow_nothing=false, 
                       constant_if_two_points=false, expected_dim=nothing,
                       interpolation=:linear)

Internal helper to create an interpolated function from discrete data.

Arguments

  • data: Matrix{Float64}, Function, or Nothing (if allow_nothing=true)
  • T: Time grid vector
  • dim: Dimension to extract from matrix (nothing = take full matrix)
  • type_param: Type parameter for dispatch (Matrix, Function, or Nothing)
  • allow_nothing: If false, throws IncorrectArgument when data is nothing
  • constant_if_two_points: If true and length(T)==2, return constant function
  • expected_dim: If provided, validates matrix dimension matches (via @ensure)
  • interpolation: Interpolation type (:linear or :constant)

Returns

  • Interpolated function (or nothing if data=nothing and allow_nothing=true)

Throws

  • IncorrectArgument: If data is nothing and allow_nothing=false
  • AssertionError: If expected_dim provided and doesn't match (via @ensure)

Notes

This is a low-level helper. Use build_interpolated_function for the complete workflow.

See also: CTModels.Solutions.build_interpolated_function, CTModels.Solutions._wrap_scalar_and_deepcopy.

_serialize_solution [Function]

CTModels.Solutions._serialize_solutionFunction
_serialize_solution(
    sol::CTModels.Solutions.Solution
) -> Dict{String, Any}

Serialize a solution into discrete data for export to persistent storage (JLD2, JSON, etc.).

This function converts a Solution object (which may contain interpolated functions) into a fully discrete, serializable representation. All trajectory functions are evaluated on their respective time grids and stored as matrices. The serialization format automatically adapts based on whether the solution uses unified or multiple time grids.

Serialization Formats

The function produces two different formats depending on the solution's time grid model:

Unified Time Grid Format (Legacy)

When all grids are identical (UnifiedTimeGridModel), produces:

Dict(
    "time_grid" => T,                    # Single grid for all components
    "state" => Matrix,                   # Discretized on T
    "control" => Matrix,                 # Discretized on T
    "costate" => Matrix,                 # Discretized on T
    "path_constraints_dual" => Matrix,   # Discretized on T
    # ... other fields
)

Multiple Time Grids Format

When grids differ (MultipleTimeGridModel), produces:

Dict(
    "time_grid_state" => T_state,        # State-specific grid
    "time_grid_control" => T_control,    # Control-specific grid
    "time_grid_costate" => T_costate,    # Costate-specific grid
    "time_grid_path" => T_path,          # Path constraints grid
    "state" => Matrix,                   # Discretized on T_state
    "control" => Matrix,                 # Discretized on T_control
    "costate" => Matrix,                 # Discretized on T_costate
    "path_constraints_dual" => Matrix,   # Discretized on T_path
    # ... other fields
)

Arguments

  • sol::Solution: Solution object to serialize (may contain functions or matrices)

Returns

  • Dict{String, Any}: Complete serializable dictionary containing:
    • Time grids: Either single "time_grid" or four separate grids
    • Trajectories: "state", "control", "costate" as Matrix{Float64}
    • Variable: "variable" as Vector{Float64} (time-independent)
    • Objective: "objective" as Float64
    • Dual variables: All constraint duals (can be nothing if not present)
      • "path_constraints_dual": Path constraint duals on path grid
      • "state_constraints_lb_dual", "state_constraints_ub_dual": State box duals on state grid
      • "control_constraints_lb_dual", "control_constraints_ub_dual": Control box duals on control grid
      • "boundary_constraints_dual": Boundary duals (time-independent vector)
      • "variable_constraints_lb_dual", "variable_constraints_ub_dual": Variable duals (vectors)
    • Solver info: "iterations", "message", "status", "successful", "constraints_violation", "infos"

Discretization Behavior

  • Function trajectories: Evaluated at each point of their associated time grid
  • Matrix trajectories: Copied as-is (already discrete)
  • Nothing duals: Preserved as nothing in the dictionary
  • Grid association: Each component is discretized on its correct grid:
    • State and state box duals → T_state
    • Control and control box duals → T_control
    • Costate → T_costate
    • Path constraint duals → T_path

Example

using CTModels

# Solve OCP with multiple grids
sol = solve(ocp, strategy=MyStrategy())

# Serialize to dictionary
data = _serialize_solution(sol)

# Check format
if haskey(data, "time_grid_state")
    # Multiple grids format
    println("State grid: ", length(data["time_grid_state"]), " points")
    println("Control grid: ", length(data["time_grid_control"]), " points")
    println("Costate grid: ", length(data["time_grid_costate"]), " points")
else
    # Unified grid format
    println("Unified grid: ", length(data["time_grid"]), " points")
end

# Export to file (handled by extensions)
export_ocp_solution(sol; filename="solution", format=:JLD)

# Reconstruct from data
sol_reconstructed = _reconstruct_solution_from_data(ocp, data)

Notes

Backward Compatibility

The serialization format is designed for backward compatibility:

  • Old files with single "time_grid" can be read (costate defaults to state grid)
  • New files with four grids are forward-compatible with updated readers
  • The _reconstruct_solution_from_data function handles both formats automatically

Memory Efficiency

When all grids are identical, the unified format avoids storing redundant grid data, reducing file size and memory usage.

Round-Trip Guarantee

The serialized data is fully compatible with build_solution for exact reconstruction:

data = _serialize_solution(sol)
sol_new = build_solution(ocp, data["time_grid_state"], ...; objective=data["objective"], ...)

See also: build_solution, _reconstruct_solution_from_data, export_ocp_solution, import_ocp_solution

_serialize_solution(
    _::CTModels.Solutions.UnifiedTimeGridModel,
    sol::CTModels.Solutions.Solution,
    dim_x::Int64,
    dim_u::Int64
) -> Dict{String, Any}

Serialize solution with unified time grid (legacy single-grid format).

This method handles solutions where all components share the same time grid. It produces the legacy format with a single "time_grid" key, which is backward-compatible with older versions of the package.

Format Produced

Dict(
    "time_grid" => T,                    # Single unified grid
    "state" => Matrix,                   # All components discretized on T
    "control" => Matrix,
    "costate" => Matrix,
    # ... all other fields
)

Arguments

  • ::UnifiedTimeGridModel: Time grid model type (dispatch parameter)
  • sol::Solution: Solution to serialize
  • dim_x::Int: State dimension
  • dim_u::Int: Control dimension

Returns

  • Dict{String, Any}: Serialized data with single time grid

Notes

This format is used when build_solution is called with identical grids for all components, or when using the legacy single-grid signature. It ensures backward compatibility with files created before the multi-grid feature was introduced.

See also: CTModels.Solutions._serialize_solution

_serialize_solution(
    _::CTModels.Solutions.MultipleTimeGridModel,
    sol::CTModels.Solutions.Solution,
    dim_x::Int64,
    dim_u::Int64
) -> Dict{String, Any}

Serialize solution with multiple independent time grids (modern format).

This method handles solutions where different components use different time grids. It produces the modern format with four separate grid keys (time_grid_state, time_grid_control, time_grid_costate, time_grid_path), preserving the independent discretizations.

Format Produced

Dict(
    "time_grid_state" => T_state,        # State-specific grid
    "time_grid_control" => T_control,    # Control-specific grid
    "time_grid_costate" => T_costate,    # Costate-specific grid
    "time_grid_path" => T_path,          # Path constraints grid
    "state" => Matrix,                   # Discretized on T_state
    "control" => Matrix,                 # Discretized on T_control
    "costate" => Matrix,                 # Discretized on T_costate
    "path_constraints_dual" => Matrix,   # Discretized on T_path
    # ... all other fields
)

Arguments

  • ::MultipleTimeGridModel: Time grid model type (dispatch parameter)
  • sol::Solution: Solution to serialize
  • dim_x::Int: State dimension
  • dim_u::Int: Control dimension

Returns

  • Dict{String, Any}: Serialized data with four independent time grids

Notes

This format is used when build_solution is called with different grids for different components. It allows numerical schemes to use optimal discretizations for each component (e.g., finer grid for state, coarser for control, custom for costate).

The reconstruction function _reconstruct_solution_from_data detects this format by checking for the presence of "time_grid_state" key and handles it appropriately.

See also: CTModels.Solutions._serialize_solution, CTModels.Solutions.build_solution

_validate_and_fix_time_grid [Function]

CTModels.Solutions._validate_and_fix_time_gridFunction
_validate_and_fix_time_grid(
    T::Vector{Float64},
    component_name::String
) -> Vector{Float64}

Validate and fix a time grid by ensuring it is strictly increasing.

Arguments

  • T::Vector{Float64}: Time grid to validate
  • component_name::String: Name of the component for error messages

Returns

  • Vector{Float64}: Validated and potentially reordered time grid

Notes

If the grid is not strictly increasing, it is reordered and a warning is emitted.

_wrap_scalar_and_deepcopy [Function]

CTModels.Solutions._wrap_scalar_and_deepcopyFunction
_wrap_scalar_and_deepcopy(func, dim)

Internal helper to wrap a function with scalar extraction and deepcopy, returning a CTModels.Components.CoercedTrajectory.

Arguments

  • func: Function or callable to wrap (or nothing)
  • dim: Dimension of output (1 = scalar extraction via only, otherwise identity)

Returns

  • Components.CoercedTrajectory(deepcopy(func), coerce) where coerce is only (dim==1) or identity (otherwise)
  • nothing if func is nothing

Notes

deepcopy is applied to func before storing it in the struct. This is essential for closures supplied by the user: Julia closures capture variable REFERENCES, not values, so without deepcopy, modifying external variables after solution creation would affect the solution.

Example:

param_x = 1.0
X_func = t -> [param_x * t]
sol = build_solution(...)
param_x = 999.0
# Without deepcopy: sol.state(0.5) would return [499.5] (uses new param_x)
# With deepcopy: sol.state(0.5) returns [0.5] (uses original param_x value)

See also: CTModels.Solutions._interpolate_from_data, CTModels.Solutions.build_interpolated_function.

build_interpolated_function [Function]

CTModels.Solutions.build_interpolated_functionFunction
build_interpolated_function(data, T, dim, type_param; allow_nothing=false,
                            constant_if_two_points=false, expected_dim=nothing,
                            interpolation=:linear)

Unified function to build an interpolated function with deepcopy and scalar wrapping.

This is the main entry point that combines interpolation and wrapping in one call.

Arguments

  • data: Matrix{Float64}, Function, or Nothing (if allow_nothing=true)
  • T: Time grid vector
  • dim: Dimension to extract (nothing = take full matrix)
  • type_param: Type parameter for dispatch
  • allow_nothing: Allow data=nothing (for optional duals)
  • constant_if_two_points: Return constant function if length(T)==2 (for costate)
  • expected_dim: Validate matrix has this dimension (for robustness)
  • interpolation: Interpolation type (:linear or :constant)

Returns

  • Wrapped interpolated function ready for use in Solution
  • nothing if data=nothing and allow_nothing=true

Throws

  • IncorrectArgument: If data is nothing and allow_nothing=false
  • AssertionError: If expected_dim doesn't match actual dimension

Examples

# State interpolation (required, with validation)
fx = build_interpolated_function(X, T, dim_x, TX; expected_dim=dim_x)

# Control with piecewise-constant interpolation
fu = build_interpolated_function(U, T, dim_u, TU; expected_dim=dim_u, interpolation=:constant)

# Costate with special 2-point handling
fp = build_interpolated_function(P, T, dim_x, TP; 
                                 constant_if_two_points=true, expected_dim=dim_x)

# Optional dual (can be nothing)
fscbd = build_interpolated_function(state_constraints_lb_dual, T, dim_x, 
                                    Union{Matrix{Float64},Nothing};
                                    allow_nothing=true)

See also: CTModels.Solutions._interpolate_from_data, CTModels.Solutions._wrap_scalar_and_deepcopy.

dual_model [Function]

CTModels.Solutions.dual_modelFunction
dual_model(
    sol::CTModels.Solutions.Solution{<:CTModels.Solutions.AbstractTimeGridModel, <:CTModels.Components.AbstractTimesModel, <:CTModels.Components.AbstractStateModel, <:CTModels.Components.AbstractControlModel, <:CTModels.Components.AbstractVariableModel, <:CTModels.Models.AbstractModel, <:Function, <:Real, DM<:CTModels.Solutions.AbstractDualModel}
) -> CTModels.Solutions.AbstractDualModel

Return the dual model containing all constraint multipliers.