Skip to content

Commit e4ed6e2

Browse files
author
Lucas Morton
committed
Clean-up comments & tests.
1 parent 837fb38 commit e4ed6e2

File tree

2 files changed

+47
-60
lines changed

2 files changed

+47
-60
lines changed

src/systems/validation.jl

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,33 @@ struct ValidationError <: Exception
55
message::String
66
end
77

8+
"Throw exception on invalid unit types, otherwise return argument."
89
function screen_unit(result)
910
result isa Unitful.Unitlike || throw(ValidationError("Unit must be a subtype of Unitful.Unitlike, not $(typeof(result))."))
1011
result isa Unitful.ScalarUnits || throw(ValidationError("Non-scalar units such as $result are not supported. Use a scalar unit instead."))
1112
result == u"°" && throw(ValidationError("Degrees are not supported. Use radians instead."))
1213
result
1314
end
15+
16+
"""Test unit equivalence.
17+
18+
Example of implemented behavior:
19+
```julia
20+
using ModelingToolkit, Unitful
21+
MT = ModelingToolkit
22+
@parameters γ P [unit = u"MW"] E [unit = u"kJ"] τ [unit = u"ms"]
23+
@test MT.equivalent(u"MW" ,u"kJ/ms") # Understands prefixes
24+
@test !MT.equivalent(u"m", u"cm") # Units must be same magnitude
25+
@test MT.equivalent(MT.get_unit(P^γ), MT.get_unit((E/τ)^γ)) # Handles symbolic exponents
26+
```
27+
"""
1428
equivalent(x,y) = isequal(1*x,1*y)
1529
unitless = Unitful.unit(1)
1630

1731
#For dispatching get_unit
1832
Literal = Union{Sym,Symbolics.ArrayOp,Symbolics.Arr,Symbolics.CallWithMetadata}
1933
Conditional = Union{typeof(ifelse),typeof(IfElse.ifelse)}
20-
Comparison = Union{typeof(Base.:>), typeof(Base.:<), typeof(==)}
34+
Comparison = Union{typeof.([==, !=, , <, <=, , >, >=, ])...}
2135

2236
"Find the unit of a symbolic item."
2337
get_unit(x::Real) = unitless
@@ -26,9 +40,9 @@ get_unit(x::AbstractArray) = map(get_unit,x)
2640
get_unit(x::Num) = get_unit(value(x))
2741
get_unit(x::Literal) = screen_unit(getmetadata(x,VariableUnit, unitless))
2842
get_unit(op::Differential, args) = get_unit(args[1]) / get_unit(op.x)
29-
get_unit(op::Difference, args) = get_unit(args[1]) / get_unit(op.t) #why are these not identical?!?
43+
get_unit(op::Difference, args) = get_unit(args[1]) / get_unit(op.t)
3044
get_unit(op::typeof(getindex),args) = get_unit(args[1])
31-
function get_unit(op,args) #Fallback
45+
function get_unit(op,args) # Fallback
3246
result = op(1 .* get_unit.(args)...)
3347
try
3448
unit(result)
@@ -87,24 +101,22 @@ end
87101

88102
function get_unit(op::Comparison, args)
89103
terms = get_unit.(args)
90-
equivalent(terms[1], terms[2]) || throw(ValidationError(", in comparison $x, units [$(terms[1])] and [$(terms[2])] do not match."))
104+
equivalent(terms[1], terms[2]) || throw(ValidationError(", in comparison $op, units [$(terms[1])] and [$(terms[2])] do not match."))
91105
return unitless
92106
end
93107

94108
function get_unit(x::Symbolic)
95109
if SymbolicUtils.istree(x)
96110
op = operation(x)
97-
if op isa Sym # Not a real function call, just a dependent variable. Unit is on the Sym.
98-
return screen_unit(getmetadata(x, VariableUnit, unitless))
99-
elseif op isa Term && !(operation(op) isa Term) #
100-
gp = getmetadata(x,Symbolics.GetindexParent,nothing)
111+
if op isa Sym || (op isa Term && operation(op) isa Term) # Dependent variables, not function calls
112+
return screen_unit(getmetadata(x, VariableUnit, unitless)) # Like x(t) or x[i]
113+
elseif op isa Term && !(operation(op) isa Term)
114+
gp = getmetadata(x,Symbolics.GetindexParent,nothing) # Like x[1](t)
101115
return screen_unit(getmetadata(gp, VariableUnit, unitless))
102-
elseif op isa Term
103-
return screen_unit(getmetadata(x, VariableUnit, unitless))
104-
end
116+
end # Actual function calls:
105117
args = arguments(x)
106118
return get_unit(op, args)
107-
else #This function should only be reached by Terms, for which `istree` is true
119+
else # This function should only be reached by Terms, for which `istree` is true
108120
throw(ArgumentError("Unsupported value $x."))
109121
end
110122
end
@@ -119,7 +131,7 @@ function safe_get_unit(term, info)
119131
@warn("$info: $(err.x) and $(err.y) are not dimensionally compatible.")
120132
elseif err isa ValidationError
121133
@warn(info*err.message)
122-
elseif err isa MethodError #Warning: Unable to get unit for operation x[1] with arguments SymbolicUtils.Sym{Real, Base.ImmutableDict{DataType, Any}}[t].
134+
elseif err isa MethodError
123135
@warn("$info: no method matching $(err.f) for arguments $(typeof.(err.args)).")
124136
else
125137
rethrow()

test/units.jl

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,59 @@
11
using ModelingToolkit, Unitful, OrdinaryDiffEq, DiffEqJump, IfElse
22
using Test
33
MT = ModelingToolkit
4-
@parameters τ [unit = u"ms"]
4+
@parameters τ [unit = u"ms"] γ
55
@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"]
66
D = Differential(t)
77

8+
#This is how equivalent works:
9+
@test MT.equivalent(u"MW" ,u"kJ/ms")
10+
@test !MT.equivalent(u"m", u"cm")
11+
@test MT.equivalent(MT.get_unit(P^γ), MT.get_unit((E/τ)^γ))
12+
13+
# Basic access
814
@test MT.get_unit(t) == u"ms"
915
@test MT.get_unit(E) == u"kJ"
1016
@test MT.get_unit(τ) == u"ms"
17+
@test MT.get_unit(γ) == MT.unitless
18+
@test MT.get_unit(0.5) == MT.unitless
1119

20+
# Prohibited unit types
1221
@parameters β [unit = u"°"] α [unit = u"°C"] γ [unit = 1u"s"]
1322
@test_throws MT.ValidationError MT.get_unit(β)
1423
@test_throws MT.ValidationError MT.get_unit(α)
1524
@test_throws MT.ValidationError MT.get_unit(γ)
1625

17-
unitless = Unitful.unit(1)
18-
@test MT.get_unit(0.5) == unitless
19-
@test MT.get_unit(t) == u"ms"
20-
@test MT.get_unit(P) == u"MW"
21-
@test MT.get_unit(τ) == u"ms"
22-
26+
# Non-trivial equivalence & operators
2327
@test MT.get_unit^-1) == u"ms^-1"
2428
@test MT.equivalent(MT.get_unit(D(E)),u"MW")
2529
@test MT.equivalent(MT.get_unit(E/τ), u"MW")
2630
@test MT.get_unit(2*P) == u"MW"
27-
@test MT.get_unit(t/τ) == unitless
31+
@test MT.get_unit(t/τ) == MT.unitless
2832
@test MT.equivalent(MT.get_unit(P - E/τ),u"MW")
2933
@test MT.equivalent(MT.get_unit(D(D(E))),u"MW/ms")
30-
31-
@test MT.get_unit(1.0^(t/τ)) == unitless
32-
@test MT.get_unit(exp(t/τ)) == unitless
33-
@test MT.get_unit(sin(t/τ)) == unitless
34-
@test MT.get_unit(sin(1u"rad")) == unitless
34+
@test MT.get_unit(IfElse.ifelse(t>t,P,E/τ)) == u"MW"
35+
@test MT.get_unit(1.0^(t/τ)) == MT.unitless
36+
@test MT.get_unit(exp(t/τ)) == MT.unitless
37+
@test MT.get_unit(sin(t/τ)) == MT.unitless
38+
@test MT.get_unit(sin(1u"rad")) == MT.unitless
3539
@test MT.get_unit(t^2) == u"ms^2"
3640

37-
@test !MT.validate(E^1.5 ~ E^(t/τ))
38-
@test MT.validate(E^(t/τ) ~ E^(t/τ))
39-
4041
eqs = [D(E) ~ P - E/τ
4142
0 ~ P]
4243
@test MT.validate(eqs)
4344
@named sys = ODESystem(eqs)
44-
@named sys = ODESystem(eqs, t, [P, E], [τ])
4545

4646
@test !MT.validate(D(D(E)) ~ P)
4747
@test !MT.validate(0 ~ P + E*τ)
4848

49-
#Unit-free
50-
@variables x y z u
51-
@parameters σ ρ β
52-
eqs = [0 ~ σ*(y - x)]
53-
@test MT.validate(eqs)
54-
55-
##Array variables
49+
# Array variables
5650
@variables t [unit = u"s"] x[1:3](t) [unit = u"m"]
5751
@parameters v[1:3] = [1,2,3] [unit = u"m/s"]
5852
D = Differential(t)
5953
eqs = D.(x) .~ v
6054
ODESystem(eqs,name=:sys)
6155

62-
#Difference equation with units
56+
# Difference equation
6357
@parameters t [unit = u"s"] a [unit = u"s"^-1]
6458
@variables x(t) [unit = u"kg"]
6559
δ = Differential(t)
@@ -69,18 +63,6 @@ eqs = [
6963
]
7064
de = ODESystem(eqs, t, [x], [a],name=:sys)
7165

72-
73-
@parameters t
74-
@variables y[1:3](t)
75-
@parameters k[1:3]
76-
D = Differential(t)
77-
78-
eqs = [D(y[1]) ~ -k[1]*y[1] + k[3]*y[2]*y[3],
79-
D(y[2]) ~ k[1]*y[1] - k[3]*y[2]*y[3] - k[2]*y[2]^2,
80-
0 ~ y[1] + y[2] + y[3] - 1]
81-
82-
@named sys = ODESystem(eqs,t,y,k)
83-
8466
# Nonlinear system
8567
@parameters a [unit = u"kg"^-1]
8668
@variables x [unit = u"kg"]
@@ -99,16 +81,18 @@ eqs = [D(E) ~ P - E/τ
9981
noiseeqs = [0.1u"MW",
10082
0.1u"MW"]
10183
@named sys = SDESystem(eqs, noiseeqs, t, [P, E], [τ, Q])
84+
10285
# With noise matrix
10386
noiseeqs = [0.1u"MW" 0.1u"MW"
10487
0.1u"MW" 0.1u"MW"]
10588
@named sys = SDESystem(eqs,noiseeqs, t, [P, E], [τ, Q])
10689

90+
# Invalid noise matrix
10791
noiseeqs = [0.1u"MW" 0.1u"MW"
10892
0.1u"MW" 0.1u"s"]
10993
@test !MT.validate(eqs,noiseeqs)
11094

111-
#Test non-trivial simplifications
95+
# Non-trivial simplifications
11296
@variables t [unit = u"s"] V(t) [unit = u"m"^3] L(t) [unit = u"m"]
11397
@parameters v [unit = u"m/s"] r [unit =u"m"^3/u"s"]
11498
D = Differential(t)
@@ -166,12 +150,3 @@ maj1 = MassActionJump(2.0, [0 => 1], [S => 1])
166150
maj2 = MassActionJump(γ, [S => 1], [S => -1])
167151
@named js4 = JumpSystem([maj1, maj2], t, [S], [β, γ])
168152

169-
#Test comparisons
170-
@parameters t
171-
vars = @variables x(t)
172-
D = Differential(t)
173-
eqs = [
174-
D(x) ~ IfElse.ifelse(t>0.1,2,1)
175-
]
176-
@named sys = ODESystem(eqs, t, vars, [])
177-

0 commit comments

Comments
 (0)