Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@

This document describes breaking changes in CTModels releases and how to migrate your code.

## [0.9.12-beta] - 2026-04-03

### No Breaking Changes

This release introduces enhancements without breaking existing functionality:

#### New Default Behavior (Non-Breaking)

- **Enhanced `time_grid()` function**: `time_grid(sol)` now works for `MultipleTimeGridModel` solutions without explicit component
- **Default component**: Automatically uses `:state` when no component specified
- **Full backward compatibility**: All existing code continues to work unchanged

#### What Changed

```julia
# These still work exactly as before
time_grid(sol_unified) # UnifiedTimeGridModel (unchanged)
time_grid(sol_multi, :state) # MultipleTimeGridModel with explicit component
time_grid(sol_multi, :control) # MultipleTimeGridModel with explicit component

# This now works (previously threw IncorrectArgument)
time_grid(sol_multi) # MultipleTimeGridModel without component (NEW)
```

#### Migration

- **No action required**: Existing code continues to work
- **Optional simplification**: Can use `time_grid(sol)` instead of `time_grid(sol, :state)` for state grid access
- **Explicit still preferred**: Use explicit component specification when accessing non-state grids

---

## [0.9.11] - 2026-03-31

### No Breaking Changes
Expand Down
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,58 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.12-beta] - 2026-04-03

### 🚀 Enhancements

#### Default Component for Multiple Time Grid Solutions

- **Default time grid access**: `time_grid(sol)` now works for `MultipleTimeGridModel` without explicit component specification
- **Sensible default**: When no component is specified, defaults to `:state` grid (most commonly used)
- **Consistent API**: Same `time_grid(sol)` syntax works for both unified and multiple time grid solutions
- **Backward compatibility**: All existing code with explicit component specification continues to work unchanged

#### Improved Developer Experience

- **Reduced verbosity**: No need to specify `:state` component for most common use case
- **Intuitive behavior**: State trajectory is the natural default for optimal control problems
- **Cleaner code**: Simplified access to time grids in multi-grid solutions

### 📊 API Changes

```julia
# Before (required component specification)
time_grid(sol_multi, :state) # Required for MultipleTimeGridModel
time_grid(sol_unified) # Worked for UnifiedTimeGridModel

# After (consistent behavior)
time_grid(sol_multi) # Now works! Defaults to :state
time_grid(sol_multi, :state) # Still works (explicit)
time_grid(sol_unified) # Still works (unchanged)
```

### 🔧 Internal Changes

- **Default function**: Added `__time_grid_default_component()::Symbol = :state` in `defaults.jl`
- **Method signature**: Updated `time_grid` method for `MultipleTimeGridModel` with default parameter
- **Removed exception**: Eliminated method that threw `IncorrectArgument` for missing component
- **Enhanced tests**: Updated test suites to verify new default behavior

### 🧪 Testing

- **Comprehensive coverage**: All existing tests pass with new behavior
- **Default behavior tests**: Added tests for automatic component selection
- **Compatibility verification**: Confirmed backward compatibility with explicit specifications
- **Integration testing**: End-to-end testing of multi-grid workflows

### 📝 Migration Notes

- **No breaking changes**: Existing code continues to work unchanged
- **Optional enhancement**: Can adopt new shorter syntax when convenient
- **Explicit still supported**: `time_grid(sol, :component)` syntax remains fully functional

---

## [0.9.11] - 2026-03-31

### 🔧 Internal Improvements
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "CTModels"
uuid = "34c4fa32-2049-4079-8329-de33c2a22e2d"
version = "0.9.11"
version = "0.9.12-beta"
authors = ["Olivier Cots <olivier.cots@toulouse-inp.fr>"]

[deps]
Expand Down
46 changes: 1 addition & 45 deletions src/OCP/Building/solution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ function time_grid(
<:AbstractDualModel,
<:AbstractSolverInfos,
},
component::Symbol,
component::Symbol = __time_grid_default_component(),
)::TimesDisc
# Clean and validate component symbol
component_clean = clean_component_symbols((component,))[1]
Expand All @@ -1011,50 +1011,6 @@ end
"""
$(TYPEDSIGNATURES)

Return the time grid for solutions with multiple time grids (component must be specified).

# Throws
- `IncorrectArgument`: Always thrown for MultipleTimeGridModel without component specification

# Notes
This method enforces explicit component specification for solutions with multiple time grids
to avoid ambiguity about which grid is being accessed.

# Examples
```julia-repl
julia> time_grid(sol) # ❌ Error for MultipleTimeGridModel
julia> time_grid(sol, :state) # ✅ Correct usage
```
"""
function time_grid(
sol::Solution{
<:MultipleTimeGridModel,
<:AbstractTimesModel,
<:AbstractStateModel,
<:AbstractControlModel,
<:AbstractVariableModel,
<:AbstractModel,
<:Function,
<:ctNumber,
<:AbstractDualModel,
<:AbstractSolverInfos,
},
)
# ⚠️ Applying Exception Rule: Missing component specification
throw(
CTBase.Exceptions.IncorrectArgument(
"Component must be specified for solutions with multiple time grids";
got="no component specified",
expected="time_grid(sol, :component) where component ∈ {:state, :control, :path}",
suggestion="Specify which time grid to access, e.g., time_grid(sol, :state)",
context="time_grid for MultipleTimeGridModel",
),
)
end

"""
$(TYPEDSIGNATURES)

Return the objective value.

"""
Expand Down
10 changes: 10 additions & 0 deletions src/OCP/Core/defaults.jl
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,13 @@ The default value is `:constant` for piecewise constant interpolation (direct me
The other possible value is `:linear` for piecewise linear interpolation (indirect methods).
"""
__control_interpolation()::Symbol = :constant

"""
$(TYPEDSIGNATURES)

Return the default component for time grid access in multiple time grid solutions.

The default value is `:state` since the state trajectory is typically the most
commonly accessed component in optimal control problems.
"""
__time_grid_default_component()::Symbol = :state
1 change: 1 addition & 0 deletions test/suite/ocp/test_defaults.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function test_defaults()
Test.@testset "time and criterion defaults" begin
Test.@test CTModels.OCP.__time_name() == "t"
Test.@test CTModels.OCP.__criterion_type() == :min
Test.@test CTModels.OCP.__time_grid_default_component() == :state
end

Test.@testset "variable naming defaults" begin
Expand Down
10 changes: 6 additions & 4 deletions test/suite/ocp/test_solution_multi_grids.jl
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,8 @@ function test_solution_multi_grids()
successful=true,
)

# Should require component specification
Test.@test_throws Exceptions.IncorrectArgument CTModels.time_grid(sol)
# Should work with default component (state)
Test.@test CTModels.time_grid(sol) == T_state

# Should work with component specification
Test.@test CTModels.time_grid(sol, :state) == T_state
Expand Down Expand Up @@ -605,8 +605,10 @@ function test_solution_multi_grids()
)
end

Test.@testset "Missing component specification" begin
Test.@test_throws Exceptions.IncorrectArgument CTModels.time_grid(sol)
Test.@testset "Default component specification" begin
# Should default to state grid
Test.@test CTModels.time_grid(sol) == T_state
Test.@test CTModels.time_grid(sol) == CTModels.time_grid(sol, :state)
end
end

Expand Down
5 changes: 3 additions & 2 deletions test/suite/serialization/test_multi_grids.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,9 @@ function test_multi_grids()
successful=CTModels.successful(sol_unified),
)

# time_grid without component should throw error
Test.@test_throws Exceptions.IncorrectArgument CTModels.time_grid(sol_multi)
# time_grid without component should return state grid (default behavior)
Test.@test CTModels.time_grid(sol_multi) == T_state
Test.@test CTModels.time_grid(sol_multi) == CTModels.time_grid(sol_multi, :state)

# Invalid component should throw error
Test.@test_throws Exceptions.IncorrectArgument CTModels.time_grid(
Expand Down
Loading