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
27 changes: 27 additions & 0 deletions lib/ModelingToolkitBase/src/systems/problem_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,32 @@ function build_operating_point(sys::AbstractSystem, op; fast_path = false)
return op
end

struct MissingNecessaryInitialConditionsError <: Exception
missing_ics::NecessaryInitialConditionsT
end

const MISSING_NECESSARY_ICS_ERR_PRELUDE = """
Missing necessary initial conditions for some variables. The missing variables are listed \
below, along with the reason why the initial condition is required:
"""

function Base.showerror(io::IO, err::MissingNecessaryInitialConditionsError)
println(io, MISSING_NECESSARY_ICS_ERR_PRELUDE)
for (k, v) in err.missing_ics
printstyled(io, k; bold = true)
println(io, ": ", v)
end
return
end

function check_necessary_initial_conditions(sys::AbstractSystem, op::SymmapT)
ics = get_necessary_initial_conditions(sys)
isempty(ics) && return
missing_ics = filter(!Base.Fix1(has_possibly_indexed_key, op) ∘ first, ics)
isempty(missing_ics) || throw(MissingNecessaryInitialConditionsError(missing_ics))
return
end

"""
$(TYPEDSIGNATURES)

Expand Down Expand Up @@ -1972,6 +1998,7 @@ function __process_SciMLProblem(
check_inputmap_keys(sys, op)

op = getmetadata(sys, ProblemConstructionHook, identity)(op)::SymmapT
check_necessary_initial_conditions(sys, op)

kwargs = NamedTuple(kwargs)

Expand Down
39 changes: 39 additions & 0 deletions lib/ModelingToolkitBase/src/systems/system.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1798,3 +1798,42 @@ to `sys` for which [`reverse_transformation_during_initialization`](@ref) is `tr
function reverse_transformations_for_initialization(sys::System)
return __reverse_transformations_helper(reverse_transformation_during_initialization, sys)
end

# Necessary Initial Conditions API

# Metadata key
abstract type NecessaryInitialConditions end
const NecessaryInitialConditionsT = OrderedDict{SymbolicT, Union{String, LazyString}}

"""
$TYPEDSIGNATURES

Get a map from variables in `sys` whose initial conditions are required to a `String`
reason for why they are required. This can be mutated to change the necessary
initial conditions. After mutation, [`set_necessary_initial_conditions`](@ref) MUST
be called with the mutated map.
"""
function get_necessary_initial_conditions(sys::System)
if !isempty(get_systems(sys))
throw(ArgumentError("This is only valid for flattened systems!"))
end
return @something(
getmetadata(
sys, NecessaryInitialConditions, nothing
)::Union{NecessaryInitialConditionsT, Nothing},
NecessaryInitialConditionsT()
)
end

"""
$TYPEDSIGNATURES

See [`get_necessary_initial_conditions`](@ref).
"""
function set_necessary_initial_conditions(sys::System, ics)
if !isempty(get_systems(sys))
throw(ArgumentError("This is only valid for flattened systems!"))
end
get_necessary_initial_conditions(sys) === ics && return sys
return setmetadata(sys, NecessaryInitialConditions, ics)::System
end
9 changes: 9 additions & 0 deletions lib/ModelingToolkitBase/test/system_building.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,12 @@ end
@named sys = System([D(x) ~ x'], t)
@test_nowarn mtkcompile(sys)
end

@testset "Necessary initial conditions" begin
@variables x(t) y(t)
@mtkcomplete sys = System([D(x) ~ t, x ~ y], t)
ics = ModelingToolkitBase.get_necessary_initial_conditions(sys)
ics[y] = "Because I want to"
sys = ModelingToolkitBase.set_necessary_initial_conditions(sys, ics)
@test_throws ModelingToolkitBase.MissingNecessaryInitialConditionsError ODEProblem(sys, [x => 1], (0.0, 1.0))
end
Loading