-
-
Notifications
You must be signed in to change notification settings - Fork 236
Description
After some testing from my side, I noticed that using arrays of symbolics is far faster than using symbolic arrays (I have a lot of equations where I need to scalarize).
However, where Symbolics.jl provides the function Symbolics.variables to create arrays of symbolics, MTK doesn't seem to have it's counterpart (and the use of Symbolics.variables is often impossible due to the independent variables).
I think it would be great to have functions like
ModelingToolkit.variables(name::Symbol, indices...; T=Real, iv=nothing, default=nothing, symbolic_array=false)
ModelingToolkit.parameters(name::Symbol, indices...; T=Real, iv=nothing, default=nothing, symbolic_array=false)My knowledge of the MTK internals is very limited but I came up (with a bit of help from Claude) with those naive implementation, that themselves call the macros @variables and @parameters. This, however, doesn't cover the cases where additional metadata are needed, such as @variables x(t) [irreducible=true, ...].
using Symbolics: variable, FnType, map_subscripts, Num, setmetadata, VariableSource, Sym
using ModelingToolkit: @variables, @parameters
export variables, parameters
"""
variables(name::Symbol, indices...; T=Real, iv=nothing, default=nothing, symbolic_array=false)
Create a multi-dimensional array of individual variables with subscript notation.
# Arguments
- `name::Symbol`: Base name for the variables
- `indices...`: Index ranges for each dimension
- `T=Real`: Type of the variables
- `iv=nothing`: Independent variable (e.g., time). If provided, creates time-dependent variables.
- `default=nothing`: Default value for the variables. If provided, creates variables with default values.
- `symbolic_array=false`: If `true`, creates a classical symbolic array `name(iv)[indices...]`.
If `false` (default), creates an array of individual symbolic variables with subscript notation.
# Returns
Array of symbolic variables. If `iv` is provided, returns time-dependent variables compatible
with ModelingToolkit.
"""
function variables(name::Symbol, indices...; T::Type=Real, iv=nothing, default=nothing, symbolic_array::Bool=false)
# If symbolic_array is true, use the classical syntax
if symbolic_array
dims = length.(indices)
if iv === nothing && default === nothing
expr = quote
@variables $(name)[$(indices...)]::$T
$(name)
end
elseif iv === nothing && default !== nothing
expr = quote
@variables $(name)[$(indices...)]::$T = fill($(T(default)), $(dims...))
$(name)
end
elseif iv !== nothing && default === nothing
expr = quote
@variables $(name)($(iv))[$(indices...)]::$T
$(name)
end
else # iv !== nothing && default !== nothing
expr = quote
@variables $(name)($(iv))[$(indices...)]::$T = fill($(T(default)), $(dims...))
$(name)
end
end
return eval(expr)
end
# Build the array manually using metaprogramming
vars = Array{Num}(undef, length.(indices)...)
for (i, idx) in enumerate(Iterators.product(indices...))
# Create variable name with subscripts
name_ij = Symbol(name, join(map_subscripts.(idx), "ˏ"))
# Build the macro call dynamically based on iv and default
if iv === nothing && default === nothing
expr = quote
@variables $(name_ij)::$T
$(name_ij)
end
elseif iv === nothing && default !== nothing
expr = quote
@variables $(name_ij)::$T = $(T(default))
$(name_ij)
end
elseif iv !== nothing && default === nothing
expr = quote
@variables $(name_ij)($(iv))::$T
$(name_ij)
end
else # iv !== nothing && default !== nothing
expr = quote
@variables $(name_ij)($(iv))::$T = $(T(default))
$(name_ij)
end
end
vars[i] = eval(expr)
end
return vars
end
"""
parameters(name::Symbol, indices...; T=Real, iv=nothing, default=nothing, symbolic_array=false)
Create a multi-dimensional array of parameters with subscript notation.
# Arguments
- `name::Symbol`: Base name for the parameters
- `indices...`: Index ranges for each dimension
- `T=Real`: Type of the parameters
- `iv=nothing`: Independent variable. If provided, creates time-dependent parameters.
- `default=nothing`: Default value for the parameters. If provided, creates parameters with default values.
- `symbolic_array=false`: If `true`, creates a classical symbolic array `name(iv)[indices...]`.
If `false` (default), creates an array of individual symbolic parameters with subscript notation.
# Returns
Array of symbolic parameters created using the `@parameters` macro.
"""
function parameters(name::Symbol, indices...; T::Type=Real, iv=nothing, default=nothing, symbolic_array::Bool=false)
# If symbolic_array is true, use the classical syntax
if symbolic_array
dims = length.(indices)
if iv === nothing && default === nothing
expr = quote
@parameters $(name)[$(indices...)]::$T
$(name)
end
elseif iv === nothing && default !== nothing
expr = quote
@parameters $(name)[$(indices...)]::$T = fill($(T(default)), $(dims...))
$(name)
end
elseif iv !== nothing && default === nothing
expr = quote
@parameters $(name)($(iv))[$(indices...)]::$T
$(name)
end
else # iv !== nothing && default !== nothing
expr = quote
@parameters $(name)($(iv))[$(indices...)]::$T = fill($(T(default)), $(dims...))
$(name)
end
end
return eval(expr)
end
# For parameters, we always use @parameters macro
params = Array{Num}(undef, length.(indices)...)
for (i, idx) in enumerate(Iterators.product(indices...))
name_ij = Symbol(name, join(map_subscripts.(idx), "ˏ"))
# Build the macro call dynamically based on iv and default
if iv === nothing && default === nothing
expr = quote
@parameters $(name_ij)::$T
$(name_ij)
end
elseif iv === nothing && default !== nothing
expr = quote
@parameters $(name_ij)::$T = $(T(default))
$(name_ij)
end
elseif iv !== nothing && default === nothing
expr = quote
@parameters $(name_ij)($(iv))::$T
$(name_ij)
end
else # iv !== nothing && default !== nothing
expr = quote
@parameters $(name_ij)($(iv))::$T = $(T(default))
$(name_ij)
end
end
params[i] = eval(expr)
end
return params
endWith this probably 'bad' implementation my issue is fixed, but I think it can be a useful functionality for the package.