Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into MTK
Browse files Browse the repository at this point in the history
  • Loading branch information
vyudu committed Jan 8, 2025
2 parents ef1f089 + a976298 commit d23d6f7
Show file tree
Hide file tree
Showing 30 changed files with 1,639 additions and 458 deletions.
15 changes: 10 additions & 5 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ModelingToolkit"
uuid = "961ee093-0014-501f-94e3-6117800e7a78"
authors = ["Yingbo Ma <[email protected]>", "Chris Rackauckas <[email protected]> and contributors"]
version = "9.58.0"
version = "9.60.0"

[deps]
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
Expand Down Expand Up @@ -90,6 +90,7 @@ ConstructionBase = "1"
DataInterpolations = "6.4"
DataStructures = "0.17, 0.18"
DeepDiffs = "1"
DelayDiffEq = "5.50"
DiffEqBase = "6.157"
DiffEqCallbacks = "2.16, 3, 4"
DiffEqNoiseProcess = "5"
Expand Down Expand Up @@ -118,27 +119,30 @@ Libdl = "1"
LinearAlgebra = "1"
MLStyle = "0.4.17"
NaNMath = "0.3, 1"
NonlinearSolve = "3.14, 4"
NonlinearSolve = "4.3"
OffsetArrays = "1"
OrderedCollections = "1"
OrdinaryDiffEq = "6.82.0"
OrdinaryDiffEqCore = "1.13.0"
OrdinaryDiffEqDefault = "1.2"
OrdinaryDiffEqNonlinearSolve = "1.3.0"
PrecompileTools = "1"
REPL = "1"
RecursiveArrayTools = "3.26"
Reexport = "0.2, 1"
RuntimeGeneratedFunctions = "0.5.9"
SCCNonlinearSolve = "1.0.0"
SciMLBase = "2.66"
SciMLBase = "2.68.1"
SciMLStructures = "1.0"
Serialization = "1"
Setfield = "0.7, 0.8, 1"
SimpleNonlinearSolve = "0.1.0, 1, 2"
SparseArrays = "1"
SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2"
StaticArrays = "0.10, 0.11, 0.12, 1.0"
SymbolicIndexingInterface = "0.3.35"
StochasticDiffEq = "6.72.1"
StochasticDelayDiffEq = "1.8.1"
SymbolicIndexingInterface = "0.3.36"
SymbolicUtils = "3.7"
Symbolics = "6.19"
URIs = "1"
Expand All @@ -165,6 +169,7 @@ OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1"
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
OrdinaryDiffEqDefault = "50262376-6c5a-4cf5-baba-aaf4f84d72d7"
OrdinaryDiffEqNonlinearSolve = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Expand All @@ -180,4 +185,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEq", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve"]
test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve"]
5 changes: 3 additions & 2 deletions ext/MTKHomotopyContinuationExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,16 @@ function MTK.HomotopyContinuationProblem(
return prob
end

function MTK._safe_HomotopyContinuationProblem(sys, u0map, parammap = nothing; kwargs...)
function MTK._safe_HomotopyContinuationProblem(sys, u0map, parammap = nothing;
fraction_cancel_fn = SymbolicUtils.simplify_fractions, kwargs...)
if !iscomplete(sys)
error("A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`")
end
transformation = MTK.PolynomialTransformation(sys)
if transformation isa MTK.NotPolynomialError
return transformation
end
result = MTK.transform_system(sys, transformation)
result = MTK.transform_system(sys, transformation; fraction_cancel_fn)
if result isa MTK.NotPolynomialError
return result
end
Expand Down
1 change: 1 addition & 0 deletions src/ModelingToolkit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ include("discretedomain.jl")
include("systems/systemstructure.jl")
include("systems/clock_inference.jl")
include("systems/systems.jl")
include("systems/if_lifting.jl")

include("debugging.jl")
include("systems/alias_elimination.jl")
Expand Down
25 changes: 17 additions & 8 deletions src/inputoutput.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ has_var(ex, x) = x ∈ Set(get_variables(ex))
# Build control function

"""
(f_oop, f_ip), x_sym, p, io_sys = generate_control_function(
(f_oop, f_ip), x_sym, p_sym, io_sys = generate_control_function(
sys::AbstractODESystem,
inputs = unbound_inputs(sys),
disturbance_inputs = nothing;
Expand All @@ -177,8 +177,7 @@ f_ip : (xout,x,u,p,t) -> nothing
The return values also include the chosen state-realization (the remaining unknowns) `x_sym` and parameters, in the order they appear as arguments to `f`.
If `disturbance_inputs` is an array of variables, the generated dynamics function will preserve any state and dynamics associated with disturbance inputs, but the disturbance inputs themselves will not be included as inputs to the generated function. The use case for this is to generate dynamics for state observers that estimate the influence of unmeasured disturbances, and thus require unknown variables for the disturbance model, but without disturbance inputs since the disturbances are not available for measurement.
See [`add_input_disturbance`](@ref) for a higher-level interface to this functionality.
If `disturbance_inputs` is an array of variables, the generated dynamics function will preserve any state and dynamics associated with disturbance inputs, but the disturbance inputs themselves will (by default) not be included as inputs to the generated function. The use case for this is to generate dynamics for state observers that estimate the influence of unmeasured disturbances, and thus require unknown variables for the disturbance model, but without disturbance inputs since the disturbances are not available for measurement. To add an input argument corresponding to the disturbance inputs, either include the disturbance inputs among the control inputs, or set `disturbance_argument=true`, in which case an additional input argument `w` is added to the generated function `(x,u,p,t,w)->rhs`.
!!! note "Un-simplified system"
This function expects `sys` to be un-simplified, i.e., `structural_simplify` or `@mtkbuild` should not be called on the system before passing it into this function. `generate_control_function` calls a special version of `structural_simplify` internally.
Expand All @@ -196,6 +195,7 @@ f[1](x, inputs, p, t)
"""
function generate_control_function(sys::AbstractODESystem, inputs = unbound_inputs(sys),
disturbance_inputs = disturbances(sys);
disturbance_argument = false,
implicit_dae = false,
simplify = false,
eval_expression = false,
Expand All @@ -219,10 +219,11 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu
# ps = [ps; disturbance_inputs]
end
inputs = map(x -> time_varying_as_func(value(x), sys), inputs)
disturbance_inputs = unwrap.(disturbance_inputs)

eqs = [eq for eq in full_equations(sys)]
eqs = map(subs_constants, eqs)
if disturbance_inputs !== nothing
if disturbance_inputs !== nothing && !disturbance_argument
# Set all disturbance *inputs* to zero (we just want to keep the disturbance state)
subs = Dict(disturbance_inputs .=> 0)
eqs = [eq.lhs ~ substitute(eq.rhs, subs) for eq in eqs]
Expand All @@ -239,16 +240,24 @@ function generate_control_function(sys::AbstractODESystem, inputs = unbound_inpu
t = get_iv(sys)

# pre = has_difference ? (ex -> ex) : get_postprocess_fbody(sys)

args = (u, inputs, p..., t)
if disturbance_argument
args = (u, inputs, p..., t, disturbance_inputs)
else
args = (u, inputs, p..., t)
end
if implicit_dae
ddvs = map(Differential(get_iv(sys)), dvs)
args = (ddvs, args...)
end
process = get_postprocess_fbody(sys)
wrapped_arrays_vars = disturbance_argument ?
wrap_array_vars(
sys, rhss; dvs, ps, inputs, extra_args = (disturbance_inputs,)) :
wrap_array_vars(sys, rhss; dvs, ps, inputs)
f = build_function(rhss, args...; postprocess_fbody = process,
expression = Val{true}, wrap_code = wrap_mtkparameters(sys, false, 3) .∘
wrap_array_vars(sys, rhss; dvs, ps, inputs) .∘
expression = Val{true}, wrap_code = wrap_mtkparameters(
sys, false, 3, Int(disturbance_argument) + 1) .∘
wrapped_arrays_vars .∘
wrap_parameter_dependencies(sys, false),
kwargs...)
f = eval_or_rgf.(f; eval_expression, eval_module)
Expand Down
40 changes: 35 additions & 5 deletions src/systems/abstractsystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,33 @@ function wrap_parameter_dependencies(sys::AbstractSystem, isscalar)
wrap_assignments(isscalar, [eq.lhs eq.rhs for eq in parameter_dependencies(sys)])
end

"""
$(TYPEDSIGNATURES)
Add the necessary assignment statements to allow use of unscalarized array variables
in the generated code. `expr` is the expression returned by the function. `dvs` and
`ps` are the unknowns and parameters of the system `sys` to use in the generated code.
`inputs` can be specified as an array of symbolics if the generated function has inputs.
If `history == true`, the generated function accepts a history function. `cachesyms` are
extra variables (arrays of variables) stored in the cache array(s) of the parameter
object. `extra_args` are extra arguments appended to the end of the argument list.
The function is assumed to have the signature `f(du, u, h, x, p, cache_syms..., t, extra_args...)`
Where:
- `du` is the optional buffer to write to for in-place functions.
- `u` is the list of unknowns. This argument is not present if `dvs === nothing`.
- `h` is the optional history function, present if `history == true`.
- `x` is the array of inputs, present only if `inputs !== nothing`. Values are assumed
to be in the order of variables passed to `inputs`.
- `p` is the parameter object.
- `cache_syms` are the cache variables. These are part of the splatted parameter object.
- `t` is time, present only if the system is time dependent.
- `extra_args` are the extra arguments passed to the function, present only if
`extra_args` is non-empty.
"""
function wrap_array_vars(
sys::AbstractSystem, exprs; dvs = unknowns(sys), ps = parameters(sys),
inputs = nothing, history = false, cachesyms::Tuple = ())
inputs = nothing, history = false, cachesyms::Tuple = (), extra_args::Tuple = ())
isscalar = !(exprs isa AbstractArray)
var_to_arridxs = Dict()

Expand All @@ -252,6 +276,10 @@ function wrap_array_vars(
if inputs !== nothing
rps = (inputs, rps...)
end
if has_iv(sys)
rps = (rps..., get_iv(sys))
end
rps = (rps..., extra_args...)
for sym in reduce(vcat, rps; init = [])
iscall(sym) && operation(sym) == getindex || continue
arg = arguments(sym)[1]
Expand Down Expand Up @@ -332,7 +360,7 @@ end
const MTKPARAMETERS_ARG = Sym{Vector{Vector}}(:___mtkparameters___)

"""
wrap_mtkparameters(sys::AbstractSystem, isscalar::Bool, p_start = 2)
wrap_mtkparameters(sys::AbstractSystem, isscalar::Bool, p_start = 2, offset = Int(is_time_dependent(sys)))
Return function(s) to be passed to the `wrap_code` keyword of `build_function` which
allow the compiled function to be called as `f(u, p, t)` where `p isa MTKParameters`
Expand All @@ -342,12 +370,14 @@ the first parameter vector in the out-of-place version of the function. For exam
if a history function (DDEs) was passed before `p`, then the function before wrapping
would have the signature `f(u, h, p..., t)` and hence `p_start` would need to be `3`.
`offset` is the number of arguments at the end of the argument list to ignore. Defaults
to 1 if the system is time-dependent (to ignore `t`) and 0 otherwise.
The returned function is `identity` if the system does not have an `IndexCache`.
"""
function wrap_mtkparameters(sys::AbstractSystem, isscalar::Bool, p_start = 2)
function wrap_mtkparameters(sys::AbstractSystem, isscalar::Bool, p_start = 2,
offset = Int(is_time_dependent(sys)))
if has_index_cache(sys) && get_index_cache(sys) !== nothing
offset = Int(is_time_dependent(sys))

if isscalar
function (expr)
param_args = expr.args[p_start:(end - offset)]
Expand Down
Loading

0 comments on commit d23d6f7

Please sign in to comment.