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
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Display improvement**: Dual variables only displayed if model has declared constraints
- **New exports**: `dim_dual_state_constraints_box`, `dim_dual_control_constraints_box`, `dim_dual_variable_constraints_box`

#### Optional Definition with EmptyDefinition Sentinel

- **Type hierarchy**: Introduced `AbstractDefinition` with concrete types `Definition(expr::Expr)` and `EmptyDefinition` (sentinel)
- **Optional definition**: `PreModel.definition` defaults to `EmptyDefinition()` instead of `nothing`
- **Model parametric**: `Model` is now parametric on `DefinitionType<<:AbstractDefinition`
- **Expression getter**: Added `expression()` function to extract `Expr` from `AbstractDefinition`
- **Build relaxation**: Removed precondition requiring definition in `build()`
- **Display refactor**: Split `Display/print.jl` into 5 focused files by responsibility
- **Code organization**: Moved definition setters to `Components/definition.jl`, Model getters to `Building/model.jl`

#### API Enhancements

```julia
# Definition is now optional
pre = PreModel()
pre.definition isa EmptyDefinition # true by default

# Set definition via setter (auto-wraps Expr)
definition!(pre, quote
t ∈ [0, 1], time
x ∈ R, state
u ∈ R, control
ẋ(t) == u(t)
∫(0.5u(t)^2) → min
end)

# Extract expression
expr = expression(pre.definition) # Returns the Expr

# Build without definition is now valid
model = build(pre) # Works even without definition
```

#### Consistent Variable and Control Checking Functions

- **New functions**: Added `is_variable()` and `is_control_free()` for checking problem properties
Expand Down
9 changes: 7 additions & 2 deletions src/Display/Display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ using MacroTools: MacroTools
# Import types from parent module (will be available after CTModels loads this)
# These are forward declarations - actual types defined in OCP module
import ..OCP: Model, PreModel, Solution, AbstractSolution
import ..OCP: AbstractDefinition, Definition, EmptyDefinition

# Import internal helpers from OCP for display
import ..OCP: __is_empty, __is_definition_set, definition, __is_consistent
Expand All @@ -49,8 +50,12 @@ import ..OCP:
dim_state_constraints_box, dim_control_constraints_box, dim_variable_constraints_box
import ..OCP: build

# Include display functions
include("print.jl")
# Include display functions (split by responsibility)
include("ansi.jl")
include("definition.jl")
include("mathematical.jl")
include("model.jl")
include("pre_model.jl")

# -----------------------------
# RecipesBase.plot stub - to be extended by CTModelsPlots extension
Expand Down
53 changes: 53 additions & 0 deletions src/Display/ansi.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ------------------------------------------------------------------------------ #
# ANSI helpers and generic Expr printing
# ------------------------------------------------------------------------------ #

"""
Generate ANSI escape sequence for the specified color and formatting.
"""
function _ansi_color(color::Symbol, bold::Bool=false)
color_codes = Dict(
:black => 30,
:red => 31,
:green => 32,
:yellow => 33,
:blue => 34,
:magenta => 35,
:cyan => 36,
:white => 37,
:default => 39,
)

code = get(color_codes, color, 39)
return bold ? "\033[1;$(code)m" : "\033[$(code)m"
end

"""Generate ANSI reset sequence to clear formatting."""
_ansi_reset() = "\033[0m"

"""
Print text with ANSI color formatting for Documenter compatibility.
"""
function _print_ansi_styled(
io, text::Union{String,Symbol,Type}, color::Symbol, bold::Bool=false
)
print(io, _ansi_color(color, bold), string(text), _ansi_reset())
end

"""
$(TYPEDSIGNATURES)

Print an expression with indentation.

# Arguments

- `e::Expr`: The expression to print.
- `io::IO`: The output stream.
- `l::Int`: The indentation level (number of spaces).
"""
function __print(e::Expr, io::IO, l::Int)
MLStyle.@match e begin
:(($a, $b)) => println(io, " "^l, a, ", ", b)
_ => println(io, " "^l, e)
end
end
48 changes: 48 additions & 0 deletions src/Display/definition.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ------------------------------------------------------------------------------ #
# Abstract (symbolic) definition printing
# ------------------------------------------------------------------------------ #

"""
$(TYPEDSIGNATURES)

Print an [`EmptyDefinition`](@ref): no output is produced.

Returns `false` to indicate that nothing was printed.
"""
_print_abstract_definition(::IO, ::EmptyDefinition)::Bool = false

"""
$(TYPEDSIGNATURES)

Print a [`Definition`](@ref) under an "Abstract definition:" header.

Block expressions are unfolded line-by-line; other expression heads are
printed as a single indented entry.

Returns `true` to indicate that output was produced.

# Arguments

- `io::IO`: The output stream.
- `d::Definition`: The symbolic definition to display.
"""
function _print_abstract_definition(io::IO, d::Definition)::Bool
_print_ansi_styled(io, "Abstract definition:\n\n", :default, true)
tab = 4
code = MacroTools.striplines(d.expr)
MLStyle.@match code.head begin
:block => [__print(code.args[i], io, tab) for i in eachindex(code.args)]
_ => __print(code, io, tab)
end
return true
end

"""
$(TYPEDSIGNATURES)

Display method for any [`AbstractDefinition`](@ref).

Delegates to [`_print_abstract_definition`](@ref).
"""
Base.show(io::IO, ::MIME"text/plain", d::AbstractDefinition) =
_print_abstract_definition(io, d)
Loading
Loading