diff --git a/Project.toml b/Project.toml index ffb2347..cfbcd52 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "DynOptInterface" uuid = "6c38235a-427b-4736-80fa-cf75909744ec" authors = ["Eduardo M. G. Vila <72969764+e-duar-do@users.noreply.github.com> and contributors"] -version = "0.1.0" +version = "0.2.0" [deps] MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" diff --git a/README.md b/README.md index f8c9f36..2409bfa 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ [![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuDO-dev.github.io/DynOptInterface.jl/dev/) [![Build Status](https://github.com/JuDO-dev/DynOptInterface.jl/actions/workflows/CI.yml/badge.svg?branch=dev)](https://github.com/JuDO-dev/DynOptInterface.jl/actions/workflows/CI.yml?query=branch%3Adev) - +[![Coverage](https://codecov.io/gh/JuDO-dev/DynOptInterface.jl/branch/dev/graph/badge.svg)](https://codecov.io/gh/JuDO-dev/DynOptInterface.jl) \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index e871051..f9d8fca 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -7,6 +7,7 @@ DocMeta.setdocmeta!(DynOptInterface, :DocTestSetup, :(import DynOptInterface as const _PAGES = [ "Home" => "index.md", + "Dynamic Optimization" => "dynamic_optimization.md", "API Reference" => [ "Dynamic Functions" => [ "reference/dynamic_functions/abstraction.md", diff --git a/docs/src/assets/dynamic_variable.png b/docs/src/assets/dynamic_variable.png new file mode 100644 index 0000000..dc297f2 Binary files /dev/null and b/docs/src/assets/dynamic_variable.png differ diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 000ad14..40a4ed5 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -7,6 +7,12 @@ CurrentModule = DynOptInterface 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). +## v0.2.0 (August 31, 2024) + +- Added phase attributes +- Changed `DynamicVariableDerivative` to `Derivative`, also changing `ExplicitDifferentialFunction` +- Added `MultiPhaseIntegral` + ## v0.1.0 (July 26, 2024) - Initial release \ No newline at end of file diff --git a/docs/src/dynamic_optimization.md b/docs/src/dynamic_optimization.md new file mode 100644 index 0000000..d18d5ec --- /dev/null +++ b/docs/src/dynamic_optimization.md @@ -0,0 +1,63 @@ +```@meta +CurrentModule = DynOptInterface +``` + +# Dynamic Optimization + +## Phases and Dynamic Variables + +Phases represent intervals $t \in [t_0, t_f]$ on which dynamic variables $t \mapsto \boldsymbol y(t)$ are defined. + +![dynamic variable](assets/dynamic_variable.png) + +## Problem Formulation + +MOI's [Function-in-Set form problem](@extref Standard-form-problem) is extended as follows. +Dynamic Optimization Problems deal with finding variables $x \in \mathbb R^{n_x}$, phase boundaries $t_0^{(i)} \in \mathbb R$, $t_f^{(i)} \in \mathbb R$, and dynamic variables $\boldsymbol y^{(i)} : [t_0^{(i)}, t_f^{(i)}] \rightarrow \mathbb R^{n_y^{(i)}}$ that + +```math +\begin{align*} +\begin{array}{rl} + \text{minimize} \quad & + m \big( \boldsymbol y(t_0), \boldsymbol y(t_f), t_0, t_f, x \big) + + \displaystyle{\sum_{i=1}^{n_p} \bigg[ + \int_{t_0^{(i)}}^{t_f^{(i)}} \ell^{(i)} \big( \boldsymbol y^{(i)}(t), t, x \big) \textrm{d}t \bigg],}\\ + % + \text{subject to} \quad & + \begin{aligned} + f(x) & \in \mathcal S,\\ + % + d^{(i)}\big(\dot{\boldsymbol y}^{(i)}(t), \boldsymbol y^{(i)}(t), t, x) & \in \mathcal D^{(i)}, + \quad \forall t \in [t_0^{(i)}, t_f^{(i)}], + \quad \forall i \in \{1, 2, ..., n_p\},\\ + b(\boldsymbol y(t_0), \boldsymbol y(t_f), t_0, t_f, x) &\in \mathcal B, + \end{aligned} +\end{array} +\end{align*} +``` + +where: +* ``f`` are [`MOI.AbstractScalarFunction`](@extref MathOptInterface.AbstractScalarFunction)s +* ``\ell`` and ``d`` are [`AbstractDynamicFunction`](@ref)s +* ``m`` and ``b`` are [`AbstractBoundaryFunction`](@ref)s + + +## Dynamic Functions + +* [`PhaseIndex`](@ref) +* [`DynamicVariableIndex`](@ref) +* [`LinearDynamicFunction`](@ref) +* [`PureQuadraticDynamicFunction`](@ref) +* [`NonlinearDynamicFunction`](@ref) +* [`Derivative`](@ref) +* [`ExplicitDifferentialFunction`](@ref) + +## Boundary Functions + +* [`Initial`](@ref) +* [`Final`](@ref) +* [`Linkage`](@ref) +* [`NonlinearBoundaryFunction`](@ref) +* [`Integral`](@ref) +* [`MultiPhaseIntegral`](@ref) +* [`Bolza`](@ref) \ No newline at end of file diff --git a/docs/src/reference/boundary_functions.md b/docs/src/reference/boundary_functions.md index 8640400..a8b4b03 100644 --- a/docs/src/reference/boundary_functions.md +++ b/docs/src/reference/boundary_functions.md @@ -32,5 +32,6 @@ NonlinearBoundaryFunction ## Integrals ```@docs Integral +MultiPhaseIntegral Bolza ``` \ No newline at end of file diff --git a/docs/src/reference/dynamic_functions/derivatives.md b/docs/src/reference/dynamic_functions/derivatives.md index 4ad6336..f221b54 100644 --- a/docs/src/reference/dynamic_functions/derivatives.md +++ b/docs/src/reference/dynamic_functions/derivatives.md @@ -5,6 +5,6 @@ CurrentModule = DynOptInterface # Derivatives ```@docs -DynamicVariableDerivative +Derivative ExplicitDifferentialFunction ``` \ No newline at end of file diff --git a/docs/src/reference/dynamic_functions/phases.md b/docs/src/reference/dynamic_functions/phases.md index ee558e6..2e029ac 100644 --- a/docs/src/reference/dynamic_functions/phases.md +++ b/docs/src/reference/dynamic_functions/phases.md @@ -14,15 +14,12 @@ MOI.is_valid(::MOI.ModelLike, ::PhaseIndex) InvalidPhaseIndex ``` -# Attributes - -The [`PhaseIndex`](@ref) object is compatible with the following attributes: -* [`MOI.VariableName`](@extref MathOptInterface.VariableName) with value types `String` -* [`MOI.VariablePrimal`](@extref MathOptInterface.VariablePrimal) with value types `Tuple{<:Real,<:Real}` -* [`MOI.VariablePrimalStart`](@extref MathOptInterface.VariablePrimalStart) with value types `Tuple{<:Real,<:Real}` +# Phase Attributes ```@docs -MOI.supports(::MOI.ModelLike, ::MOI.AbstractVariableAttribute, ::Type{PhaseIndex}) -MOI.set(::MOI.ModelLike, ::MOI.AbstractVariableAttribute, ::PhaseIndex, ::Any) -MOI.get(::MOI.ModelLike, ::MOI.AbstractVariableAttribute, ::PhaseIndex) +AbstractPhaseAttribute +MOI.supports(::MOI.ModelLike, ::AbstractPhaseAttribute, ::Type{PhaseIndex}) +MOI.set(::MOI.ModelLike, ::AbstractPhaseAttribute, ::PhaseIndex, ::Any) +MOI.get(::MOI.ModelLike, ::AbstractPhaseAttribute, ::PhaseIndex) +PhaseName ``` \ No newline at end of file diff --git a/src/boundary_functions.jl b/src/boundary_functions.jl index 8f28888..2a80fb8 100644 --- a/src/boundary_functions.jl +++ b/src/boundary_functions.jl @@ -16,16 +16,16 @@ abstract type AbstractBoundaryFunction <: MOI.AbstractScalarFunction end ## Initial & Final """ - Initial{DF}(dyn_fun::DF) where {DF<:AbstractDynamicFunction} + Initial{DF}(evaluand::DF) where {DF<:AbstractDynamicFunction} Represents the evaluation of an [`AbstractDynamicFunction`](@ref) at the initial point of its phase. It is a sub-type of [`AbstractBoundaryFunction`](@ref). The dynamic function is stored -in the `dyn_fun` field. +in the `evaluand` field. """ struct Initial{DF<:AbstractDynamicFunction} <: AbstractBoundaryFunction - dyn_fun::DF + evaluand::DF end function MOI.Utilities._to_string( @@ -35,22 +35,22 @@ function MOI.Utilities._to_string( ) return string( "Initial(", - MOI.Utilities._to_string(options, model, initial.dyn_fun), + MOI.Utilities._to_string(options, model, initial.evaluand), ")", ) end """ - Final{DF}(dyn_fun::DF) where {DF<:AbstractDynamicFunction} + Final{DF}(evaluand::DF) where {DF<:AbstractDynamicFunction} Represents the evaluation of an [`AbstractDynamicFunction`](@ref) at the final point of its phase. It is a sub-type of [`AbstractBoundaryFunction`](@ref). The dynamic function is stored -in the `dyn_fun` field. +in the `evaluand` field. """ struct Final{DF<:AbstractDynamicFunction} <: AbstractBoundaryFunction - dyn_fun::DF + evaluand::DF end function MOI.Utilities._to_string( @@ -60,7 +60,7 @@ function MOI.Utilities._to_string( ) return string( "Final(", - MOI.Utilities._to_string(options, model, final.dyn_fun), + MOI.Utilities._to_string(options, model, final.evaluand), ")", ) end @@ -69,18 +69,18 @@ end """ Linkage{DF}( - final::Final{DF}, - initial::Initial{DF}, + final_evaluand::DF, + initial_evaluand::DF, ) where {DF<:AbstractDynamicFunction} Represents the expression ``f_f(y(t^f), t^f) - f_0(y(t^0), t^0)``. It is a sub-type of [`AbstractBoundaryFunction`](@ref). The final function is stored in the -`final` field and the initial function is stored in the `initial` field. +`final_evaluand` field and the initial function is stored in the `initial_evaluand` field. """ struct Linkage{DF<:AbstractDynamicFunction} <: AbstractBoundaryFunction - final::Final{DF} - initial::Initial{DF} + final_evaluand::DF + initial_evaluand::DF end function MOI.Utilities._to_string( @@ -89,9 +89,11 @@ function MOI.Utilities._to_string( linkage::Linkage, ) return string( - MOI.Utilities._to_string(options, model, linkage.final), - " - ", + "Final(", + MOI.Utilities._to_string(options, model, linkage.final_evaluand), + ") - Initial(", MOI.Utilities._to_string(options, model, linkage.initial), + ")", ) end @@ -149,14 +151,14 @@ end ## Integrals """ - Integral{DF}(dyn_fun::AbstractDynamicFunction) where {DF<:AbstractDynamicFunction} + Integral{DF}(integrand::DF) where {DF<:AbstractDynamicFunction} Represents the integral ``\\int_{t_i^o}^{t_i^f} f_d(\\dot{y}(t_i), y(t_i), t_i, x) \\mathrm{d}t_i``. -It is a sub-type of [`AbstractBoundaryFunction`](@ref). The integrand is stored in the `dyn_fun` field. +It is a sub-type of [`AbstractBoundaryFunction`](@ref). The integrand is stored in the `integrand` field. """ struct Integral{DF<:AbstractDynamicFunction} <: AbstractBoundaryFunction - dyn_fun::DF + integrand::DF end function MOI.Utilities._to_string( @@ -166,34 +168,75 @@ function MOI.Utilities._to_string( ) return string( "∫(", - MOI.Utilities._to_string(options, model, integral.dyn_fun), + MOI.Utilities._to_string(options, model, integral.integrand), ")d(", MOI.Utilities._to_string(options, model, phase_index(integral)), ")", ) end -phase_index(integral::Integral) = phase_index(integral.dyn_fun) +phase_index(integral::Integral) = phase_index(integral.integrand) + +""" + MultiPhaseIntegral{DF}( + integrands::Vector{DF}, + ) where {DF<:AbstractDynamicFunction} + +Represents the sum of integrals +``\\sum_i \\big[ \\int_{t_i^o}^{t_i^f} f_d(\\dot{y}(t_i), y(t_i), t_i, x) \\mathrm{d}t_i \\big]``. + +It is a sub-type of [`AbstractBoundaryFunction`](@ref). The vector of integrands is +stored in the `integrands` field. +""" +struct MultiPhaseIntegral{DF<:AbstractDynamicFunction} <: AbstractBoundaryFunction + integrands::Vector{DF} +end + +function MOI.Utilities._to_string( + options::MOI.Utilities._PrintOptions, + model::MOI.ModelLike, + multi_phase_integral::MultiPhaseIntegral, +) + integrands = multi_phase_integral.integrands + + s = string( + "∫(", + MOI.Utilities._to_string(options, model, integrands[1]), + ")d(", + MOI.Utilities._to_string(options, model, phase_index(integrands[1])), + ")", + ) + + for i in 2:length(integrands) + s *= string( + " + ∫(", + MOI.Utilities._to_string(options, model, integrands[i]), + ")d(", + MOI.Utilities._to_string(options, model, phase_index(integrands[i])), + ")", + ) + end + return s +end """ - Bolza{BF,DF}( + Bolza{BF,IF}( bou_fun::BF, - integral::Integral{DF}, - ) where {BF<:AbstractBoundaryFunction,DF<:AbstractDynamicFunction} + integral::IF, + ) where {BF<:AbstractBoundaryFunction,IF<:Union{Integral,MultiPhaseIntegral}} ```math f_b(y_0, y_f, t_0, t_f, x) + \\int_{t_i^o}^{t_i^f} f_d(\\dot{y}(t_i), y(t_i), t_i, x) \\mathrm{d}t_i ``` -Represents the sum of an [`AbstractBoundaryFunction`](@ref) with the integral of -an [`AbstractDynamicFunction`](@ref). +Represents the sum of an [`AbstractBoundaryFunction`](@ref) with either an [`Integral`](@ref) or a +[`MultiPhaseIntegral`](@ref). It is a sub-type of [`AbstractBoundaryFunction`](@ref). The boundary function is stored in the `bou_fun` field and the integral is stored in the `integral` field. """ -struct Bolza{BF<:AbstractBoundaryFunction,DF<:AbstractDynamicFunction} <: - AbstractBoundaryFunction +struct Bolza{BF<:AbstractBoundaryFunction,IF<:Union{Integral,MultiPhaseIntegral}} <: AbstractBoundaryFunction bou_fun::BF - integral::Integral{DF} + integral::IF end function MOI.Utilities._to_string( diff --git a/src/dynamic_functions/abstraction.jl b/src/dynamic_functions/abstraction.jl index 2fa1171..97f8f9d 100644 --- a/src/dynamic_functions/abstraction.jl +++ b/src/dynamic_functions/abstraction.jl @@ -6,16 +6,16 @@ Abstract super-type for dynamic functions. That is, expressions that may contain: * ``t_i`` -- a [`PhaseIndex`](@ref) * ``y_j(\\cdot)`` -- a [`DynamicVariableIndex`](@ref) -* ``\\dot{y}_j(\\cdot)`` -- a [`DynamicVariableDerivative`](@ref) -Sub-types of [`AbstractDynamicFunction`](@ref) must not contain different -[`PhaseIndex`](@ref)s. + +Sub-types of [`AbstractDynamicFunction`](@ref) must be defined on a single +[`PhaseIndex`](@ref). """ abstract type AbstractDynamicFunction <: MOI.AbstractScalarFunction end """ phase_index(dyn_fun::AbstractDynamicFunction)::PhaseIndex -Returns the [`PhaseIndex`](@ref) ``t_i`` of the dynamic function `dyn_fun`. +Returns the [`PhaseIndex`](@ref) ``t_i`` of a dynamic function `dyn_fun`. """ function phase_index(::AbstractDynamicFunction)::PhaseIndex end diff --git a/src/dynamic_functions/derivatives.jl b/src/dynamic_functions/derivatives.jl index 6601f8e..73533bb 100644 --- a/src/dynamic_functions/derivatives.jl +++ b/src/dynamic_functions/derivatives.jl @@ -1,33 +1,33 @@ """ - DynamicVariableDerivative(dyn_var::DynamicVariableIndex) + Derivative{DF}(dyn_fun::DF) where DF<:AbstractDynamicFunction -A wrapper for [`DynamicVariableIndex`](@ref) for use in referencing its derivative in -a model. +A wrapper for an [`AbstractDynamicFunction`](@ref) for use in referencing its derivative +in a model. It is a sub-type of [`AbstractDynamicFunction`](@ref). It represents the derivative of -`dyn_var` with respect to its phase, that is, ``t_i \\mapsto \\dot{y}_j(t_i)``. +`dyn_fun` with respect to its phase. """ -struct DynamicVariableDerivative <: AbstractDynamicFunction - dyn_var::DynamicVariableIndex +struct Derivative{DF<:AbstractDynamicFunction} <: AbstractDynamicFunction + dyn_fun::DF end function MOI.Utilities._to_string( ::MOI.Utilities._PrintOptions, ::MOI.ModelLike, - derivative::DynamicVariableDerivative, + derivative::Derivative, ) - return string("ẏ[", derivative.dyn_var.value, "]") + return string("Derivative(", derivative.dyn_fun, ")") end -function phase_index(derivative::DynamicVariableDerivative) - return phase_index(derivative.dyn_var) +function phase_index(derivative::Derivative) + return phase_index(derivative.dyn_fun) end """ - ExplicitDifferentialFunction{DF}( - derivative::DynamicVariableDerivative, - dyn_fun::DF, - ) where {DF<:AbstractDynamicFunction} + ExplicitDifferentialFunction{D,F}( + derivative::Derivative{D}, + dyn_fun::F, + ) where {D<:AbstractDynamicFunction,F<:AbstractDynamicFunction} An object representing the function ``t_i \\mapsto \\dot{y}(t_i) - f_d(y(t_i), t_i, x)``. @@ -35,19 +35,19 @@ It is a sub-type of [`AbstractDynamicFunction`](@ref). The derivative (stored in field) and the dynamic function (stored in the `dyn_fun` field) must be defined in the same phase, otherwise a [`MixedPhases`](@ref) error is thrown. """ -struct ExplicitDifferentialFunction{DF<:AbstractDynamicFunction} <: +struct ExplicitDifferentialFunction{D<:AbstractDynamicFunction,F<:AbstractDynamicFunction} <: AbstractDynamicFunction - derivative::DynamicVariableDerivative - dyn_fun::DF + derivative::Derivative{D} + dyn_fun::F function ExplicitDifferentialFunction( - derivative::DynamicVariableDerivative, - dyn_fun::DF, - ) where {DF<:AbstractDynamicFunction} + derivative::Derivative{D}, + dyn_fun::F, + ) where {D<:AbstractDynamicFunction, F<:AbstractDynamicFunction} if phase_index(derivative) != phase_index(dyn_fun) throw(MixedPhases("")) end - return new{DF}(derivative, dyn_fun) + return new{D,F}(derivative, dyn_fun) end end diff --git a/src/dynamic_functions/dynamic_variables.jl b/src/dynamic_functions/dynamic_variables.jl index c811220..08b3228 100644 --- a/src/dynamic_functions/dynamic_variables.jl +++ b/src/dynamic_functions/dynamic_variables.jl @@ -125,7 +125,12 @@ function MOI.set( index::DynamicVariableIndex, ::Any, ) - return _set_variable_attribute_fallback(model, attr, index) + if MOI.supports(model, attr, typeof(index)) + throw(MOI.SetAttributeNotAllowed(attr)) + else + throw(MOI.UnsupportedAttribute(attr)) + end + return nothing end """ @@ -145,5 +150,9 @@ function MOI.get( attr::MOI.AbstractVariableAttribute, index::DynamicVariableIndex, ) - return _get_variable_attribute_fallback(model, attr, index) + throw(MOI.GetAttributeNotAllowed( + attr, + "$(typeof(model)) does not support getting the attribute $(attr) for $(typeof(index)).", + )) + return nothing end \ No newline at end of file diff --git a/src/dynamic_functions/expressions.jl b/src/dynamic_functions/expressions.jl index e240d53..07304f1 100644 --- a/src/dynamic_functions/expressions.jl +++ b/src/dynamic_functions/expressions.jl @@ -192,7 +192,7 @@ arguments that may be included are: * An [`MOI.ScalarNonlinearFunction`](@extref MathOptInterface.ScalarNonlinearFunction) ``f(x)`` * A [`PhaseIndex`](@ref) ``t_i`` * A [`DynamicVariableIndex`](@ref) ``y_j(\\cdot)`` -* A [`DynamicVariableDerivative`](@ref) ``\\dot{y}_j(\\cdot)`` +* A [`Derivative`](@ref) ``\\dot{y}_j(\\cdot)`` * A [`LinearDynamicFunction`](@ref) ``c^\\top y(\\cdot)`` * A [`PureQuadraticDynamicFunction`](@ref) ``y(\\cdot)^\\top C y(\\cdot)`` * Another [`NonlinearDynamicFunction`](@ref) diff --git a/src/dynamic_functions/phases.jl b/src/dynamic_functions/phases.jl index 6963271..17911f9 100644 --- a/src/dynamic_functions/phases.jl +++ b/src/dynamic_functions/phases.jl @@ -84,19 +84,26 @@ end ## Attributes +""" + AbstractPhaseAttribute + +Abstract super-type for phase attributes. +""" +abstract type AbstractPhaseAttribute end + """ MOI.supports( model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, + attr::AbstractPhaseAttribute, ::Type{PhaseIndex}, - )::Bool + ) -Return a `Bool` indicating whether `model` supports the attribute `attr` for -[`PhaseIndex`](@ref)s. +Return a `Bool` indicating whether `model` supports the phase attribute +`attr` for [`PhaseIndex`](@ref)s. """ function MOI.supports( ::MOI.ModelLike, - ::MOI.AbstractVariableAttribute, + ::AbstractPhaseAttribute, ::Type{PhaseIndex}, ) return false @@ -105,36 +112,27 @@ end """ MOI.set( model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, + attr::AbstractPhaseAttribute, index::PhaseIndex, value, ) Assign `value` to the attribute `attr` of phase `index` in model `model`. - -An [`MOI.UnsupportedAttribute`](@extref MathOptInterface.UnsupportedAttribute) -error is thrown if `model` does not support the attribute `attr`, and a -[`MOI.SetAttributeNotAllowed`](@extref MathOptInterface.SetAttributeNotAllowed) -error is thrown if it supports the attribute `attr` but it cannot be set. """ function MOI.set( model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, + attr::AbstractPhaseAttribute, index::PhaseIndex, ::Any, -) - return _set_variable_attribute_fallback(model, attr, index) -end - -function _set_variable_attribute_fallback( - model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, - index, ) if MOI.supports(model, attr, typeof(index)) - throw(MOI.SetAttributeNotAllowed(attr)) + throw(ArgumentError( + "$(typeof(model)) does not currently allow setting the attribute $(attr) to $(index)." + )) else - throw(MOI.UnsupportedAttribute(attr)) + throw(ArgumentError( + "$(typeof(model)) does not support setting attribute $(attr) to a PhaseIndex." + )) end return nothing end @@ -142,31 +140,26 @@ end """ MOI.get( model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, + attr::AbstractPhaseAttribute, index::PhaseIndex, ) Return the value of the attribute `attr` set to phase `index` in model `model`. - -If the attribute `attr` is not supported by `model` then an error should be thrown. -If the attribute is supported but has not been set, `nothing` is returned. """ function MOI.get( model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, + attr::AbstractPhaseAttribute, index::PhaseIndex, ) - return _get_variable_attribute_fallback(model, attr, index) + throw(ArgumentError( + "$(typeof(model)) does not support getting the attribute $(attr) for $(typeof(index))." + )) + return nothing end -function _get_variable_attribute_fallback( - model::MOI.ModelLike, - attr::MOI.AbstractVariableAttribute, - index, -) - throw(MOI.GetAttributeNotAllowed( - attr, - "$(typeof(model)) does not support getting the attribute $(attr) for $(typeof(index)).", - )) - return nothing -end \ No newline at end of file +""" + PhaseName() + +A phase attribute for a `String` identifying a phase. +""" +struct PhaseName <: AbstractPhaseAttribute end \ No newline at end of file diff --git a/test/phases.jl b/test/phases.jl index 869a548..987b947 100644 --- a/test/phases.jl +++ b/test/phases.jl @@ -10,12 +10,12 @@ @test DOI.supports_phase(model) == false @test_throws DOI.AddPhaseNotAllowed DOI.add_phase(model) @test MOI.is_valid(model, t_1) == false - @test_throws MOI.UnsupportedAttribute MOI.set(model, MOI.VariableName(), t_1, "Time") + @test_throws MethodError MOI.set(model, MOI.VariableName(), t_1, "Time") struct PhaseModel <: MOI.ModelLike end MOI.supports(::PhaseModel, ::MOI.VariableName, ::Type{DOI.PhaseIndex}) = true model = PhaseModel() - @test_throws MOI.SetAttributeNotAllowed MOI.set(model, MOI.VariableName(), t_1, "Time") + @test_throws MethodError MOI.set(model, MOI.VariableName(), t_1, "Time") end \ No newline at end of file