Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterwln committed Mar 27, 2024
2 parents a7ecdc7 + df6e5b4 commit 9656602
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 87 deletions.
42 changes: 21 additions & 21 deletions docs/src/lib/example_mountaincar.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ end
In order to implement the design pattern described above, we need to create a structure in which we are going to store the precomputed trajectory of the mountain car:
```@example mountaincar
mutable struct MountainCarTrajectory
mutable struct MountainCarTrajectory{T<:Real}
recompute::Bool
time_left::Real
time_left::T
trajectory::Any
T::Real
T::T
end
# Convenient getters and setters
Expand All @@ -83,11 +83,11 @@ reduce_time_left!(trajectory::MountainCarTrajectory, elapsed_time) =
Here, we also implement all helper functions that give us a convenient interface to work with this trajectory. This trajectory is wrapped in the state of a Mountain Car, which contains all variables of the mountain car that are subject to change, such as position and velocity.
```@example mountaincar
mutable struct MountainCarState
position::Real
velocity::Real
throttle::Real
trajectory::MountainCarTrajectory
mutable struct MountainCarState{T<:Real}
position::T
velocity::T
throttle::T
trajectory::MountainCarTrajectory{T}
end
# Convenient getters and setters
Expand Down Expand Up @@ -115,12 +115,12 @@ The actual Mountain Car struct will contain the state of the mountain car, as we
```@example mountaincar
struct MountainCarAgent
state::MountainCarState
engine_power::Real
friction_coefficient::Real
mass::Real
target::Real
struct MountainCarAgent{T<:Real}
state::MountainCarState{T}
engine_power::T
friction_coefficient::T
mass::T
target::T
end
MountainCarAgent(
Expand Down Expand Up @@ -167,9 +167,9 @@ MountainCarEnvironment(landscape) = MountainCarEnvironment([], landscape)
In order to encode the actions conducted by mountain car entities on the environment, we introduce a `Throttle` struct that clamps an input action between $-1$ and $1$:

```@example mountaincar
struct Throttle
throttle::Real
Throttle(throttle::Real) = new(clamp(throttle, -1, 1))
struct Throttle{T<:Real}
throttle::T
Throttle(throttle::T) where {T<:Real} = new{T}(clamp(throttle, -1, 1))
end
```
Our environment contains a field `actors`, however, we still have to tell `RxEnvironments` how to add entities to this field:
Expand Down Expand Up @@ -261,8 +261,8 @@ We can create the environment with the `RxEnvironment` factory method:
```@example mountaincar
car_engine_power = 0.6
car_friction_coefficient = 0.5
car_mass = 2
car_target = 1
car_mass = 2.0
car_target = 1.0
env = RxEnvironment(MountainCarEnvironment(landscape))
agent = add!(
Expand Down Expand Up @@ -291,8 +291,8 @@ This will still utilize all environment dynamics we have written for the continu
```@example mountaincar
car_engine_power = 0.6
car_friction_coefficient = 0.5
car_mass = 2
car_target = 1
car_mass = 2.0
car_target = 1.0
env = RxEnvironment(MountainCarEnvironment(landscape); is_discrete=true)
agent = add!(
Expand Down
35 changes: 5 additions & 30 deletions docs/src/lib/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ using Distributions
# Empty agent, could contain states as well
struct ThermostatAgent end
mutable struct BayesianThermostat
temperature::Real
min_temp::Real
max_temp::Real
mutable struct BayesianThermostat{T}
temperature::T
min_temp::T
max_temp::T
end
# Helper functions
Expand Down Expand Up @@ -50,7 +50,7 @@ RxEnvironments.update!(env::BayesianThermostat, elapsed_time)= add_temperature!(
Now we've fully specified our environment, and we can interact with it. In order to create the environment, we use the `RxEnvironment` struct, and we add an agent to this environment using `add!`:

```@example thermostat
environment = RxEnvironment(BayesianThermostat(0.0, -10, 10); emit_every_ms = 900)
environment = RxEnvironment(BayesianThermostat(0.0, -10.0, 10.0); emit_every_ms = 900)
agent = add!(environment, ThermostatAgent())
```

Expand All @@ -68,29 +68,4 @@ for i in 1:10
end
```

```
[LogActor] Data: 0.006170718477015863
[LogActor] Data: -0.09624863445330185
[LogActor] Data: -0.3269267933074502
[LogActor] Data: 0.001304207094952492
[LogActor] Data: 0.03626599314271475
[LogActor] Data: 0.010733164205412482
[LogActor] Data: 0.12313893922057219
[LogActor] Data: -0.013042652548091921
[LogActor] Data: 0.03561033321842316
[LogActor] Data: 0.6763921880509323
[LogActor] Data: 0.8313618838112217
[LogActor] Data: 1.7408316683602163
[LogActor] Data: 1.7322639115928715
[LogActor] Data: 1.458556241545732
[LogActor] Data: 1.6689296645689367
[LogActor] Data: 1.683300152848493
[LogActor] Data: 2.087509970813057
[LogActor] Data: 2.258940017058188
[LogActor] Data: 2.6537100822978372
[LogActor] Data: 2.6012179767058408
[LogActor] Data: 3.0775745739101716
[LogActor] Data: 2.7326464283572727
```

Congratulations! You've now implemented a basic environment in `RxEnvironments`.
2 changes: 1 addition & 1 deletion scripts/format.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ folders_to_format = ["scripts", "src", "test"]
overwrite = commandline_args["overwrite"]
formatted = all(
map(
folder -> JuliaFormatter.format(folder, overwrite = overwrite, verbose = true),
folder -> JuliaFormatter.format(folder, overwrite=overwrite, verbose=true),
folders_to_format,
),
)
Expand Down
17 changes: 13 additions & 4 deletions src/abstractentity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ function Rocket.subscribe!(
if emitter === receiver
throw(SelfSubscriptionException(emitter))
end
actuator = Actuator()
actuator = Actuator(decorated(emitter))
insert!(actuators(markov_blanket(emitter)), receiver, actuator)
add_sensor!(markov_blanket(receiver), emitter, receiver)
add_to_state!(decorated(emitter), decorated(receiver))
end

function Rocket.subscribe!(emitter::AbstractEntity, receiver::Rocket.Actor{T} where {T})
actuator = Actuator()
actuator = Actuator(decorated(emitter))
insert!(actuators(emitter), receiver, actuator)
return subscribe!(actuator, receiver)
end
Expand Down Expand Up @@ -192,6 +192,15 @@ define different time intervals for different entity types.
"""
time_interval(any) = 1

"""
action_type(::T)
Returns the default action type for an entity of type `T`. By default, this function returns `Any`. This is used
to determine the type of actions that can be sent to an entity. Although this restricts the type of actions that can be
sent to an entity, it allows for more type-stable code.
"""
action_type(any) = Any

update!(any, elapsed_time) =
@warn "`update!` triggered for entity of type $(typeof(any)), but no update function is defined for this type." maxlog = 1

Expand All @@ -204,7 +213,7 @@ Update the state of the entity `e` based on its current state and the time elaps
- `e::AbstractEntity{T,ContinuousEntity,E}`: The entity to update.
"""
function update!(e::AbstractEntity{T,ContinuousEntity,E}) where {T,E}
c = clock(e)
c = clock(e)::WallClock
update!(decorated(e), elapsed_time(c))
set_last_update!(c, time(c))
end
Expand All @@ -226,7 +235,7 @@ function send!(
emitter::AbstractEntity,
action::Any,
)
actuator = get_actuator(emitter, recipient)
actuator = get_actuator(emitter, recipient)::Actuator{action_type(decorated(emitter))}
send_action!(actuator, action)
end

Expand Down
43 changes: 24 additions & 19 deletions src/markovblanket.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ using Rocket
using Dictionaries
import Dictionaries: Dictionary

struct Actuator
emissions::Rocket.RecentSubjectInstance
struct Actuator{T}
emissions::Rocket.RecentSubjectInstance{T,Subject{T,AsapScheduler,AsapScheduler}}
end

Actuator() = Actuator(RecentSubject(Any))
Actuator(agent) = Actuator(RecentSubject(action_type(agent)))

emission_channel(actuator::Actuator) = actuator.emissions
send_action!(actuator::Actuator, action) = next!(emission_channel(actuator), action)

Rocket.subscribe!(actuator::Actuator, actor::Rocket.Actor{T} where {T}) =
subscribe!(emission_channel(actuator), actor)

struct SensorActor <: Rocket.Actor{Any}
emitter::AbstractEntity
receiver::AbstractEntity
struct SensorActor{E,R} <: Rocket.Actor{Any} where {E<:AbstractEntity,R<:AbstractEntity}
emitter::E
receiver::R
end

emitter(actor::SensorActor) = actor.emitter
Expand All @@ -30,8 +31,8 @@ Rocket.on_error!(actor::SensorActor, error) = @error(
)
Rocket.on_complete!(actor::SensorActor) = println("SensorActor completed")

struct Sensor
actor::SensorActor
struct Sensor{E,R}
actor::SensorActor{E,R}
subscription::Teardown
end

Expand All @@ -41,10 +42,10 @@ Sensor(actor::SensorActor) =
Sensor(actor, subscribe!(get_actuator(emitter(actor), receiver(actor)), actor))
Rocket.unsubscribe!(sensor::Sensor) = Rocket.unsubscribe!(sensor.subscription)

struct Observations{T}
state_space::T
buffer::AbstractDictionary{Any,Union{Observation,Nothing}}
target::Rocket.RecentSubjectInstance
struct Observations{S,T}
state_space::S
buffer::Dictionary{Any,Union{Observation,Nothing}}
target::Rocket.RecentSubjectInstance{T,Subject{T,AsapScheduler,AsapScheduler}}
end

subject(observations::Observations) = observations.target
Expand Down Expand Up @@ -72,9 +73,9 @@ Rocket.subscribe!(observations::Observations, actor::F where {F<:AbstractActorFa
subscribe!(target(observations) |> map(Any, (x) -> data(x)), actor)

Rocket.next!(
observations::Observations{ContinuousEntity},
observation::AbstractObservation,
) = next!(target(observations), observation)
observations::Observations{ContinuousEntity,<:T},
observation::T,
) where {T<:AbstractObservation} = next!(target(observations), observation)

function Rocket.next!(observations::Observations{DiscreteEntity}, observation::Observation)
observations.buffer[emitter(observation)] = observation
Expand All @@ -86,8 +87,8 @@ function Rocket.next!(observations::Observations{DiscreteEntity}, observation::O
end

struct MarkovBlanket{S}
actuators::AbstractDictionary{Any,Actuator}
sensors::AbstractDictionary{Any,Sensor}
actuators::Dictionary{Any,Actuator}
sensors::Dictionary{Any,Sensor}
observations::Observations{S}
end

Expand All @@ -99,13 +100,17 @@ MarkovBlanket(state_space) = MarkovBlanket(

actuators(markov_blanket::MarkovBlanket) = markov_blanket.actuators
sensors(markov_blanket::MarkovBlanket) = markov_blanket.sensors
observations(markov_blanket::MarkovBlanket) = markov_blanket.observations
observations(markov_blanket::MarkovBlanket{DiscreteEntity}) =
markov_blanket.observations::Observations{DiscreteEntity,ObservationCollection}
observations(markov_blanket::MarkovBlanket{ContinuousEntity}) =
markov_blanket.observations::Observations{ContinuousEntity,AbstractObservation}

function get_actuator(markov_blanket::MarkovBlanket, agent)
if !haskey(actuators(markov_blanket), agent)
actuator_dictionary = actuators(markov_blanket)
if !haskey(actuator_dictionary, agent)
throw(NotSubscribedException(markov_blanket, agent))
end
return actuators(markov_blanket)[agent]
return actuator_dictionary[agent]
end

add_to_state!(entity, to_add) = nothing
Expand Down
18 changes: 12 additions & 6 deletions src/rxentity.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Rocket
export create_entity

mutable struct EntityActor{T,S,E} <: Rocket.Actor{Any}
entity::AbstractEntity{T,S,E}
mutable struct EntityActor{T} <: Rocket.Actor{Any}
entity::T
subscription::Union{Nothing,Rocket.Teardown}
end

Expand All @@ -18,11 +18,14 @@ by implementing the `emits` function.
This function is automatically called whenever the entity receives an observation on it's sensor. The `observation` will contain the data sent by the
emitter as well as a reference to the emitter itself.
"""
function Rocket.on_next!(actor::EntityActor{T,S,ActiveEntity} where {T,S}, observation)
function Rocket.on_next!(
actor::EntityActor{E} where {E<:AbstractEntity{T,S,<:ActiveEntity} where {T,S}},
observation,
)
subject = entity(actor)
update!(subject)
receive!(subject, observation)
for listener in subscribers(subject)
foreach(subscribers(subject)) do listener
if emits(subject, listener, observation)
action = what_to_send(listener, subject, observation)
send!(listener, subject, action)
Expand All @@ -35,7 +38,10 @@ end
Handles the logic for an incoming observation for a passive entity. This means that we will only incorporate the observation into the entity's state.
"""
function Rocket.on_next!(actor::EntityActor{T,S,PassiveEntity} where {T,S}, observation)
function Rocket.on_next!(
actor::EntityActor{E} where {E<:AbstractEntity{T,S,<:PassiveEntity} where {T,S}},
observation,
)
receive!(entity(actor), observation)
end

Expand Down Expand Up @@ -91,7 +97,7 @@ The RxEntity is the vanilla implementation of an `AbstractEntity` that is used i
"""
struct RxEntity{T,S,E} <: AbstractEntity{T,S,E}
decorated::T
markov_blanket::MarkovBlanket
markov_blanket::MarkovBlanket{S}
properties::EntityProperties{S,E}
end

Expand Down
10 changes: 5 additions & 5 deletions src/timer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct IsNotPaused end
mutable struct PausedInformation{T}
paused::T
time_paused::TimeStamp
total_time_paused::Real
total_time_paused::Float64
end
PausedInformation() = PausedInformation(IsNotPaused(), TimeStamp(0), 0.0)

Expand All @@ -41,19 +41,19 @@ is_paused(pause::PausedInformation{IsNotPaused}) = false
time_paused(pause::PausedInformation{IsPaused}) = time(pause.time_paused)
time_paused(pause::PausedInformation{IsNotPaused}) = throw(NotPausedException())

function total_time_paused(pause::PausedInformation{IsPaused}, current_time::Real)
function total_time_paused(pause::PausedInformation{IsPaused}, current_time::Float64)
return pause.total_time_paused + (current_time - time_paused(pause))
end

function total_time_paused(pause::PausedInformation{IsNotPaused}, ::Real)
function total_time_paused(pause::PausedInformation{IsNotPaused}, ::Float64)
return pause.total_time_paused
end


mutable struct WallClock <: Clock
start_time::TimeStamp
last_update::TimeStamp
real_time_factor::Real
real_time_factor::Float64
paused::PausedInformation
end

Expand All @@ -65,7 +65,7 @@ start_time(clock::WallClock) = time(clock.start_time)
last_update(clock::WallClock) = time(clock.last_update)
set_last_update!(clock::WallClock, time::Real) = clock.last_update.time = time
real_time_factor(clock::WallClock) = clock.real_time_factor
total_time_paused(clock::WallClock, current_time::Real) =
total_time_paused(clock::WallClock, current_time::Float64) =
total_time_paused(clock.paused, current_time)

function pause!(clock::WallClock)
Expand Down
Loading

2 comments on commit 9656602

@wouterwln
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/103709

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.6 -m "<description of version>" 9656602e2dfd0b94da98a1df71aaaabe5fba6525
git push origin v0.2.6

Please sign in to comment.