From 48f771f9331596d595164d65e36b96677b71d267 Mon Sep 17 00:00:00 2001 From: Tom Short Date: Fri, 23 Dec 2016 08:12:47 -0500 Subject: [PATCH] Convert to ReactiveBasics for Discrete variables (#75) * Change Discretes to use ReactiveBasics --- REQUIRE | 3 +- docs/src/basics.md | 6 +-- examples/neural/lib.jl | 2 +- lib/electrical.jl | 12 ++--- src/Sims.jl | 8 ++-- src/dassl.jl | 1 - src/main.jl | 106 +++++++++++++++++------------------------ src/simcreation.jl | 2 +- src/sundials.jl | 1 - 9 files changed, 60 insertions(+), 81 deletions(-) diff --git a/REQUIRE b/REQUIRE index 87085e8..df91bbe 100644 --- a/REQUIRE +++ b/REQUIRE @@ -4,6 +4,5 @@ Documenter Ipopt JuMP Plots -Reactive -Reexport +ReactiveBasics Sundials diff --git a/docs/src/basics.md b/docs/src/basics.md index f7b6208..3b1fcb3 100644 --- a/docs/src/basics.md +++ b/docs/src/basics.md @@ -259,18 +259,18 @@ end ``` `Discrete` variables are based on `Signals` from the -[Reactive.jl](http://julialang.org/Reactive.jl/) package. This +[ReactiveBasics.jl](http://github.com/tshort/ReactiveBasics.jl/) package. This provides [Reactive Programming](http://en.wikipedia.org/wiki/Reactive_programming) capabilities where variables have data flow. This is similar to how -spreadsheets dynamically update and how Simulink works. This `lift` +spreadsheets dynamically update and how Simulink works. This `map` operator defines dependencies based on a function, and `reinit` is used to update inputs. Here is an example: ```julia a = Discrete(2.0) b = Discrete(4.0) -c = lift((x,y) -> x * y, a, b) # 8.0 +c = map((x,y) -> x * y, a, b) # 8.0 reinit(a, 4.0) # c becomes 16.0 ``` diff --git a/examples/neural/lib.jl b/examples/neural/lib.jl index 32db4e2..4962454 100644 --- a/examples/neural/lib.jl +++ b/examples/neural/lib.jl @@ -58,7 +58,7 @@ end function MembranePotential(v,currents,c) @equations begin - c * der(v) = - foldl((i,ax) -> i+ax, currents) + c * der(v) = - foldp((i,ax) -> i+ax, currents) end end diff --git a/lib/electrical.jl b/lib/electrical.jl index 6148f23..ff9d59e 100644 --- a/lib/electrical.jl +++ b/lib/electrical.jl @@ -1,4 +1,3 @@ -import Reactive ######################################## ## Electrical library ## @@ -532,15 +531,16 @@ IdealThyristor(n1::ElectricalNode, n2::ElectricalNode, fire::Discrete; """ function IdealThyristor(n1::ElectricalNode, n2::ElectricalNode, fire, Vknee, Ron = 1e-5, Goff = 1e-5) + fire = Parameter(fire) vals = compatible_values(n1, n2) i = Current(vals) v = Voltage(vals) s = Unknown(vals) # dummy variable spositive = Discrete(value(s) > 0.0) - ## off = @lift !spositive | (off & !fire) # on/off state of each switch - off = lift(x -> x[1], - foldl((off, spositive, fire) -> (!spositive | (off[1] & !fire), spositive, fire), - (true, value(spositive), value(fire)), spositive, fire)) + ## off = @map !spositive | (off & !fire) # on/off state of each switch + off = map(x -> x[1], + foldp((off, spositive, fire) -> (!spositive | (off[1] & !fire), spositive, fire), + (true, value(spositive), value(fire)), spositive, fire)) @equations begin Branch(n1, n2, v, i) BoolEvent(spositive, s) @@ -553,7 +553,7 @@ function IdealThyristor(n1::ElectricalNode, n2::ElectricalNode, fire; IdealThyristor(n1, n2, fire, Vknee, Ron, Goff) end - + """ This is an ideal GTO thyristor model which is **open** (off), if the voltage drop is less than 0 or `fire` is false **closed** (on), if the diff --git a/src/Sims.jl b/src/Sims.jl index ee82178..43cfaf7 100644 --- a/src/Sims.jl +++ b/src/Sims.jl @@ -2,8 +2,8 @@ __precompile__(false) module Sims -using Reexport -@reexport using Reactive +using ReactiveBasics +import ReactiveBasics.value import Base.ifelse, Base.hcat, @@ -29,7 +29,6 @@ export MTime, @unknown, @liftd, @comment ## Methods export Equation, @equations, is_unknown, der, delay, mexpr, compatible_values, reinit, ifelse, pre, basetypeof, from_real, to_real, - lift, gplot, plot, check, sim_verbose, elaborate, create_sim, create_simstate, sim, sunsim, dasslsim, solve, @@ -38,6 +37,9 @@ export Equation, @equations, is_unknown, der, delay, mexpr, compatible_values, r ## Model methods export Branch, BoolEvent +## ReactiveBasics +export foldp, value, signal, flatmap + using Documenter include("docutil.jl") diff --git a/src/dassl.jl b/src/dassl.jl index 76458f3..ed41233 100644 --- a/src/dassl.jl +++ b/src/dassl.jl @@ -63,7 +63,6 @@ function dasslsim(ss::SimState, tstop::Float64, Nsteps::Int=500, reltol::Float64 sm = ss.sm for x in sm.discrete_inputs push!(x.signal, x.initialvalue) - Reactive.run_till_now() end ss.y[:] = ss.y0 ss.yp[:] = ss.yp0 diff --git a/src/main.jl b/src/main.jl index ca28e3b..ce05abe 100644 --- a/src/main.jl +++ b/src/main.jl @@ -916,7 +916,7 @@ end """ -An abstract type representing Unknowns that use the Reactive.jl +An abstract type representing Unknowns that use the ReactiveBasics.jl package. The main types included are `Discrete` and `Parameter`. `Discrete` is normally used as inputs inside of models and includes an initial value that is reset at every simulation @@ -927,10 +927,9 @@ variations. Because they are Unknowns, UnknownReactive types form MExpr's when used in expressions just like Unknowns. -Many of the methods from Reactive.jl are supported, including `lift`, -`foldl`, `filter`, `dropif`, `droprepeats`, `keepwhen`, `dropwhen`, -`sampleon`, and `merge`. Use `reinit` to reinitialize a Discrete or a -Parameter (equivalent to `Reactive.push!`). +Many of the methods from ReactiveBasics.jl are supported, including `map`, +`foldp`, `filter`, and `merge`. Use `reinit` to reinitialize a Discrete or a +Parameter (equivalent to `push!`). """ abstract UnknownReactive{T} <: UnknownVariable @@ -940,13 +939,13 @@ Discrete is a type for discrete variables. These are only changed during events. They are not used by the integrator. Because they are not used by the integrator, almost any type can be used as a discrete variable. Discrete variables wrap a Signal from the -[Reactive.jl](http://julialang.org/Reactive.jl/) package. +[ReactiveBasics.jl](http://github.org/tshort/ReactiveBasics.jl) package. ### Constructors ```julia Discrete(initialvalue = 0.0) -Discrete(x::Reactive.Signal, initialvalue) +Discrete(x::ReactiveBasics.Signal, initialvalue) ``` Without arguments, `Discrete()` uses an initial value of 0.0. @@ -954,26 +953,26 @@ Without arguments, `Discrete()` uses an initial value of 0.0. ### Arguments * `initialvalue` : initial value and type information, defaults to 0.0 -* `x::Reactive.Signal` : a `Signal` from the Reactive.jl package. +* `x::ReactiveBasics.Signal` : a `Signal` from the ReactiveBasics.jl package. ### Details `Discrete` is the main input type for discrete variables. By default, -it wraps a `Reactive.Signal` type. `Discrete` variables support data -flow using Reactive.jl. Use `reinit` to update Discrete variables. Use -`lift` to create additional `UnknownReactive` types that depend on the -`Discrete` input. Use `foldl` for actions that remember state. For +it wraps a `ReactiveBasics.Signal` type. `Discrete` variables support data +flow using ReactiveBasics.jl. Use `reinit` to update Discrete variables. Use +`map` to create additional `UnknownReactive` types that depend on the +`Discrete` input. Use `foldp` for actions that remember state. For more information on *Reactive Programming*, see the -[Reactive.jl](http://julialang.org/Reactive.jl/) package. +[ReactiveBasics.jl](http://github.org/tshort/ReactiveBasics.jl) package. """ -type Discrete{T <: Reactive.Signal} <: UnknownReactive{T} +type Discrete{T <: ReactiveBasics.Signal} <: UnknownReactive{T} signal::T initialvalue end -# Discrete(x::Reactive.SignalSource) = Discrete(x, zero(x)) -Discrete(initialval) = Discrete(Reactive.Signal(initialval), initialval) -Discrete() = Discrete(Reactive.Signal(0.0), 0.0) +# Discrete(x::ReactiveBasics.SignalSource) = Discrete(x, zero(x)) +Discrete(initialval) = Discrete(ReactiveBasics.Signal(initialval), initialval) +Discrete() = Discrete(ReactiveBasics.Signal(0.0), 0.0) """ An `UnknownReactive` type that is useful for passing parameters at the @@ -983,20 +982,20 @@ top level. ```julia Parameter(x = 0.0) -Parameter(sig::Reactive.Signal} +Parameter(sig::ReactiveBasics.Signal} ``` ### Arguments * `x` : initial value and type information, defaults to 0.0 -* `sig` : A `Reactive.Signal +* `sig` : A `ReactiveBasics.Signal` ### Details Parameters can be reinitialized with `reinit`, either externally or inside models. If you want Parameters to be read-only, wrap them in another UnknownReactive before passing to models. For example, use -`param_read_only = lift(x -> x, param)`. +`param_read_only = map(x -> x, param)`. ### Examples @@ -1014,17 +1013,18 @@ vwp3 = sim(ss, 10.0) # should be the same as vwp1 ``` """ -type Parameter{T <: Reactive.Signal} <: UnknownReactive{T} +type Parameter{T <: ReactiveBasics.Signal} <: UnknownReactive{T} signal::T end -Parameter(x = 0.0) = Parameter(Reactive.Signal(x)) +Parameter(x = 0.0) = Parameter(ReactiveBasics.Signal(x)) +Parameter(x::UnknownReactive) = x name(a::UnknownReactive) = "discrete" -value{T}(x::UnknownReactive{T}) = Reactive.value(x.signal) +value{T}(x::UnknownReactive{T}) = ReactiveBasics.value(x.signal) signal(x::UnknownReactive) = x.signal -Reactive.push!{T}(x::Discrete{Reactive.Signal{T}}, y) = mexpr(:call, :(Reactive.push!), x.signal, y) -Reactive.push!{T}(x::Parameter{Reactive.Signal{T}}, y) = Reactive.push!(x.signal, y) +ReactiveBasics.push!{T}(x::Discrete{ReactiveBasics.Signal{T}}, y) = mexpr(:call, :(ReactiveBasics.push!), x.signal, y) +ReactiveBasics.push!{T}(x::Parameter{ReactiveBasics.Signal{T}}, y) = ReactiveBasics.push!(x.signal, y) """ @@ -1065,8 +1065,8 @@ end See also [IdealThyristor](../../lib/electrical/#idealthyristor) in the standard library. """ -reinit{T}(x::Discrete{Reactive.Signal{T}}, y) = MExpr(:( Reactive.push!($(x.signal), $y); Reactive.run_till_now() )) -reinit{T}(x::Parameter{Reactive.Signal{T}}, y) = Reactive.push!(x.signal, y) +reinit{T}(x::Discrete{ReactiveBasics.Signal{T}}, y) = MExpr(:( ReactiveBasics.push!($(x.signal), $y) )) +reinit{T}(x::Parameter{ReactiveBasics.Signal{T}}, y) = ReactiveBasics.push!(x.signal, y) function reinit(x, y) sim_info("reinit: $(x[]) to $y", 2) @@ -1093,18 +1093,10 @@ Create a new UnknownReactive type that links to existing UnknownReactive types (like Discrete and Parameter). ```julia -lift{T}(f::Function, inputs::UnknownReactive{T}...) -lift{T}(f::Function, t::Type, inputs::UnknownReactive{T}...) map{T}(f::Function, inputs::UnknownReactive{T}...) map{T}(f::Function, t::Type, inputs::UnknownReactive{T}...) ``` -See also -[Reactive.lift](http://julialang.org/Reactive.jl/api.html#lift)] and -the [@liftd](#liftd) helper macro to ease writing expressions. - -Note that `lift` is being transitioned to `Base.map`. - ### Arguments * `f::Function` : the transformation function; takes one argument for @@ -1119,8 +1111,8 @@ function. ```julia a = Discrete(1) -b = lift(x -> x + 1, a) -c = lift((x,y) -> x * y, a, b) +b = map(x -> x + 1, a) +c = map((x,y) -> x * y, a, b) reinit(a, 3) b # now 4 c # now 12 @@ -1131,11 +1123,11 @@ Note that you can use Discretes and Parameters in expressions that create MExprs. Compare the following: ```julia -j = lift((x,y) = x * y, a, b) +j = map((x,y) = x * y, a, b) k = a * b ``` -In this example, `j` uses `lift` to immediately connect to `a` and +In this example, `j` uses `map` to immediately connect to `a` and `b`. `k` is an MExpr with `a * b` embedded inside. When `j` is used in a model, the `j` UnknownReactive object is embedded in the model, and it is updated automatically. With `k`, `a * b` is inserted into the @@ -1144,34 +1136,22 @@ in the residual calculation. The advantage of the `a * b` approach is that the expression can include Unknowns. """ -Reactive.lift{T}(f::Function, input::UnknownReactive{T}, inputs::UnknownReactive{T}...) = Parameter(map(f, input.signal, [input.signal for input in inputs]...)) - -Reactive.lift{T}(f::Function, t::Type, input::UnknownReactive{T}, inputs::UnknownReactive{T}...) = Parameter(map(f, t, input.signal, [input.signal for input in inputs]...)) - Base.map{T}(f::Function, input::UnknownReactive{T}, inputs::UnknownReactive{T}...) = Parameter(map(f, input.signal, [input.signal for input in inputs]...)) Base.map{T}(f::Function, t::Type, input::UnknownReactive{T}, inputs::UnknownReactive{T}...) = Parameter(map(f, t, input.signal, [input.signal for input in inputs]...)) -Reactive.filter{T}(pred::Function, v0, s::UnknownReactive{T}) = Parameter(filter(pred, v0, s.signal)) -Reactive.dropwhen{T}(test::Signal{Bool}, v0, s::UnknownReactive{T}) = Parameter(dropwhen(pred, v0, s.signal)) -Reactive.sampleon(s1::UnknownReactive, s2::UnknownReactive) = Parameter(sampleon(s1.signal, s2.signal)) -# Reactive.merge() = nothing -Reactive.merge(signals::UnknownReactive...) = Parameter(merge(map(signal, signals))) -Reactive.droprepeats(s::UnknownReactive) = Parameter(droprepeats(signal(s))) -Reactive.dropif(pred::Function, v0, s::UnknownReactive) = Parameter(dropif(pred, v0, s.signal)) -Reactive.keepwhen(test::UnknownReactive{Signal{Bool}}, v0, s::UnknownReactive) = Parameter(keepwhen(test.signal, v0, s.signal)) +Base.filter{T}(pred::Function, v0, s::UnknownReactive{T}) = Parameter(filter(pred, v0, s.signal)) +Base.merge(signals::UnknownReactive...) = Parameter(merge(map(signal, signals))) +ReactiveBasics.flatmap(f, inputs::UnknownReactive...) = Parameter(flatmap(f, [x.signal for x in inputs]...)) """ -"Fold over time" -- an UnknownReactive updated based on stored state +"Fold over past values" -- an UnknownReactive updated based on stored state and additional inputs. -See also -[Reactive.foldl](http://julialang.org/Reactive.jl/api.html#foldl)]. - ```julia -foldl(f::Function, v0, inputs::UnknownReactive{T}...) +foldp(f::Function, v0, inputs::UnknownReactive{T}...) ``` ### Arguments @@ -1191,8 +1171,8 @@ foldl(f::Function, v0, inputs::UnknownReactive{T}...) See the definition of [pre](#pre) for an example. """ -Reactive.foldl{T,S}(f,v0::T, signal::UnknownReactive{S}, signals::UnknownReactive{S}...) = - Parameter(Reactive.foldl(f, v0, signal.signal, [s.signal for s in signals]...)) +ReactiveBasics.foldp{T,S}(f,v0::T, signal::UnknownReactive{S}, signals::UnknownReactive{S}...) = + Parameter(ReactiveBasics.foldp(f, v0, signal.signal, [s.signal for s in signals]...)) @@ -1204,7 +1184,7 @@ A helper for an expression of UnknownReactive variables ``` Note that the expression should not contain Unknowns. To mark the -Discrete variables, enter them as Symbols. This uses `lift()`. +Discrete variables, enter them as Symbols. This uses `map()`. ### Arguments @@ -1221,7 +1201,7 @@ x = Discrete(true) y = Discrete(false) z = @liftd :x & !:y ## equivalent to: -z2 = lift((x, y) -> x & !y, x, y) +z2 = map((x, y) -> x & !y, x, y) ``` """ @@ -1229,7 +1209,7 @@ macro liftd(ex) varnames = Any[] body = replace_syms(ex, varnames) front = Expr(:tuple, varnames...) - esc(:( Reactive.lift($front -> $body, $(varnames...)) )) + esc(:( map($front -> $body, $(varnames...)) )) end replace_syms(x, varnames) = x function replace_syms(e::Expr, varnames) @@ -1265,8 +1245,8 @@ pre(x::UnknownReactive) """ function pre{T}(x::UnknownReactive{T}) - Reactive.lift(x -> x[1], - Reactive.foldl((a,b) -> (a[2], b), (zero(Sims.value(x)), Sims.value(x)), x)) + map(x -> x[1], + ReactiveBasics.foldp((a,b) -> (a[2], b), (zero(Sims.value(x)), Sims.value(x)), x)) end diff --git a/src/simcreation.jl b/src/simcreation.jl index f2bbe47..0817edb 100644 --- a/src/simcreation.jl +++ b/src/simcreation.jl @@ -339,7 +339,7 @@ function replace_unknowns(a::PassedUnknown, sm::Sim) a.ref ## sm.unknown_idx_map[a.ref.sym] end -function replace_unknowns{T}(a::Discrete{Reactive.Signal{T}}, sm::Sim) +function replace_unknowns{T}(a::Discrete{ReactiveBasics.Signal{T}}, sm::Sim) push!(sm.discrete_inputs, a) # Discrete inputs :(value($a)) end diff --git a/src/sundials.jl b/src/sundials.jl index f65571d..b2cc177 100644 --- a/src/sundials.jl +++ b/src/sundials.jl @@ -119,7 +119,6 @@ function sunsim(ss::SimState, tstop, Nsteps = 500, reltol = 1e-4, abstol = 1e-4, # fix up initial values for x in sm.discrete_inputs push!(x.signal, x.initialvalue) - Reactive.run_till_now() end ss.y[:] = ss.y0 ss.yp[:] = ss.yp0