Energy-domain environments for Mango.jl.
Each environment is an EnvironmentBehavior that wraps a physical simulation and exposes
observers, actions, and events so that Mango agents can perceive and influence it.
Environments are implemented against the Mango.jl world environment API as
EnvironmentBehavior subtypes. The behavior is attached to a World at construction
time and drives the underlying physical simulation. Agents are connected to specific
elements of the simulation via Mango.install, which registers typed observers and
actions on the agent.
Every environment in this package follows the same structure:
| Concern | API |
|---|---|
| Physical simulation | Updated inside Mango.on_step / Mango.initialize |
| Connect an agent | Mango.install(behavior, agent; id, type) |
| Read state | Named observers installed on the agent |
| Change state | Named actions installed on the agent |
| React to changes | Global or agent events emitted by the behavior |
| Schedule disturbances | Behavior-specific scheduling functions |
Behavior: RestorationEnvironmentBehavior
Physical system: Coupled multi-energy network (electricity, gas, heat) via MONEE
Prerequisites: monee installed in the PyCall Python environment (pip install monee)
The behavior wraps a MONEE Python network object and re-runs energy flow analysis whenever the network state changes. Failures can be scheduled to deactivate branches or nodes at a given simulation time, which triggers global events that all agents receive.
Connect an agent to a network element via:
Mango.install(behavior, agent; id=<element_id>, type=:<node|branch|child>)type |
Observer (unnamed) | Actions |
|---|---|---|
:node |
node.model.values dict |
:regulate(factor) — if node model supports it |
:branch |
branch.model.values dict |
:switch() — toggle on/off; :regulate(factor) — if supported |
:child |
merged node.model.values + child.model.values dict |
:regulate(factor) |
| Event | Scope | Emitted when |
|---|---|---|
BranchFailureEvent(branch_id) |
Global | A branch is deactivated by a scheduled failure |
NodeFailureEvent(node_id) |
Global | A node is deactivated by a scheduled failure |
CustomFailureEvent(custom_id) |
Global | A custom failure function fires |
| Function | Description |
|---|---|
schedule_failure(behavior, world, clock, failure) |
Schedule a Failure event |
topology_based_on_grid(net, topology, world) |
Build a Mango topology mirroring the physical grid |
topology_based_on_grid_groups(net, topology, world) |
Build a topology grouped by connected component |
apply_failures(net, failures) |
Apply a list of failures directly to a network (no events) |
fetch_example_net() / fetch_cigre_net() |
Load example MONEE networks |
using MangoEnergyEnvironments
using Mango
using Dates
monee_net = fetch_example_net()
behavior = RestorationEnvironmentBehavior(net=monee_net)
world = create_world(DateTime(2024, 08, 1, 0, 0, 0), behavior=behavior,
communication_sim=SimpleCommunicationSimulation(default_delay_s=0.02))
# One agent per network node
for node in monee_net.nodes
add_agent_composed_of(world, BranchFailureHandler(); suggested_aid=node.tid)
end
# Topology mirrors physical grid connectivity
topology_grid = create_topology((topology) -> topology_based_on_grid(monee_net, topology, world))
# React to branch failures: remember and propagate to neighbors
behavior_in(world, on_global_event=BranchFailureEvent, role_types=BranchFailureHandler) do role, _
role.counter += 1
send_messages(role, "Failure attention!", topology_neighbors(role))
end
behavior_in(world, on_message=String, role_types=BranchFailureHandler) do role, msg, _
role.msg_counter += 1
end
# Schedule a branch failure at t=2s
failures = [Failure(delay_s=2, branch_ids=[(monee_net.branches[3].id, monee_net.branches[3].nid)])]
activate(world) do
for failure in failures
schedule_failure(behavior, world, clock(world), failure)
end
discrete_step_until(world, 10)
endBehavior: PowerSystemsBehavior
Physical system: AC power network via PowerSystems.jl
Prerequisites: PowerSystems.jl, PowerSimulations.jl; HiGHS (for solve_central)
The behavior reads timeseries data from a PowerSystems.System at initialization and
schedules all future value changes as simulation tasks. Each change notifies the
associated agent via a PowerUpdateInfo event. Agents can observe component properties
and, for controllable devices, regulate active power output.
Connect an agent to a system component via:
Mango.install(behavior, agent; id=<component_uuid>, type=:component)The type argument is unused; the component type is inferred automatically from id.
| Observer | Returns |
|---|---|
:statics |
All static fields of the component as a Dict |
:max_active_power |
Current maximum active power (updated by timeseries) |
:active_power |
Current dispatch setpoint |
| Action | Available for | Description |
|---|---|---|
:regulate(active_power) |
ThermalStandard, RenewableDispatch, EnergyReservoirStorage |
Set active power output |
| Event | Scope | Emitted when |
|---|---|---|
PowerUpdateInfo |
Agent | A timeseries step updates the component's value |
| Function | Description |
|---|---|
calculate_initial_time(behavior) |
Returns the earliest timestamp across all component timeseries |
get_possible_components(behavior) |
Returns all components matching relevant_components |
get_components_by_type(behavior, types) |
Filter components by a list of types |
solve_central(behavior) |
Solve a 24-hour economic dispatch centrally (HiGHS) |
using MangoEnergyEnvironments
using Mango
using PowerSystems
using PowerSystemCaseBuilder
using Dates
sys = build_system(PSITestSystems, "c_sys5_bat")
behavior = PowerSystemsBehavior(system=sys, relevant_components=[PowerLoad, ThermalStandard])
initial_time = calculate_initial_time(behavior)
world = create_world(initial_time, behavior=behavior)
# One agent per relevant component
for component in get_possible_components(behavior)
agent = add_agent_composed_of(world, LoadMonitor(); suggested_aid=string(get_uuid(component)))
Mango.install(behavior, agent; id=get_uuid(component), type=:component)
end
# React to timeseries updates
behavior_in(world, on_agent_event=PowerUpdateInfo, role_types=LoadMonitor) do role, _
p_max = observe(role, :max_active_power)
# ... agent logic
end
activate(world) do
discrete_step_until(world, Hour(48))
end