Skip to content

Commit 21dff0c

Browse files
Merge pull request #3224 from AayushSabharwal/as/parse-string
feat: add function to parse variable from string
2 parents dddcd2c + 2186298 commit 21dff0c

File tree

3 files changed

+205
-1
lines changed

3 files changed

+205
-1
lines changed

docs/src/basics/FAQ.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ parameter_index(sys, sym)
8282
Note that while the variable index will be an integer, the parameter index is a struct of
8383
type `ParameterIndex` whose internals should not be relied upon.
8484

85+
## Can I index with strings?
86+
87+
Strings are not considered symbolic variables, and thus cannot directly be used for symbolic
88+
indexing. However, ModelingToolkit does provide a method to parse the string representation of
89+
a variable, given the system in which that variable exists.
90+
91+
```@docs
92+
ModelingToolkit.parse_variable
93+
```
94+
8595
## Transforming value maps to arrays
8696

8797
ModelingToolkit.jl allows (and recommends) input maps like `[x => 2.0, y => 3.0]`

src/systems/abstractsystem.jl

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3302,6 +3302,96 @@ function dump_unknowns(sys::AbstractSystem)
33023302
end
33033303
end
33043304

3305+
"""
3306+
$(TYPEDSIGNATURES)
3307+
3308+
Return the variable in `sys` referred to by its string representation `str`.
3309+
Roughly supports the following CFG:
3310+
3311+
```
3312+
varname = "D(" varname ")" | "Differential(" iv ")(" varname ")" | arrvar | maybe_dummy_var
3313+
arrvar = maybe_dummy_var "[idxs...]"
3314+
idxs = int | int "," idxs
3315+
maybe_dummy_var = namespacedvar | namespacedvar "(" iv ")" |
3316+
namespacedvar "(" iv ")" "ˍ" ts | namespacedvar "ˍ" ts |
3317+
namespacedvar "ˍ" ts "(" iv ")"
3318+
ts = iv | iv ts
3319+
namespacedvar = ident "₊" namespacedvar | ident "." namespacedvar | ident
3320+
```
3321+
3322+
Where `iv` is the independent variable, `int` is an integer and `ident` is an identifier.
3323+
"""
3324+
function parse_variable(sys::AbstractSystem, str::AbstractString)
3325+
iv = has_iv(sys) ? string(getname(get_iv(sys))) : nothing
3326+
3327+
# I'd write a regex to validate `str`, but https://xkcd.com/1171/
3328+
str = strip(str)
3329+
derivative_level = 0
3330+
while ((cond1 = startswith(str, "D(")) || startswith(str, "Differential(")) && endswith(str, ")")
3331+
if cond1
3332+
derivative_level += 1
3333+
str = _string_view_inner(str, 2, 1)
3334+
continue
3335+
end
3336+
_tmpstr = _string_view_inner(str, 13, 1)
3337+
if !startswith(_tmpstr, "$iv)(")
3338+
throw(ArgumentError("Expected differential with respect to independent variable $iv in $str"))
3339+
end
3340+
derivative_level += 1
3341+
str = _string_view_inner(_tmpstr, length(iv) + 2, 0)
3342+
end
3343+
3344+
arr_idxs = nothing
3345+
if endswith(str, ']')
3346+
open_idx = only(findfirst('[', str))
3347+
idxs_range = nextind(str, open_idx):prevind(str, lastindex(str))
3348+
idxs_str = view(str, idxs_range)
3349+
str = view(str, firstindex(str):prevind(str, open_idx))
3350+
arr_idxs = map(Base.Fix1(parse, Int), eachsplit(idxs_str, ","))
3351+
end
3352+
3353+
if iv !== nothing && endswith(str, "($iv)")
3354+
str = _string_view_inner(str, 0, 2 + length(iv))
3355+
end
3356+
3357+
dummyderivative_level = 0
3358+
if iv !== nothing && (dd_idx = findfirst('ˍ', str)) !== nothing
3359+
t_idx = findnext(iv, str, dd_idx)
3360+
while t_idx !== nothing
3361+
dummyderivative_level += 1
3362+
t_idx = findnext(iv, str, nextind(str, last(t_idx)))
3363+
end
3364+
str = view(str, firstindex(str):prevind(str, dd_idx))
3365+
end
3366+
3367+
if iv !== nothing && endswith(str, "($iv)")
3368+
str = _string_view_inner(str, 0, 2 + length(iv))
3369+
end
3370+
3371+
cur = sys
3372+
for ident in eachsplit(str, ('.', NAMESPACE_SEPARATOR))
3373+
ident = Symbol(ident)
3374+
hasproperty(cur, ident) ||
3375+
throw(ArgumentError("System $(nameof(cur)) does not have a subsystem/variable named $(ident)"))
3376+
cur = getproperty(cur, ident)
3377+
end
3378+
3379+
if arr_idxs !== nothing
3380+
cur = cur[arr_idxs...]
3381+
end
3382+
3383+
for i in 1:(derivative_level + dummyderivative_level)
3384+
cur = Differential(get_iv(sys))(cur)
3385+
end
3386+
3387+
return cur
3388+
end
3389+
3390+
function _string_view_inner(str, startoffset, endoffset)
3391+
view(str,
3392+
nextind(str, firstindex(str), startoffset):prevind(str, lastindex(str), endoffset))
3393+
end
3394+
33053395
### Functions for accessing algebraic/differential equations in systems ###
33063396

33073397
"""

test/variable_utils.jl

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using ModelingToolkit, Test
2-
using ModelingToolkit: value, vars
2+
using ModelingToolkit: value, vars, parse_variable
33
using SymbolicUtils: <
4+
45
@parameters α β δ
56
expr = (((1 / β - 1) + δ) / α)^(1 /- 1))
67
ref = sort([β, δ, α], lt = <ₑ)
@@ -41,3 +42,106 @@ ts = collect_ivs([eq])
4142
res = vars(fn([x, y], z))
4243
@test length(res) == 3
4344
end
45+
46+
@testset "parse_variable with iv: $iv" for iv in [t, only(@independent_variables tt)]
47+
D = Differential(iv)
48+
function Lorenz(; name)
49+
@variables begin
50+
x(iv)
51+
y(iv)
52+
z(iv)
53+
end
54+
@parameters begin
55+
σ
56+
ρ
57+
β
58+
end
59+
sys = ODESystem(
60+
[D(D(x)) ~ σ * (y - x)
61+
D(y) ~ x *- z) - y
62+
D(z) ~ x * y - β * z], iv; name)
63+
end
64+
function ArrSys(; name)
65+
@variables begin
66+
x(iv)[1:2]
67+
end
68+
@parameters begin
69+
p[1:2, 1:2]
70+
end
71+
sys = ODESystem([D(D(x)) ~ p * x], iv; name)
72+
end
73+
function Outer(; name)
74+
@named 😄 = Lorenz()
75+
@named arr = ArrSys()
76+
sys = ODESystem(Equation[], iv; name, systems = [😄, arr])
77+
end
78+
79+
@mtkbuild sys = Outer()
80+
for (str, var) in [
81+
# unicode system, scalar variable
82+
("😄.x", sys.😄.x),
83+
("😄.x($iv)", sys.😄.x),
84+
("😄₊x", sys.😄.x),
85+
("😄₊x($iv)", sys.😄.x),
86+
# derivative
87+
("D(😄.x)", D(sys.😄.x)),
88+
("D(😄.x($iv))", D(sys.😄.x)),
89+
("D(😄₊x)", D(sys.😄.x)),
90+
("D(😄₊x($iv))", D(sys.😄.x)),
91+
("Differential($iv)(😄.x)", D(sys.😄.x)),
92+
("Differential($iv)(😄.x($iv))", D(sys.😄.x)),
93+
("Differential($iv)(😄₊x)", D(sys.😄.x)),
94+
("Differential($iv)(😄₊x($iv))", D(sys.😄.x)),
95+
# other derivative
96+
("😄.xˍ$iv", D(sys.😄.x)),
97+
("😄.x($iv$iv", D(sys.😄.x)),
98+
("😄₊xˍ$iv", D(sys.😄.x)),
99+
("😄₊x($iv$iv", D(sys.😄.x)),
100+
# scalar parameter
101+
("😄.σ", sys.😄.σ),
102+
("😄₊σ", sys.😄.σ),
103+
# array variable
104+
("arr.x", sys.arr.x),
105+
("arr₊x", sys.arr.x),
106+
("arr.x($iv)", sys.arr.x),
107+
("arr₊x($iv)", sys.arr.x),
108+
# getindex
109+
("arr.x[1]", sys.arr.x[1]),
110+
("arr₊x[1]", sys.arr.x[1]),
111+
("arr.x($iv)[1]", sys.arr.x[1]),
112+
("arr₊x($iv)[1]", sys.arr.x[1]),
113+
# derivative
114+
("D(arr.x($iv))", D(sys.arr.x)),
115+
("D(arr₊x($iv))", D(sys.arr.x)),
116+
("D(arr.x[1])", D(sys.arr.x[1])),
117+
("D(arr₊x[1])", D(sys.arr.x[1])),
118+
("D(arr.x($iv)[1])", D(sys.arr.x[1])),
119+
("D(arr₊x($iv)[1])", D(sys.arr.x[1])),
120+
("Differential($iv)(arr.x($iv))", D(sys.arr.x)),
121+
("Differential($iv)(arr₊x($iv))", D(sys.arr.x)),
122+
("Differential($iv)(arr.x[1])", D(sys.arr.x[1])),
123+
("Differential($iv)(arr₊x[1])", D(sys.arr.x[1])),
124+
("Differential($iv)(arr.x($iv)[1])", D(sys.arr.x[1])),
125+
("Differential($iv)(arr₊x($iv)[1])", D(sys.arr.x[1])),
126+
# other derivative
127+
("arr.xˍ$iv", D(sys.arr.x)),
128+
("arr₊xˍ$iv", D(sys.arr.x)),
129+
("arr.xˍ$iv($iv)", D(sys.arr.x)),
130+
("arr₊xˍ$iv($iv)", D(sys.arr.x)),
131+
("arr.xˍ$iv[1]", D(sys.arr.x[1])),
132+
("arr₊xˍ$iv[1]", D(sys.arr.x[1])),
133+
("arr.xˍ$iv($iv)[1]", D(sys.arr.x[1])),
134+
("arr₊xˍ$iv($iv)[1]", D(sys.arr.x[1])),
135+
("arr.x($iv$iv", D(sys.arr.x)),
136+
("arr₊x($iv$iv", D(sys.arr.x)),
137+
("arr.x($iv$iv[1]", D(sys.arr.x[1])),
138+
("arr₊x($iv$iv[1]", D(sys.arr.x[1])),
139+
# array parameter
140+
("arr.p", sys.arr.p),
141+
("arr₊p", sys.arr.p),
142+
("arr.p[1, 2]", sys.arr.p[1, 2]),
143+
("arr₊p[1, 2]", sys.arr.p[1, 2])
144+
]
145+
@test isequal(parse_variable(sys, str), var)
146+
end
147+
end

0 commit comments

Comments
 (0)