Skip to content

Commit

Permalink
Convert to ReactiveBasics for Discrete variables (#75)
Browse files Browse the repository at this point in the history

* Change Discretes to use ReactiveBasics
  • Loading branch information
tshort authored Dec 23, 2016
1 parent 200b35e commit 48f771f
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 81 deletions.
3 changes: 1 addition & 2 deletions REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ Documenter
Ipopt
JuMP
Plots
Reactive
Reexport
ReactiveBasics
Sundials
6 changes: 3 additions & 3 deletions docs/src/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
2 changes: 1 addition & 1 deletion examples/neural/lib.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions lib/electrical.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Reactive

########################################
## Electrical library ##
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/Sims.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ __precompile__(false)

module Sims

using Reexport
@reexport using Reactive
using ReactiveBasics
import ReactiveBasics.value

import Base.ifelse,
Base.hcat,
Expand All @@ -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,
Expand All @@ -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")
Expand Down
1 change: 0 additions & 1 deletion src/dassl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
106 changes: 43 additions & 63 deletions src/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -940,40 +939,40 @@ 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.
### 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
Expand All @@ -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
Expand All @@ -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)


"""
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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]...))



Expand All @@ -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
Expand All @@ -1221,15 +1201,15 @@ 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)
```
"""
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)
Expand Down Expand Up @@ -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


Expand Down
2 changes: 1 addition & 1 deletion src/simcreation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/sundials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 48f771f

Please sign in to comment.