Skip to content

Commit

Permalink
refactor Opera (#24)
Browse files Browse the repository at this point in the history
* refactor Instantious Interactions
* implement Futures, Controls
  • Loading branch information
thevolatilebit authored Mar 1, 2023
1 parent d675d4f commit 504d21b
Show file tree
Hide file tree
Showing 14 changed files with 461 additions and 161 deletions.
3 changes: 1 addition & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ version = "0.3.11"

[deps]
Crayons = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Glob = "c27321d9-0574-5035-807b-f59d2c89b15c"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
Crayons = "4.1"
DataStructures = "0.18"
Glob = "1.3"
MacroTools = "0.5"
Requires = "1.3"
Expand Down
48 changes: 43 additions & 5 deletions docs/src/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,49 @@ The inner area enclosed by a dashed border represents where program control is g

## Opera

The Opera system allows interactions between agents to be scheduled, which will be executed at the end of a time step, sorted by priority. By default, AlgebraicAgents.jl provides support for two types of interactions:
The Opera system allows interactions between agents to be scheduled. By default, AlgebraicAgents.jl provides support for three types of interactions:

* [`@schedule`](@ref) is used to schedule a "wake up" call to the agent, custom behavior can be implemented by defining [`AlgebraicAgents._interact!`](@ref) for subtypes of `AbstractAlgebraicAgent`.
* [`@schedule_call`](@ref) is used to schedule a callback function to the agent.
* **delayed interactions**
* **controls**
* **instantious interactions**
[`poke`](@ref) is used to schedule a "wake up" call to the agent, custom behavior can be implemented by defining [`AlgebraicAgents._interact!`](@ref) for subtypes of `AbstractAlgebraicAgent`.
* [`@call`](@ref) is used to schedule a callback function to the agent.

However the system can work with arbitrary types of interactions. To do so, simply define a new call type that is a subtype of `AbstractOperaCall`. The methods `execute_action!` and `opera_enqueue!` must be specialized for your new call type. After that, your new interaction type can be used just like any other! To see an example, please check out our tests.
However the system can work with arbitrary types of interactions. To do so, simply define a new call type that is a subtype of `AbstractOperaCall`. The methods `execute_action!` and `add_instantious!` must be specialized for your new call type. After that, your new interaction type can be used just like any other! To see an example, please check out our tests.

For more details, see the API documentation of [`Opera`](@ref) and our tests.
For more details, see the API documentation of [`Opera`](@ref) and our tests.

A dynamic structure that
- contains a **directory of algebraic agents** (dictionary of `uuid => agent` pairs);
- keeps track of, and executes, **futures (delayed interactions)**;
- keeps track of, and executes, **system controls**;
- keeps track of, and executes, **instantious interactions**;

### Future Interactions

You may schedule function calls, to be executed at predetermined points of time.
The action is specified as a tuple `(id, call, time)`, where `id` is an optional textual identifier of the action, `call` is a (parameterless) anonymous function, which will be called at given `time`.
Once the action is executed, the return value with corresponding action id and execution time is added to `futures_log` field of `Opera` instance.

See [`add_future!`](@ref) and [`@future`](@ref).

### Control Interactions

You may schedule control function calls, to be executed at every step of the model.
The action is specified as a tuple `(id, call)`, where `id` is an optional textual identifier of the action, and `call` is a (parameterless) anonymous function.
Once the action is executed, the return value with corresponding action id and execution time is added to `controls_log` field of `Opera` instance.

See [`add_control!`](@ref) and [`@control`](@ref).

### Instantious Interactions

You may schedule additional interactions which exist within a single step of the model;
such actions are modeled as named tuples `(id, priority=0., call)`. Here, `call` is a (parameterless) anonymous function.

They exist within a single step of the model and are executed after the calls
to `_prestep!` and `_step!` finish, in the order of the assigned priorities.

In particular, you may schedule interactions of two kinds:

- `poke(agent, priority)`, which will translate into a call `() -> _interact!(agent)`, with the specified priority,
- `@call opera expresion priority`, which will translate into a call `() -> expression`, with the specified priority.
14 changes: 8 additions & 6 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,13 @@ by_name

```@docs
Opera
AbstractOperaCall
AgentCall
opera_enqueue!
poke
@call
add_instantious!
@future
add_future!
@control
add_control!
```

### Operations
Expand Down Expand Up @@ -162,12 +166,10 @@ postwalk_ret
@get_agent
```

### Observable accessor, interaction schedulers
### Retrieving observables

```@docs
@observables
@schedule
@schedule_call
```

### Flat representation
Expand Down
4 changes: 2 additions & 2 deletions docs/src/sketches/sciml.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ function f_(u,p,t)
# schedule interaction
## first, schedule a call to `_interact!(agent)` with priority 0
## this is the default behavior
@schedule agent
poke(agent)
## alternatively, provide a function call f(args...)
## this will be expanded to a call f(agent, args...)
@schedule_call agent custom_function(t)
@call agent custom_function(agent, t)
min(2., 1.01*u + o1 + o2 + o3)
end
Expand Down
10 changes: 6 additions & 4 deletions src/AlgebraicAgents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ using Requires

using Glob
using UUIDs
using DataStructures
using MacroTools
using Crayons
using Random: randstring

# abstract algebraic agent types
include("abstract.jl")
Expand All @@ -21,8 +21,10 @@ export getagent, by_name, entangle!, disentangle!
# and and which contains a directory of algebraic integrators
include("opera.jl")
export AbstractOperaCall, AgentCall, Opera
## enqueue an action
export opera_enqueue!
# Opera interface
export add_instantious!, poke, @call
export add_future!, @future
export add_control!, @control

# utility functions
include("utils.jl")
Expand All @@ -31,7 +33,7 @@ export @wrap
## declare derived sequence
export @derived
## convenient observable accessor, interaction schedulers
export @observables, @schedule, @schedule_call
export @observables
## flat representation of agent hierarchy
export flatten
## instantiate an integration and add it to Julia's load path
Expand Down
5 changes: 4 additions & 1 deletion src/integrations/SciMLIntegration/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ getobservable(::Val{DummyType}, args...) = 0
gettimeobservable(::Val{DummyType}, args...) = 0
getopera(::Val{DummyType}) = Val(DummyType)
AgentCall(::Val{DummyType}, args...) = Val(DummyType)
opera_enqueue!(::Val{DummyType}, args...) = nothing
add_instantious!(::Val{DummyType}, args...) = nothing
get_count(::Val{DummyType}, args...) = "nothing"
add_future!(::Val{DummyType}, args...) = "nothing"
add_control!(::Val{DummyType}, args...) = "nothing"

# custom pretty-printing
function print_custom(io::IO, mime::MIME"text/plain", a::DiffEqAgent)
Expand Down
18 changes: 15 additions & 3 deletions src/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,11 @@ function step!(a::AbstractAlgebraicAgent, t = projected_to(a); isroot = true)
end
@ret ret _projected_to(a)

isroot && opera_run!(getopera(a))
if isroot
execute_instantious_interactions!(getopera(a), t)
@ret ret execute_futures!(getopera(a), t)
execute_controls!(getopera(a), t)
end

ret
end
Expand All @@ -205,10 +209,16 @@ end
Return `true` if all algebraic agent's time horizon was reached (or `nothing` in case of delegated evolution).
Else return the minimum time up to which the evolution of an algebraic agent, and all its descendants, has been projected.
"""
function projected_to(a::AbstractAlgebraicAgent)
function projected_to(a::AbstractAlgebraicAgent; isroot = true)
ret = _projected_to(a)
foreach(values(inners(a))) do a
@ret ret projected_to(a)
@ret ret projected_to(a; isroot = false)
end

if isroot
foreach(getopera(a).futures) do i
@ret ret i.time
end
end

ret
Expand All @@ -218,12 +228,14 @@ end
function _projected_to(t::AbstractAlgebraicAgent)
@error("type $(typeof(t)) doesn't implement `_projected_to`")
end

_projected_to(::FreeAgent) = nothing

"Step an agent forward (call only if its projected time is equal to the least projected time, among all agents in the hierarchy)."
function _step!(a::AbstractAlgebraicAgent)
@error "algebraic agent $(typeof(a)) doesn't implement `_step!`"
end

_step!(::FreeAgent) = nothing

"Pre-step to a step call (e.g., projecting algebraic agent's solution up to time `t`)."
Expand Down
Loading

0 comments on commit 504d21b

Please sign in to comment.