Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion usecases_examples/PowerGrid/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ full_scn_jan_28_1.txt
# Scenario folder
#env_ICAPS_input_data_test/

# Trained failure-forecast deployment artifacts (distributed separately)
Ressources/failure_forecast/*.pkl

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down Expand Up @@ -171,4 +174,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
5 changes: 3 additions & 2 deletions usecases_examples/PowerGrid/Dockerfile.app
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1

FROM python:3.9-slim-bullseye
FROM python:3.10-slim-bullseye
EXPOSE 5000

RUN mkdir /code
Expand All @@ -9,5 +9,6 @@ WORKDIR /code

RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
RUN pip3 install -r requirements-app.txt
RUN pip3 install -r requirements-forecast.txt

CMD ["python3", "PowerGrid_poc_simulator_app.py"]
CMD ["python3", "PowerGrid_poc_simulator_app.py"]
20 changes: 20 additions & 0 deletions usecases_examples/PowerGrid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,28 @@ When using Docker, dependencies are installed inside the image automatically.
```commandline
cd InteractiveAI/usecases_examples/PowerGrid
pip install -r requirements-app.txt
pip install -r requirements-forecast.txt
```

The overload failure forecast also requires these trained artifacts in
`Ressources/failure_forecast/`:

```text
mean_forecaster.pkl
aleatoric_forecaster.pkl
failure_classifier.pkl
failure_classifier_metadata.json
```

For a local validation with artifacts stored elsewhere, set
`FAILURE_FORECAST_ARTIFACT_DIR` to that directory before starting the
simulator. The default remains `Ressources/failure_forecast`.

The simulator keeps the required one-hour, one-day, and one-week observation
history in memory. When an overload event is accepted by InteractiveAI, the
simulator forecasts failure for that overloaded line and publishes a second
event whose `parent_event_id` is the overload event ID.

# 2 Run the simulator

The simulator is a web application. Two Docker Compose files are provided:
Expand Down
31 changes: 30 additions & 1 deletion usecases_examples/PowerGrid/app/models/Communicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numpy as np
from config.config import logging, set_pause, get_pause_status
from app.models.recommendation_store import store as recommendation_store
from app.models.failure_forecast import build_failure_forecast_event

class Communicate:
"""
Expand Down Expand Up @@ -273,6 +274,7 @@ def send_payload_and_store_it(self, payload, obs, scn_first_step):
obs: The current observation of the network.
scn_first_step: First step of the scenario.
"""
response_payload = None
try:
# Payload updating for same events detected
try:
Expand Down Expand Up @@ -301,6 +303,7 @@ def send_payload_and_store_it(self, payload, obs, scn_first_step):
# logging.info("FULL EVENT : %s", self.payload)
# print(response.text)
response.raise_for_status()
response_payload = response.json()
text = self.payload["title"]

if self.cab_api_on is True:
Expand All @@ -325,6 +328,7 @@ def send_payload_and_store_it(self, payload, obs, scn_first_step):
except Exception as e:
logging.error(e)
logging.info("The event's follow-up is not working")
return response_payload

def send_event_online(self,
context_date,
Expand Down Expand Up @@ -357,6 +361,7 @@ def send_event_online(self,
case_anticip: Flag for anticipation case (default is False).
case_line_lost: Flag for line lost case (default is False).
"""
event_response = None
if zone is None:
zone = []
if line is None:
Expand Down Expand Up @@ -388,7 +393,9 @@ def send_event_online(self,

payload = json.dumps(payload_dict)
# print(f"Overload description: {payload}")
self.send_payload_and_store_it(payload, obs, scn_first_step)
event_response = self.send_payload_and_store_it(
payload, obs, scn_first_step
)
except Exception as e:
logging.error(e)

Expand Down Expand Up @@ -503,6 +510,28 @@ def send_event_online(self,
except Exception as e:
logging.error(e)

return event_response

def send_failure_forecast_event(self,
context_date,
scn_first_step,
forecast,
parent_event_id,
obs,
line_name,
img_b64=None):
"""Send a failure forecast linked to its overload parent event."""
payload_dict = build_failure_forecast_event(
context_date,
forecast,
parent_event_id,
line_name,
img_b64,
)
return self.send_payload_and_store_it(
json.dumps(payload_dict), obs, scn_first_step
)

def issues_follow_up(self):
"""
Updates the list of ongoing issues in the simulator.
Expand Down
82 changes: 73 additions & 9 deletions usecases_examples/PowerGrid/app/models/Simulator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import time
import os
from datetime import datetime, timedelta, timezone
import grid2op
from grid2op.Chronics.handlers import PerfectForecastHandler, CSVHandler
Expand All @@ -8,9 +9,11 @@
import numpy as np
import toml
import json
from pathlib import Path
import matplotlib
matplotlib.use('agg')
from app.models.Listener import Listener
from app.models.failure_forecast import FailureForecastService
from config.config import logging, set_pause, get_pause_status
from app.models.utils import (create_observation_image, get_alert_lines, search_chronic_num_from_name,
get_curent_lines_in_bad_kpi, get_curent_lines_lost,
Expand Down Expand Up @@ -39,6 +42,7 @@ def __init__(self, socketio):
self.local_assistant = None
self.com = None
self.agent_reco = None
self.failure_forecast = None
self.socketio = socketio

def load_and_edit_config(self, params=None):
Expand Down Expand Up @@ -95,6 +99,23 @@ def initialize_simulation(self, com, session):
self.env.chronics_handler.get_name())
session['message'].append(f"Loaded scenario: {self.env.chronics_handler.get_name()}")

artifact_dir = Path(os.environ.get(
'FAILURE_FORECAST_ARTIFACT_DIR',
self.config.get(
'failure_forecast_artifact_dir',
'Ressources/failure_forecast'
)
))
try:
self.failure_forecast = FailureForecastService(artifact_dir)
self.failure_forecast.record_observation(self.obs)
logging.info(
"Failure forecast artifacts loaded from %s", artifact_dir
)
except Exception as exc:
self.failure_forecast = None
logging.error("Failure forecast is unavailable: %s", exc)

assistant_path = self.config['assistant_path']
assistant_seed = int(self.config['assistant_seed'])
self.local_assistant = load_assistant(
Expand Down Expand Up @@ -175,6 +196,8 @@ def run_simulator(self, com):

# Beginning of step: observation update
self.obs, _, done, info = self.env.step(act)
if self.failure_forecast is not None:
self.failure_forecast.record_observation(self.obs)

# Confirm whether a recommendation received from InteractiveAI was
# actually applied to the simulation on this step.
Expand Down Expand Up @@ -328,15 +351,56 @@ def run_simulator(self, com):
if not img_b64_current:
img_b64_current = create_observation_image(self.env,
self.obs)
com.send_event_online(context_date,
self.config['scenario_first_step'],
self.listen.trigger_kpis(
self.obs, act),
self.obs, self.listen.current_issues,
img_b64_current,
line_name=get_curent_lines_in_bad_kpi(
self.obs),
case_overload=True)
overloaded_line = get_curent_lines_in_bad_kpi(self.obs)
forecast_line = overloaded_line.rsplit(":", 1)[-1]
overload_response = com.send_event_online(
context_date,
self.config['scenario_first_step'],
self.listen.trigger_kpis(self.obs, act),
self.obs,
self.listen.current_issues,
img_b64_current,
line_name=overloaded_line,
case_overload=True
)
parent_event_id = None
if isinstance(overload_response, dict):
parent_event_id = (
overload_response.get("id_event")
or overload_response.get("id")
)
if parent_event_id and self.failure_forecast is not None:
try:
forecast = self.failure_forecast.predict(
self.env,
self.obs,
forecast_line
)
com.send_failure_forecast_event(
context_date,
self.config['scenario_first_step'],
forecast,
parent_event_id,
self.obs,
overloaded_line,
img_b64_current
)
logging.info(
"Failure forecast for line %s: %s",
overloaded_line,
forecast
)
except Exception as exc:
logging.error(
"Failure forecast failed for line %s: %s",
overloaded_line,
exc
)
elif self.failure_forecast is not None:
logging.error(
"Failure forecast event was not sent because "
"the overload event ID was unavailable"
)

if (self.obs.current_step < self.config['scenario_first_step']) or (com.cab_api_on is False):
# Use cached XD_Silly (local)
Expand Down
Loading