diff --git a/CHANGELOG.md b/CHANGELOG.md index d1df0ca6..538243d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/Display/Display.jl b/src/Display/Display.jl index f6cdec57..59a6592e 100644 --- a/src/Display/Display.jl +++ b/src/Display/Display.jl @@ -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 @@ -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 diff --git a/src/Display/ansi.jl b/src/Display/ansi.jl new file mode 100644 index 00000000..4dda4a2c --- /dev/null +++ b/src/Display/ansi.jl @@ -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 diff --git a/src/Display/definition.jl b/src/Display/definition.jl new file mode 100644 index 00000000..94639486 --- /dev/null +++ b/src/Display/definition.jl @@ -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) diff --git a/src/Display/print.jl b/src/Display/mathematical.jl similarity index 50% rename from src/Display/print.jl rename to src/Display/mathematical.jl index 8dab5b7e..e22c53e8 100644 --- a/src/Display/print.jl +++ b/src/Display/mathematical.jl @@ -1,82 +1,7 @@ # ------------------------------------------------------------------------------ # -# PRINT +# Mathematical definition 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 - -""" -$(TYPEDSIGNATURES) - -Print the abstract definition of an optimal control problem. - -# Arguments - -- `io::IO`: The output stream. -- `ocp::Union{Model,PreModel}`: The optimal control problem. - -# Returns - -- `Bool`: `true` if something was printed. -""" -function __print_abstract_definition(io::IO, ocp::Union{Model,PreModel}) - @assert hasproperty(definition(ocp), :head) - _print_ansi_styled(io, "Abstract definition:\n\n", :default, true) - tab = 4 - code = MacroTools.striplines(definition(ocp)) - 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) @@ -311,195 +236,3 @@ function __print_mathematical_definition( end return true end - -""" -$(TYPEDSIGNATURES) - -Print the optimal control problem. -""" -function Base.show(io::IO, ::MIME"text/plain", ocp::Model) - - # ------------------------------------------------------------------------------ # - # print the code - some_printing = __print_abstract_definition(io, ocp) - - # ------------------------------------------------------------------------------ # - # print in mathematical form - - # dimensions - x_dim = state_dimension(ocp) - u_dim = control_dimension(ocp) - v_dim = variable_dimension(ocp) - - # names - t_name = time_name(ocp) - t0_name = initial_time_name(ocp) - tf_name = final_time_name(ocp) - x_name = state_name(ocp) - u_name = control_name(ocp) - v_name = variable_name(ocp) - xi_names = state_components(ocp) - ui_names = control_components(ocp) - vi_names = variable_components(ocp) - - # dependencies - is_variable_dependent = is_variable(ocp) - is_time_dependent = !is_autonomous(ocp) - is_control_free_ocp = is_control_free(ocp) - - # cost - has_a_lagrange_cost = has_lagrange_cost(ocp) - has_a_mayer_cost = has_mayer_cost(ocp) - - # constraints dimensions: path, boundary, state, control, variable, boundary - dim_path_cons_nl = dim_path_constraints_nl(ocp) - dim_boundary_cons_nl = dim_boundary_constraints_nl(ocp) - dim_state_cons_box = dim_state_constraints_box(ocp) - dim_control_cons_box = dim_control_constraints_box(ocp) - dim_variable_cons_box = dim_variable_constraints_box(ocp) - - # - some_printing = __print_mathematical_definition( - io, - some_printing, - x_dim, - u_dim, - v_dim, - t_name, - t0_name, - tf_name, - x_name, - u_name, - v_name, - xi_names, - ui_names, - vi_names, - is_variable_dependent, - is_time_dependent, - is_control_free_ocp, - has_a_lagrange_cost, - has_a_mayer_cost, - dim_path_cons_nl, - dim_boundary_cons_nl, - dim_state_cons_box, - dim_control_cons_box, - dim_variable_cons_box, - ) - - # - return nothing -end - -""" -$(TYPEDSIGNATURES) - -Default show method for a [`Model`](@ref CTModels.OCP.Model). - -Prints only the type name. -""" -function Base.show_default(io::IO, ocp::Model) - return print(io, typeof(ocp)) -end - -# ------------------------------------------------------------------------------ # -# PreModel - -""" -$(TYPEDSIGNATURES) - -Print the optimal control problem. -""" -function Base.show(io::IO, ::MIME"text/plain", ocp::PreModel) - - # check if the problem is empty - __is_empty(ocp) && return nothing - - # - some_printing = false - - if __is_definition_set(ocp) - # ------------------------------------------------------------------------------ # - # print the code - some_printing = __print_abstract_definition(io, ocp) - end - - # ------------------------------------------------------------------------------ # - # print in mathematical form - - if __is_consistent(ocp) - - # dimensions - x_dim = dimension(ocp.state) - u_dim = dimension(ocp.control) - v_dim = dimension(ocp.variable) - - # names - t_name = time_name(ocp.times) - t0_name = initial_time_name(ocp.times) - tf_name = final_time_name(ocp.times) - x_name = name(ocp.state) - u_name = name(ocp.control) - v_name = name(ocp.variable) - xi_names = components(ocp.state) - ui_names = components(ocp.control) - vi_names = components(ocp.variable) - - # dependencies - is_variable_dependent = is_variable(ocp) - is_time_dependent = !is_autonomous(ocp) - is_control_free_ocp = is_control_free(ocp) - - # cost - has_a_lagrange_cost = has_lagrange_cost(ocp.objective) - has_a_mayer_cost = has_mayer_cost(ocp.objective) - - # constraints dimensions: path, boundary, state, control, variable, boundary - constraints = build(ocp.constraints) - dim_path_cons_nl = dim_path_constraints_nl(constraints) - dim_boundary_cons_nl = dim_boundary_constraints_nl(constraints) - dim_state_cons_box = dim_state_constraints_box(constraints) - dim_control_cons_box = dim_control_constraints_box(constraints) - dim_variable_cons_box = dim_variable_constraints_box(constraints) - - # - some_printing = __print_mathematical_definition( - io, - some_printing, - x_dim, - u_dim, - v_dim, - t_name, - t0_name, - tf_name, - x_name, - u_name, - v_name, - xi_names, - ui_names, - vi_names, - is_variable_dependent, - is_time_dependent, - is_control_free_ocp, - has_a_lagrange_cost, - has_a_mayer_cost, - dim_path_cons_nl, - dim_boundary_cons_nl, - dim_state_cons_box, - dim_control_cons_box, - dim_variable_cons_box, - ) - end - - return nothing -end - -""" -$(TYPEDSIGNATURES) - -Default show method for a `PreModel`. - -Prints only the type name. -""" -function Base.show_default(io::IO, ocp::PreModel) - return print(io, typeof(ocp)) -end diff --git a/src/Display/model.jl b/src/Display/model.jl new file mode 100644 index 00000000..c9099941 --- /dev/null +++ b/src/Display/model.jl @@ -0,0 +1,92 @@ +# ------------------------------------------------------------------------------ # +# Base.show for Model +# ------------------------------------------------------------------------------ # + +""" +$(TYPEDSIGNATURES) + +Print the optimal control problem. +""" +function Base.show(io::IO, ::MIME"text/plain", ocp::Model) + + # ------------------------------------------------------------------------------ # + # print the abstract (symbolic) definition, if any + some_printing = _print_abstract_definition(io, definition(ocp)) + + # ------------------------------------------------------------------------------ # + # print in mathematical form + + # dimensions + x_dim = state_dimension(ocp) + u_dim = control_dimension(ocp) + v_dim = variable_dimension(ocp) + + # names + t_name = time_name(ocp) + t0_name = initial_time_name(ocp) + tf_name = final_time_name(ocp) + x_name = state_name(ocp) + u_name = control_name(ocp) + v_name = variable_name(ocp) + xi_names = state_components(ocp) + ui_names = control_components(ocp) + vi_names = variable_components(ocp) + + # dependencies + is_variable_dependent = is_variable(ocp) + is_time_dependent = !is_autonomous(ocp) + is_control_free_ocp = is_control_free(ocp) + + # cost + has_a_lagrange_cost = has_lagrange_cost(ocp) + has_a_mayer_cost = has_mayer_cost(ocp) + + # constraints dimensions: path, boundary, state, control, variable, boundary + dim_path_cons_nl = dim_path_constraints_nl(ocp) + dim_boundary_cons_nl = dim_boundary_constraints_nl(ocp) + dim_state_cons_box = dim_state_constraints_box(ocp) + dim_control_cons_box = dim_control_constraints_box(ocp) + dim_variable_cons_box = dim_variable_constraints_box(ocp) + + # + some_printing = __print_mathematical_definition( + io, + some_printing, + x_dim, + u_dim, + v_dim, + t_name, + t0_name, + tf_name, + x_name, + u_name, + v_name, + xi_names, + ui_names, + vi_names, + is_variable_dependent, + is_time_dependent, + is_control_free_ocp, + has_a_lagrange_cost, + has_a_mayer_cost, + dim_path_cons_nl, + dim_boundary_cons_nl, + dim_state_cons_box, + dim_control_cons_box, + dim_variable_cons_box, + ) + + # + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Default show method for a [`Model`](@ref CTModels.OCP.Model). + +Prints only the type name. +""" +function Base.show_default(io::IO, ocp::Model) + return print(io, typeof(ocp)) +end diff --git a/src/Display/pre_model.jl b/src/Display/pre_model.jl new file mode 100644 index 00000000..abd916f3 --- /dev/null +++ b/src/Display/pre_model.jl @@ -0,0 +1,98 @@ +# ------------------------------------------------------------------------------ # +# Base.show for PreModel +# ------------------------------------------------------------------------------ # + +""" +$(TYPEDSIGNATURES) + +Print the optimal control problem. +""" +function Base.show(io::IO, ::MIME"text/plain", ocp::PreModel) + + # check if the problem is empty + __is_empty(ocp) && return nothing + + # ------------------------------------------------------------------------------ # + # print the abstract (symbolic) definition, if any + some_printing = _print_abstract_definition(io, ocp.definition) + + # ------------------------------------------------------------------------------ # + # print in mathematical form + + if __is_consistent(ocp) + + # dimensions + x_dim = dimension(ocp.state) + u_dim = dimension(ocp.control) + v_dim = dimension(ocp.variable) + + # names + t_name = time_name(ocp.times) + t0_name = initial_time_name(ocp.times) + tf_name = final_time_name(ocp.times) + x_name = name(ocp.state) + u_name = name(ocp.control) + v_name = name(ocp.variable) + xi_names = components(ocp.state) + ui_names = components(ocp.control) + vi_names = components(ocp.variable) + + # dependencies + is_variable_dependent = is_variable(ocp) + is_time_dependent = !is_autonomous(ocp) + is_control_free_ocp = is_control_free(ocp) + + # cost + has_a_lagrange_cost = has_lagrange_cost(ocp.objective) + has_a_mayer_cost = has_mayer_cost(ocp.objective) + + # constraints dimensions: path, boundary, state, control, variable, boundary + constraints = build(ocp.constraints) + dim_path_cons_nl = dim_path_constraints_nl(constraints) + dim_boundary_cons_nl = dim_boundary_constraints_nl(constraints) + dim_state_cons_box = dim_state_constraints_box(constraints) + dim_control_cons_box = dim_control_constraints_box(constraints) + dim_variable_cons_box = dim_variable_constraints_box(constraints) + + # + some_printing = __print_mathematical_definition( + io, + some_printing, + x_dim, + u_dim, + v_dim, + t_name, + t0_name, + tf_name, + x_name, + u_name, + v_name, + xi_names, + ui_names, + vi_names, + is_variable_dependent, + is_time_dependent, + is_control_free_ocp, + has_a_lagrange_cost, + has_a_mayer_cost, + dim_path_cons_nl, + dim_boundary_cons_nl, + dim_state_cons_box, + dim_control_cons_box, + dim_variable_cons_box, + ) + end + + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Default show method for a `PreModel`. + +Prints only the type name. +""" +function Base.show_default(io::IO, ocp::PreModel) + return print(io, typeof(ocp)) +end diff --git a/src/OCP/Building/definition.jl b/src/OCP/Building/definition.jl deleted file mode 100644 index 8961df62..00000000 --- a/src/OCP/Building/definition.jl +++ /dev/null @@ -1,60 +0,0 @@ -# ------------------------------------------------------------------------------ # -# SETTER -# ------------------------------------------------------------------------------ # - -""" -$(TYPEDSIGNATURES) - -Set the model definition of the optimal control problem. - -# Arguments - -- `ocp::PreModel`: The pre-model to modify. -- `definition::Expr`: The symbolic expression defining the problem. - -# Returns - -- `Nothing` -""" -function definition!(ocp::PreModel, definition::Expr)::Nothing - ocp.definition = definition - return nothing -end - -# ------------------------------------------------------------------------------ # -# GETTERS -# ------------------------------------------------------------------------------ # - -""" -$(TYPEDSIGNATURES) - -Return the model definition of the optimal control problem. - -# Arguments - -- `ocp::Model`: The built optimal control problem model. - -# Returns - -- `Expr`: The symbolic expression defining the problem. -""" -function definition(ocp::Model)::Expr - return ocp.definition -end - -""" -$(TYPEDSIGNATURES) - -Return the model definition of the optimal control problem or `nothing`. - -# Arguments - -- `ocp::PreModel`: The pre-model (may not have a definition set). - -# Returns - -- `Union{Expr, Nothing}`: The symbolic expression or `nothing` if not set. -""" -function definition(ocp::PreModel) - return ocp.definition -end diff --git a/src/OCP/Building/model.jl b/src/OCP/Building/model.jl index 55180426..8488f96b 100644 --- a/src/OCP/Building/model.jl +++ b/src/OCP/Building/model.jl @@ -473,12 +473,6 @@ function build(pre_ocp::PreModel; build_examodel=nothing)::Model suggestion="Call objective!(pre_ocp, ...) before building", context="build function - objective validation", ) - @ensure __is_definition_set(pre_ocp) Exceptions.PreconditionError( - "Definition must be set before building model", - reason="definition has not been set yet", - suggestion="Call definition!(pre_ocp) before building", - context="build function - definition validation", - ) @ensure __is_autonomous_set(pre_ocp) Exceptions.PreconditionError( "Time dependence must be set before building model", reason="autonomous status has not been defined yet", @@ -578,6 +572,7 @@ function is_autonomous( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, ) @@ -599,6 +594,7 @@ function is_autonomous( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, ) @@ -639,6 +635,7 @@ function state( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::T where {T<:AbstractStateModel} @@ -688,6 +685,7 @@ function control( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::T where {T<:AbstractControlModel} @@ -737,6 +735,7 @@ function variable( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::T where {T<:AbstractVariableModel} @@ -786,6 +785,7 @@ function times( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::T where {T<:TimesModel} @@ -850,6 +850,7 @@ function initial_time( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::T where {T<:Time} @@ -871,6 +872,7 @@ function initial_time( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, variable::AbstractVector{T}, @@ -893,6 +895,7 @@ function initial_time( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, variable::T, @@ -975,6 +978,7 @@ function final_time( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::T where {T<:Time} @@ -996,6 +1000,7 @@ function final_time( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, variable::AbstractVector{T}, @@ -1018,6 +1023,7 @@ function final_time( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, variable::T, @@ -1068,6 +1074,7 @@ function objective( <:Function, O, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::O where {O<:AbstractObjectiveModel} @@ -1115,6 +1122,7 @@ function mayer( <:Function, <:MayerObjectiveModel{M}, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::M where {M<:Function} @@ -1136,6 +1144,7 @@ function mayer( <:Function, <:BolzaObjectiveModel{M,<:Function}, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::M where {M<:Function} @@ -1183,6 +1192,7 @@ function lagrange( <:Function, LagrangeObjectiveModel{L}, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::L where {L<:Function} @@ -1204,6 +1214,7 @@ function lagrange( <:Function, <:BolzaObjectiveModel{<:Function,L}, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::L where {L<:Function} @@ -1235,6 +1246,7 @@ function dynamics( D, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::D where {D<:Function} @@ -1257,6 +1269,7 @@ function get_build_examodel( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, BE, }, )::BE where {BE<:Function} @@ -1278,6 +1291,7 @@ function get_build_examodel( <:Function, <:AbstractObjectiveModel, <:AbstractConstraintsModel, + <:AbstractDefinition, <:Nothing, }, ) @@ -1307,6 +1321,7 @@ function constraints( <:Function, <:AbstractObjectiveModel, C, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::C where {C<:AbstractConstraintsModel} @@ -1337,6 +1352,7 @@ function path_constraints_nl( <:Function, <:AbstractObjectiveModel, <:ConstraintsModel{TP,<:Tuple,<:Tuple,<:Tuple,<:Tuple}, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::TP where {TP<:Tuple} @@ -1358,6 +1374,7 @@ function boundary_constraints_nl( <:Function, <:AbstractObjectiveModel, <:ConstraintsModel{<:Tuple,TB,<:Tuple,<:Tuple,<:Tuple}, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::TB where {TB<:Tuple} @@ -1379,6 +1396,7 @@ function state_constraints_box( <:Function, <:AbstractObjectiveModel, <:ConstraintsModel{<:Tuple,<:Tuple,TS,<:Tuple,<:Tuple}, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::TS where {TS<:Tuple} @@ -1400,6 +1418,7 @@ function control_constraints_box( <:Function, <:AbstractObjectiveModel, <:ConstraintsModel{<:Tuple,<:Tuple,<:Tuple,TC,<:Tuple}, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::TC where {TC<:Tuple} @@ -1421,6 +1440,7 @@ function variable_constraints_box( <:Function, <:AbstractObjectiveModel, <:ConstraintsModel{<:Tuple,<:Tuple,<:Tuple,<:Tuple,TV}, + <:AbstractDefinition, <:Union{Function,Nothing}, }, )::TV where {TV<:Tuple} @@ -1471,3 +1491,59 @@ Return the dimension of box constraints on variable. function dim_variable_constraints_box(ocp::Model)::Dimension return dim_variable_constraints_box(constraints(ocp)) end + +# ------------------------------------------------------------------------------ # +# Definition getters +# ------------------------------------------------------------------------------ # + +""" +$(TYPEDSIGNATURES) + +Return the model definition of the optimal control problem. + +# Arguments + +- `ocp::Model`: The built optimal control problem model. + +# Returns + +- `AbstractDefinition`: The [`Definition`](@ref) wrapping the symbolic + expression, or an [`EmptyDefinition`](@ref) if the user did not attach one + before [`build`](@ref). +""" +function definition( + ocp::Model{ + <:TimeDependence, + <:TimesModel, + <:AbstractStateModel, + <:AbstractControlModel, + <:AbstractVariableModel, + <:Function, + <:AbstractObjectiveModel, + <:AbstractConstraintsModel, + D, + <:Union{Function,Nothing}, + }, +)::D where {D<:AbstractDefinition} + return ocp.definition +end + +""" +$(TYPEDSIGNATURES) + +Return the symbolic expression of the model definition of a built optimal control +problem. + +Delegates to [`expression`](@ref) on the underlying [`AbstractDefinition`](@ref): +returns `d.expr` if a [`Definition`](@ref) was attached before [`build`](@ref), +or `:(begin end)` if the definition is an [`EmptyDefinition`](@ref). + +# Arguments + +- `ocp::Model`: The built optimal control problem model. + +# Returns + +- `Expr`: The symbolic expression, or `:(begin end)` if no definition was attached. +""" +expression(ocp::Model)::Expr = expression(definition(ocp)) diff --git a/src/OCP/Components/definition.jl b/src/OCP/Components/definition.jl new file mode 100644 index 00000000..3db1d82d --- /dev/null +++ b/src/OCP/Components/definition.jl @@ -0,0 +1,82 @@ +# ------------------------------------------------------------------------------ # +# SETTER +# ------------------------------------------------------------------------------ # + +""" +$(TYPEDSIGNATURES) + +Set the model definition of the optimal control problem from a raw `Expr`. + +The expression is wrapped in a [`Definition`](@ref) and stored on the pre-model. + +# Arguments + +- `ocp::PreModel`: The pre-model to modify. +- `definition::Expr`: The symbolic expression defining the problem. + +# Returns + +- `Nothing` +""" +function definition!(ocp::PreModel, definition::Expr)::Nothing + ocp.definition = Definition(definition) + return nothing +end + +""" +$(TYPEDSIGNATURES) + +Set the model definition of the optimal control problem from an existing +[`AbstractDefinition`](@ref) value (either a [`Definition`](@ref) or an +[`EmptyDefinition`](@ref)). + +# Arguments + +- `ocp::PreModel`: The pre-model to modify. +- `definition::AbstractDefinition`: The definition value to store. + +# Returns + +- `Nothing` +""" +function definition!(ocp::PreModel, definition::AbstractDefinition)::Nothing + ocp.definition = definition + return nothing +end + +# ------------------------------------------------------------------------------ # +# EXPRESSION GETTERS +# ------------------------------------------------------------------------------ # + +""" +$(TYPEDSIGNATURES) + +Return an empty block expression for an [`EmptyDefinition`](@ref). + +Since no symbolic definition was attached, the canonical empty expression +`:(begin end)` (equivalent to `quote end`) is returned. + +# Arguments + +- `::EmptyDefinition`: The empty definition sentinel. + +# Returns + +- `Expr`: An empty block expression `:(begin end)`. +""" +expression(::EmptyDefinition)::Expr = :(begin end) + +""" +$(TYPEDSIGNATURES) + +Return the symbolic expression wrapped by a [`Definition`](@ref). + +# Arguments + +- `d::Definition`: The definition holding the symbolic expression. + +# Returns + +- `Expr`: The `Expr` value stored in `d.expr`. +""" +expression(d::Definition)::Expr = d.expr diff --git a/src/OCP/OCP.jl b/src/OCP/OCP.jl index 1b972045..25b43d00 100644 --- a/src/OCP/OCP.jl +++ b/src/OCP/OCP.jl @@ -66,9 +66,9 @@ include("Components/times.jl") include("Components/dynamics.jl") include("Components/objective.jl") include("Components/constraints.jl") +include("Components/definition.jl") # Load builders (depend on types and components) -include("Building/definition.jl") include("Building/dual_model.jl") include("Building/discretization_utils.jl") include("Building/interpolation_helpers.jl") @@ -83,6 +83,7 @@ export Model, PreModel, AbstractModel export Solution, AbstractSolution export FixedTimeModel, FreeTimeModel, TimesModel, AbstractTimeModel export StateModel, ControlModel, EmptyControlModel, VariableModel, EmptyVariableModel +export AbstractDefinition, Definition, EmptyDefinition export MayerObjectiveModel, LagrangeObjectiveModel, BolzaObjectiveModel export DualModel, AbstractDualModel export SolverInfos, AbstractSolverInfos @@ -124,7 +125,7 @@ export dim_state_constraints_box, dim_control_constraints_box, dim_variable_cons export dim_dual_state_constraints_box, dim_dual_control_constraints_box, dim_dual_variable_constraints_box export state, control, variable, costate, objective export dynamics, mayer, lagrange -export definition, dual +export definition, expression, dual export iterations, status, message, success, successful export constraints_violation, infos export get_build_examodel diff --git a/src/OCP/Types/components.jl b/src/OCP/Types/components.jl index 8c870bb7..d3858b88 100644 --- a/src/OCP/Types/components.jl +++ b/src/OCP/Types/components.jl @@ -526,3 +526,70 @@ struct ConstraintsModel{TP<:Tuple,TB<:Tuple,TS<:Tuple,TC<:Tuple,TV<:Tuple} <: control_box::TC variable_box::TV end + +# ------------------------------------------------------------------------------ # +# Definition (symbolic) +# ------------------------------------------------------------------------------ # +""" +$(TYPEDEF) + +Abstract base type for the symbolic (abstract) definition attached to an +optimal control problem. + +Subtypes represent either a concrete symbolic definition ([`Definition`](@ref)) +or the absence of such a definition ([`EmptyDefinition`](@ref)). The +`definition` field of [`Model`](@ref CTModels.OCP.Model) and `PreModel` stores +a value of a subtype of `AbstractDefinition`. + +See also: [`Definition`](@ref), [`EmptyDefinition`](@ref). +""" +abstract type AbstractDefinition end + +""" +$(TYPEDEF) + +Sentinel type representing the absence of a symbolic definition attached to +an optimal control problem. + +Used as the default value of the `definition` field in `PreModel` and in a +built [`Model`](@ref CTModels.OCP.Model) when the user did not call +[`definition!`](@ref). When encountered, display and serialization routines +treat the definition as empty and skip the "Abstract definition" section. + +# Example + +```julia-repl +julia> using CTModels + +julia> ed = CTModels.EmptyDefinition() +``` + +See also: [`AbstractDefinition`](@ref), [`Definition`](@ref). +""" +struct EmptyDefinition <: AbstractDefinition end + +""" +$(TYPEDEF) + +Wrapper around a Julia `Expr` holding the original symbolic definition of an +optimal control problem (typically produced by the `@def` DSL). + +# Fields + +- `expr::Expr`: The symbolic expression defining the problem. + +# Example + +```julia-repl +julia> using CTModels + +julia> d = CTModels.Definition(:(x = 1)) +julia> d.expr +:(x = 1) +``` + +See also: [`AbstractDefinition`](@ref), [`EmptyDefinition`](@ref). +""" +struct Definition <: AbstractDefinition + expr::Expr +end diff --git a/src/OCP/Types/model.jl b/src/OCP/Types/model.jl index 6889cd1c..9b2ddd4e 100644 --- a/src/OCP/Types/model.jl +++ b/src/OCP/Types/model.jl @@ -35,7 +35,9 @@ and the types of all its components. once in the stored tuples, bounds are the intersection of all declared bounds, and every declared label is preserved in the `aliases` field of the box tuples (see `ConstraintsModel`). -- `definition::Expr`: Original symbolic definition of the problem. +- `definition::DefinitionType`: Original symbolic definition of the problem, + stored as a subtype of [`AbstractDefinition`](@ref) ([`Definition`](@ref) when + set, [`EmptyDefinition`](@ref) otherwise). - `build_examodel::BuildExaModelType`: Optional ExaModels builder function. # Example @@ -56,6 +58,7 @@ struct Model{ DynamicsModelType<:Function, ObjectiveModelType<:AbstractObjectiveModel, ConstraintsModelType<:AbstractConstraintsModel, + DefinitionType<:AbstractDefinition, BuildExaModelType<:Union{Function,Nothing}, } <: AbstractModel times::TimesModelType @@ -65,7 +68,7 @@ struct Model{ dynamics::DynamicsModelType objective::ObjectiveModelType constraints::ConstraintsModelType - definition::Expr + definition::DefinitionType build_examodel::BuildExaModelType function Model{TD}( # TD must be specified explicitly @@ -76,7 +79,7 @@ struct Model{ dynamics::Function, objective::AbstractObjectiveModel, constraints::AbstractConstraintsModel, - definition::Expr, + definition::AbstractDefinition, build_examodel::Union{Function,Nothing}, ) where {TD<:TimeDependence} return new{ @@ -88,6 +91,7 @@ struct Model{ typeof(dynamics), typeof(objective), typeof(constraints), + typeof(definition), typeof(build_examodel), }( times, @@ -153,7 +157,15 @@ __is_objective_set(ocp::Model)::Bool = true """ $(TYPEDSIGNATURES) -Return `true` since definition is always set in a built [`Model`](@ref CTModels.OCP.Model). +Return `true` if `d` is an [`EmptyDefinition`](@ref). +""" +__is_definition_empty(d) = d isa EmptyDefinition + +""" +$(TYPEDSIGNATURES) + +Return `true` since definition is always structurally set in a built +[`Model`](@ref CTModels.OCP.Model). """ __is_definition_set(ocp::Model)::Bool = true @@ -175,7 +187,9 @@ and the model is validated before building. - `dynamics::Union{Function,Vector,Nothing}`: System dynamics (function or component-wise). - `objective::Union{AbstractObjectiveModel,Nothing}`: Cost functional. - `constraints::ConstraintsDictType`: Dictionary of constraints being built. -- `definition::Union{Expr,Nothing}`: Symbolic definition expression. +- `definition::AbstractDefinition`: Symbolic definition; defaults to + [`EmptyDefinition`](@ref) and becomes a [`Definition`](@ref) when + [`definition!`](@ref) is called with a real expression. - `autonomous::Union{Bool,Nothing}`: Whether the system is autonomous. # Example @@ -196,7 +210,7 @@ julia> # Set fields incrementally... nothing objective::Union{AbstractObjectiveModel,Nothing} = nothing constraints::ConstraintsDictType = ConstraintsDictType() - definition::Union{Expr,Nothing} = nothing + definition::AbstractDefinition = EmptyDefinition() autonomous::Union{Bool,Nothing} = nothing end @@ -273,9 +287,10 @@ __is_objective_set(ocp::PreModel)::Bool = __is_set(ocp.objective) """ $(TYPEDSIGNATURES) -Return `true` if definition has been set in the `PreModel`. +Return `true` if a non-empty definition has been set in the `PreModel` +(i.e. [`definition!`](@ref) was called with a [`Definition`](@ref)). """ -__is_definition_set(ocp::PreModel)::Bool = __is_set(ocp.definition) +__is_definition_set(ocp::PreModel)::Bool = !__is_definition_empty(ocp.definition) """ $(TYPEDSIGNATURES) @@ -365,7 +380,6 @@ function __is_complete(ocp::PreModel)::Bool __is_state_set(ocp) && __is_dynamics_complete(ocp) && __is_objective_set(ocp) && - __is_definition_set(ocp) && __is_autonomous_set(ocp) end diff --git a/test/suite/display/test_print.jl b/test/suite/display/test_print.jl index 2a87b39a..977be71f 100644 --- a/test/suite/display/test_print.jl +++ b/test/suite/display/test_print.jl @@ -10,11 +10,36 @@ function test_print() Test.@testset "Display Tests" verbose=VERBOSE showtiming=SHOWTIMING begin # ==================================================================== - # UNIT TESTS - Display Functions + # UNIT TESTS - AbstractDefinition display # ==================================================================== - Test.@testset "Abstract Types" begin - # Pure unit tests for display functionality + Test.@testset "AbstractDefinition display" begin + Test.@testset "show(EmptyDefinition) produces no output" begin + io = IOBuffer() + show(io, MIME"text/plain"(), CTModels.EmptyDefinition()) + Test.@test isempty(String(take!(io))) + end + + Test.@testset "show(Definition) prints header" begin + d = CTModels.Definition(:(x = 1)) + io = IOBuffer() + show(io, MIME"text/plain"(), d) + Test.@test occursin("Abstract definition:", String(take!(io))) + end + + Test.@testset "_print_abstract_definition returns false for EmptyDefinition" begin + io = IOBuffer() + result = CTModels.Display._print_abstract_definition(io, CTModels.EmptyDefinition()) + Test.@test result == false + Test.@test isempty(String(take!(io))) + end + + Test.@testset "_print_abstract_definition returns true for Definition" begin + io = IOBuffer() + result = CTModels.Display._print_abstract_definition(io, CTModels.Definition(:(x = 1))) + Test.@test result == true + Test.@test occursin("Abstract definition:", String(take!(io))) + end end # ==================================================================== @@ -92,6 +117,48 @@ function test_print() Test.@test occursin("Abstract definition:", s) Test.@test occursin("optimal control problem is of the form:", s) end + + Test.@testset "show(Model) without definition omits abstract header" begin + pre = CTModels.PreModel() + CTModels.time!(pre; t0=0.0, tf=1.0) + CTModels.state!(pre, 1, "x", ["x"]) + CTModels.control!(pre, 1, "u", ["u"]) + CTModels.variable!(pre, 0) + dyn!(r, t, x, u, v) = r .= 0 + CTModels.dynamics!(pre, dyn!) + CTModels.objective!(pre, :min; mayer=(x0, xf, v) -> 0.0, lagrange=(t, x, u, v) -> 0.0) + CTModels.time_dependence!(pre; autonomous=false) + + model = CTModels.build(pre) + + io = IOBuffer() + show(io, MIME"text/plain"(), model) + s = String(take!(io)) + + Test.@test !occursin("Abstract definition:", s) + Test.@test occursin("optimal control problem is of the form:", s) + end + end + + Test.@testset "PreModel Display - without definition" begin + Test.@testset "show(PreModel) without definition omits abstract header" begin + pre = CTModels.PreModel() + CTModels.time!(pre; t0=0.0, tf=1.0) + CTModels.state!(pre, 1, "x", ["x"]) + CTModels.control!(pre, 1, "u", ["u"]) + CTModels.variable!(pre, 0) + dyn!(r, t, x, u, v) = r .= 0 + CTModels.dynamics!(pre, dyn!) + CTModels.objective!(pre, :min; mayer=(x0, xf, v) -> 0.0, lagrange=(t, x, u, v) -> 0.0) + CTModels.time_dependence!(pre; autonomous=false) + + io = IOBuffer() + show(io, MIME"text/plain"(), pre) + s = String(take!(io)) + + Test.@test !occursin("Abstract definition:", s) + Test.@test occursin("optimal control problem is of the form:", s) + end end end end diff --git a/test/suite/ocp/test_definition.jl b/test/suite/ocp/test_definition.jl index f7e04163..b679b930 100644 --- a/test/suite/ocp/test_definition.jl +++ b/test/suite/ocp/test_definition.jl @@ -10,60 +10,139 @@ function test_definition() Test.@testset "Definition Tests" verbose=VERBOSE showtiming=SHOWTIMING begin # ==================================================================== - # UNIT TESTS - Abstract Types + # UNIT TESTS - Type Hierarchy # ==================================================================== - Test.@testset "Abstract Types" begin - # Pure unit tests for definition functionality + Test.@testset "AbstractDefinition hierarchy" begin + Test.@testset "EmptyDefinition construction" begin + d = CTModels.EmptyDefinition() + Test.@test d isa CTModels.EmptyDefinition + Test.@test d isa CTModels.AbstractDefinition + end + + Test.@testset "Definition construction and field access" begin + expr = :(x = 1) + d = CTModels.Definition(expr) + Test.@test d isa CTModels.Definition + Test.@test d isa CTModels.AbstractDefinition + Test.@test d.expr === expr + end end # ==================================================================== - # UNIT TESTS - Setters/Getters on PreModel and Model + # UNIT TESTS - PreModel default # ==================================================================== - Test.@testset "definition! and definition on PreModel" begin + Test.@testset "PreModel default definition is EmptyDefinition" begin pre = CTModels.PreModel() - expr = :(x = 1) + Test.@test pre.definition isa CTModels.EmptyDefinition + Test.@test !CTModels.OCP.__is_definition_set(pre) + end + # ==================================================================== + # UNIT TESTS - Setters/Getters + # ==================================================================== + + Test.@testset "definition! auto-wraps Expr into Definition" begin + pre = CTModels.PreModel() + expr = :(x = 1) CTModels.definition!(pre, expr) + Test.@test pre.definition isa CTModels.Definition + Test.@test pre.definition.expr === expr + Test.@test CTModels.OCP.__is_definition_set(pre) + end - Test.@test CTModels.definition(pre) === expr + Test.@testset "definition! accepts AbstractDefinition directly" begin + pre = CTModels.PreModel() + d = CTModels.Definition(:(y = 2)) + CTModels.definition!(pre, d) + Test.@test pre.definition === d + Test.@test CTModels.OCP.__is_definition_set(pre) + end + + Test.@testset "definition! with EmptyDefinition leaves predicate false" begin + pre = CTModels.PreModel() + CTModels.definition!(pre, CTModels.EmptyDefinition()) + Test.@test pre.definition isa CTModels.EmptyDefinition + Test.@test !CTModels.OCP.__is_definition_set(pre) end # ==================================================================== - # INTEGRATION TESTS - Definition Propagated Through Build + # UNIT TESTS - expression getter # ==================================================================== - Test.@testset "definition carried to Model after build" begin + Test.@testset "expression on EmptyDefinition returns empty block Expr" begin + e = CTModels.expression(CTModels.EmptyDefinition()) + Test.@test e isa Expr + Test.@test e.head == :block + end + + Test.@testset "expression on Definition returns wrapped expr" begin + expr = :(x = 1) + e = CTModels.expression(CTModels.Definition(expr)) + Test.@test e === expr + end + + Test.@testset "expression on EmptyDefinition via field returns empty block" begin pre = CTModels.PreModel() + e = CTModels.expression(pre.definition) + Test.@test e isa Expr + Test.@test e.head == :block + end - # Minimal consistent problem using the high-level API + Test.@testset "expression on Definition via field returns expr" begin + pre = CTModels.PreModel() + expr = :(x = 1) + CTModels.definition!(pre, expr) + Test.@test CTModels.expression(pre.definition) === expr + end + + # ==================================================================== + # INTEGRATION TESTS + # ==================================================================== + + Test.@testset "build without definition → Model holds EmptyDefinition" begin + pre = CTModels.PreModel() CTModels.time!(pre; t0=0.0, tf=1.0) CTModels.state!(pre, 1) CTModels.control!(pre, 1) CTModels.variable!(pre, 0) - dyn!(r, t, x, u, v) = r .= 0 CTModels.dynamics!(pre, dyn!) + CTModels.objective!(pre, :min; mayer=(x0, xf, v) -> 0.0, lagrange=(t, x, u, v) -> 0.0) + CTModels.time_dependence!(pre; autonomous=false) - mayer(x0, xf, v) = 0.0 - lagrange(t, x, u, v) = 0.0 - CTModels.objective!(pre, :min; mayer=mayer, lagrange=lagrange) + model = CTModels.build(pre) + Test.@test CTModels.definition(model) isa CTModels.EmptyDefinition + Test.@test CTModels.OCP.__is_definition_set(model) + Test.@test CTModels.expression(model) isa Expr + Test.@test CTModels.expression(model).head == :block + end + Test.@testset "build with definition → Model holds Definition with correct expr" begin + pre = CTModels.PreModel() + CTModels.time!(pre; t0=0.0, tf=1.0) + CTModels.state!(pre, 1) + CTModels.control!(pre, 1) + CTModels.variable!(pre, 0) + dyn!(r, t, x, u, v) = r .= 0 + CTModels.dynamics!(pre, dyn!) + CTModels.objective!(pre, :min; mayer=(x0, xf, v) -> 0.0, lagrange=(t, x, u, v) -> 0.0) expr = quote t ∈ [0, 1], time x ∈ R, state u ∈ R, control - ẋ(t) == u(t) + ẋ(t) == u(t) ∫(0.5u(t)^2) → min end - CTModels.definition!(pre, expr) CTModels.time_dependence!(pre; autonomous=false) model = CTModels.build(pre) - - Test.@test CTModels.definition(model) === expr + Test.@test CTModels.definition(model) isa CTModels.Definition + Test.@test CTModels.definition(model).expr === expr + Test.@test CTModels.OCP.__is_definition_set(model) + Test.@test CTModels.expression(model) === expr end end end diff --git a/test/suite/ocp/test_ocp.jl b/test/suite/ocp/test_ocp.jl index 0499b930..d6cf16e8 100644 --- a/test/suite/ocp/test_ocp.jl +++ b/test/suite/ocp/test_ocp.jl @@ -119,7 +119,7 @@ function test_ocp() end # Model definition - definition = quote + definition = CTModels.OCP.Definition(quote t ∈ [0, 1], time x ∈ R², state u ∈ R, control @@ -127,7 +127,7 @@ function test_ocp() x(1) == [0, 0] ẋ(t) == [x₂(t), u(t)] ∫(0.5u(t)^2) → min - end + end) build_examodel = nothing @@ -402,7 +402,7 @@ function test_ocp() objective = CTModels.MayerObjectiveModel(mayer_user, :min) pre_constraints = CTModels.ConstraintsDictType() constraints = CTModels.build(pre_constraints) - definition = quote end + definition = CTModels.OCP.EmptyDefinition() ocp = CTModels.Model{CTModels.NonAutonomous}( times, state, @@ -428,7 +428,7 @@ function test_ocp() objective = CTModels.MayerObjectiveModel(mayer_user, :min) pre_constraints = CTModels.ConstraintsDictType() constraints = CTModels.build(pre_constraints) - definition = quote end + definition = CTModels.OCP.EmptyDefinition() ocp = CTModels.Model{CTModels.NonAutonomous}( times, state, diff --git a/test/suite/ocp/test_ocp_model_types.jl b/test/suite/ocp/test_ocp_model_types.jl index e22afd87..5b050e67 100644 --- a/test/suite/ocp/test_ocp_model_types.jl +++ b/test/suite/ocp/test_ocp_model_types.jl @@ -37,7 +37,7 @@ function test_ocp_model_types() dynamics = (r, t, x, u, v) -> nothing objective = CTModels.MayerObjectiveModel((x0, xf, v) -> 0.0, :min) constraints = CTModels.ConstraintsModel((), (), (), (), ()) - definition = quote end + definition = CTModels.EmptyDefinition() build_examodel = nothing ocp = CTModels.Model{CTModels.Autonomous}( @@ -62,6 +62,7 @@ function test_ocp_model_types() typeof(dynamics), typeof(objective), typeof(constraints), + typeof(definition), typeof(build_examodel), } @@ -111,11 +112,11 @@ function test_ocp_model_types() Test.@test CTModels.OCP.__is_objective_set(ocp) Test.@test CTModels.OCP.__is_autonomous_set(ocp) - # At this stage the model is consistent but not yet complete + # definition is optional: model is complete without it Test.@test CTModels.OCP.__is_consistent(ocp) - Test.@test !CTModels.OCP.__is_complete(ocp) + Test.@test CTModels.OCP.__is_complete(ocp) - ocp.definition = quote end + ocp.definition = CTModels.Definition(quote end) Test.@test CTModels.OCP.__is_definition_set(ocp) Test.@test CTModels.OCP.__is_complete(ocp) @@ -150,7 +151,7 @@ function test_ocp_model_types() ocp.variable = variable ocp.dynamics = dynamics ocp.objective = objective - ocp.definition = quote end + ocp.definition = CTModels.Definition(quote end) ocp.autonomous = true Test.@test can_build(ocp) diff --git a/test/suite/ocp/test_ocp_solution_types.jl b/test/suite/ocp/test_ocp_solution_types.jl index 4f52e91d..8c58dbbc 100644 --- a/test/suite/ocp/test_ocp_solution_types.jl +++ b/test/suite/ocp/test_ocp_solution_types.jl @@ -84,7 +84,7 @@ function test_ocp_solution_types() dynamics = (r, t, x, u, v) -> nothing objective = CTModels.MayerObjectiveModel((x0, xf, v) -> 0.0, :min) constraints = CTModels.ConstraintsModel((), (), (), (), ()) - definition = quote end + definition = CTModels.OCP.EmptyDefinition() build_examodel = nothing model = CTModels.Model{CTModels.Autonomous}( @@ -183,7 +183,7 @@ function test_ocp_solution_types() dynamics = (r, t, x, u, v) -> nothing objective = CTModels.MayerObjectiveModel((x0, xf, v) -> 0.0, :min) constraints = CTModels.ConstraintsModel((), (), (), (), ()) - definition = quote end + definition = CTModels.OCP.EmptyDefinition() build_examodel = nothing model = CTModels.Model{CTModels.Autonomous}(