Skip to content

Commit

Permalink
(v3.5.5) - Tqdm progress bar and terminal messages improvement (#439)
Browse files Browse the repository at this point in the history
* Constants: Deleted mock path due to some containers don't have tests/ folder, instead specify direcly in fixtures

* Logging: Update and improve some sinergym terminal logging messages.

* Simulator: Add episode num in constructor, and update instantiation in environment

* Simulator: Improve progres bar with tqdm (parameters set carefully)

* TerminalLogger: Updated consoleHandler with a new created TqdmLoggingHandler to syncronize with tqdm bar

* Constants: Add callback log level

* Requirements: Add tqdm dependency

* Updated Sinergym version from 3.5.4 to 3.5.5

* Scripts: Updating script to use Sinergym terminal logger

* Tests: Update test about simulator

* delete print test_stable_baselines.py

* Isort fix project
  • Loading branch information
AlejandroCN7 authored Sep 6, 2024
1 parent 594a527 commit b7d9a69
Show file tree
Hide file tree
Showing 15 changed files with 160 additions and 63 deletions.
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ pytype
twine
xlsxwriter
pipdeptree
wandb
wandb
tqdm
34 changes: 24 additions & 10 deletions scripts/eval/load_agent.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
import json
import logging
import sys

import gymnasium as gym
Expand All @@ -13,6 +14,7 @@
import sinergym.utils.gcloud as gcloud
from sinergym.utils.common import is_wrapped
from sinergym.utils.constants import *
from sinergym.utils.logger import TerminalLogger
from sinergym.utils.rewards import *
from sinergym.utils.wrappers import *

Expand All @@ -31,6 +33,15 @@
)
args = parser.parse_args()

# Optional: Terminal log in the same format as Sinergym.
# Logger info can be replaced by print.
terminal_logger = TerminalLogger()
logger = terminal_logger.getLogger(
name='EVALUATION',
level=logging.INFO
)


# ---------------------------------------------------------------------------- #
# Read json parameters #
# ---------------------------------------------------------------------------- #
Expand Down Expand Up @@ -139,24 +150,27 @@
# Execute loaded agent #
# ---------------------------------------------------------------------------- #
for i in range(conf['episodes']):
# Reset the environment to start a new episode
obs, info = env.reset()
rewards = []
truncated = terminated = False
current_month = 0
while not (terminated or truncated):
a, _ = model.predict(obs, deterministic=True)
# Random action control
a = env.action_space.sample()
# Read observation and reward
obs, reward, terminated, truncated, info = env.step(a)
rewards.append(reward)
if info['month'] != current_month:
# If this timestep is a new month start
if info['month'] != current_month: # display results every month
current_month = info['month']
print(info['month'], sum(rewards))
print(
'Episode ',
i,
'Mean reward: ',
np.mean(rewards),
'Cumulative reward: ',
sum(rewards))
# Print information
logger.info('Reward: {}'.format(sum(rewards)))
logger.info('Info: {}'.format(info))
# Final episode information print
logger.info(
'Episode {} - Mean reward: {} - Cumulative Reward: {}'.format(
i, np.mean(rewards), sum(rewards)))
env.close()

# ---------------------------------------------------------------------------- #
Expand Down
30 changes: 22 additions & 8 deletions scripts/try_env.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,48 @@
import logging

import gymnasium as gym
import numpy as np
from gymnasium.wrappers.normalize import NormalizeReward

import sinergym
from sinergym.utils.logger import TerminalLogger
from sinergym.utils.wrappers import (LoggerWrapper, NormalizeAction,
NormalizeObservation)

# Optional: Terminal log in the same format as Sinergym.
# Logger info can be replaced by print.
terminal_logger = TerminalLogger()
logger = terminal_logger.getLogger(
name='MAIN',
level=logging.INFO
)

env = gym.make('Eplus-demo-v1')
env = NormalizeAction(env)
env = NormalizeObservation(env)
env = NormalizeReward(env)
env = LoggerWrapper(env)

# Execute interactions during 1 episode
for i in range(1):
# Reset the environment to start a new episode
obs, info = env.reset()
rewards = []
truncated = terminated = False
current_month = 0
while not (terminated or truncated):
# Random action control
a = env.action_space.sample()
# Read observation and reward
obs, reward, terminated, truncated, info = env.step(a)
rewards.append(reward)
# If this timestep is a new month start
if info['month'] != current_month: # display results every month
current_month = info['month']
print('Reward: ', sum(rewards), info)
print(
'Episode ',
i,
'Mean reward: ',
np.mean(rewards),
'Cumulative reward: ',
sum(rewards))
# Print information
logger.info('Reward: {}'.format(sum(rewards)))
logger.info('Info: {}'.format(info))
# Final episode information print
logger.info('Episode {} - Mean reward: {} - Cumulative Reward: {}'.format(i,
np.mean(rewards), sum(rewards)))
env.close()
18 changes: 12 additions & 6 deletions sinergym/config/modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ def __init__(
self.timestep_per_episode = int(
self.episode_length / self.step_size)

self.logger.info('runperiod established: {}'.format(self.runperiod))
self.logger.info('Runperiod established.')
self.logger.debug('Runperiod: {}'.format(self.runperiod))
self.logger.info(
'Episode length (seconds): {}'.format(
self.episode_length))
Expand Down Expand Up @@ -187,7 +188,8 @@ def adapt_building_to_epw(
self.building['Site:Location'] = new_location
self.building['SizingPeriod:DesignDay'] = new_designdays

self.logger.info('Adapting weather to building model. [{}]'.format(
self.logger.info('Adapting weather to building model.')
self.logger.debug('Weather path: {}'.format(
self._weather_path.split('/')[-1]))

def adapt_building_to_variables(self) -> None:
Expand All @@ -203,7 +205,7 @@ def adapt_building_to_variables(self) -> None:
'reporting_frequency': 'Timestep'}

self.logger.info(
'Updated building model with whole Output:Variable available names')
'Update building model Output:Variable with variable names.')

# Delete default Output:Variables and added whole building variables to
# Output:Variable field
Expand All @@ -221,7 +223,7 @@ def adapt_building_to_meters(self) -> None:
str(i)] = {'key_name': meter_name, 'reporting_frequency': 'Timestep'}

self.logger.info(
'Updated building model with whole Output:Meter available names')
'Update building model Output:Meter with meter names.')

# Delete default Output:Variables and added whole building variables to
# Output:Variable field
Expand Down Expand Up @@ -513,7 +515,9 @@ def set_episode_working_dir(self) -> str:
self._rm_past_history_dir(episode_path, '-sub_run')

self.logger.info(
'Episode directory created [{}]'.format(
'Episode directory created.')
self.logger.debug(
'Episode directory path: {}'.format(
episode_path))

return episode_path
Expand Down Expand Up @@ -547,7 +551,9 @@ def _set_experiment_working_dir(self, env_name: str) -> str:
self.experiment_path = experiment_path

self.logger.info(
'Experiment working directory created [{}]'.format(experiment_path))
'Experiment working directory created.')
self.logger.info(
'Working directory: {}'.format(experiment_path))

return experiment_path

Expand Down
30 changes: 19 additions & 11 deletions sinergym/envs/eplus_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ def __init__(

print('#==============================================================================================#')
self.logger.info(
'Creating Gymnasium environment... [{}]'.format(env_name))
'Creating Gymnasium environment.')
self.logger.info(
'Name: {}'.format(env_name))
print('#==============================================================================================#')

# ---------------------------------------------------------------------------- #
Expand Down Expand Up @@ -186,7 +188,7 @@ def __init__(
self._check_eplus_env()

self.logger.info(
'Environment {} created successfully.'.format(env_name))
'Environment created successfully.')

# ---------------------------------------------------------------------------- #
# RESET #
Expand Down Expand Up @@ -226,8 +228,10 @@ def reset(self,
# ------------------------ Preparation for new episode ----------------------- #
print('#----------------------------------------------------------------------------------------------#')
self.logger.info(
'Starting a new episode... [{}] [Episode {}]'.format(
self.name, self.episode))
'Starting a new episode.')
self.logger.info(
'Episode {}: {}'.format(
self.episode, self.name))
print('#----------------------------------------------------------------------------------------------#')
# Get new episode working dir
self.episode_dir = self.model.set_episode_working_dir()
Expand All @@ -241,13 +245,17 @@ def reset(self,
variation=reset_options.get('weather_variability'))
eplus_working_out_path = (self.episode_dir + '/' + 'output')
self.logger.info(
'Saving episode output path... [{}]'.format(
'Saving episode output path.'.format(
eplus_working_out_path))
self.logger.debug(
'Path: {}'.format(
eplus_working_out_path))

self.energyplus_simulator.start(
building_path=eplus_working_building_path,
weather_path=eplus_working_weather_path,
output_path=eplus_working_out_path)
output_path=eplus_working_out_path,
episode=self.episode)

self.logger.info('Episode {} started.'.format(self.episode))

Expand Down Expand Up @@ -367,11 +375,11 @@ def step(self,
info.update(rw_terms)
self.last_info = info

self.logger.debug('STEP observation: {}'.format(obs))
self.logger.debug('STEP reward: {}'.format(reward))
self.logger.debug('STEP terminated: {}'.format(terminated))
self.logger.debug('STEP truncated: {}'.format(truncated))
self.logger.debug('STEP info: {}'.format(info))
# self.logger.debug('STEP observation: {}'.format(obs))
# self.logger.debug('STEP reward: {}'.format(reward))
# self.logger.debug('STEP terminated: {}'.format(terminated))
# self.logger.debug('STEP truncated: {}'.format(truncated))
# self.logger.debug('STEP info: {}'.format(info))

return np.array(list(obs.values()), dtype=np.float32
), reward, terminated, truncated, info
Expand Down
38 changes: 31 additions & 7 deletions sinergym/simulators/eplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
Class for connecting EnergyPlus with Python using pyenergyplus API.
"""

import sys
import threading
from pathlib import Path
from queue import Queue
from typing import Any, Dict, List, Optional, Tuple

from pyenergyplus.api import EnergyPlusAPI
from tqdm import tqdm

from sinergym.utils.common import *
from sinergym.utils.constants import LOG_SIM_LEVEL
Expand Down Expand Up @@ -97,14 +99,16 @@ def __init__(
def start(self,
building_path: str,
weather_path: str,
output_path: str) -> None:
output_path: str,
episode: int) -> None:
"""Initializes all callbacks and handlers using EnergyPlus API, prepare the simulation system
and start running the simulation in a Python thread.
Args:
building_path (str): EnergyPlus input description file path.
weather_path (str): EnergyPlus weather path.
output_path (str): Path where EnergyPlus process is going to allocate its output files.
episode (int): Number of the episode to run (useful to show in progress bar).
"""

# Path attributes
Expand All @@ -119,13 +123,30 @@ def start(self,
self.api.runtime.set_console_output_status(
self.energyplus_state, False)

# Progress bar for simulation
self.progress_bar = None

# Register callback used to track simulation progress
def _progress_update(percent: int) -> None:
bar_length = 100
filled_length = int(bar_length * (percent / 100.0))
bar = "*" * filled_length + '-' * (bar_length - filled_length - 1)
if self.system_ready:
print(f'\rProgress: |{bar}| {percent}%', end="\r")

if self.progress_bar is None:
# Progress bar for simulation
self.progress_bar = tqdm(
total=100,
desc='Simulation Progress [Episode {}]'.format(episode),
ncols=100,
unit='%',
leave=True,
position=0,
ascii=False,
dynamic_ncols=True,
file=sys.stdout)

percent = percent + 1 if percent < 100 else percent
self.progress_bar.update(percent - self.progress_bar.n)
self.progress_bar.set_postfix_str(f'{percent}% completed')
self.progress_bar.refresh()

self.api.runtime.callback_progress(
self.energyplus_state, _progress_update)
Expand All @@ -150,13 +171,12 @@ def _warmup_complete(state: Any) -> None:

# run EnergyPlus in a non-blocking way
def _run_energyplus(runtime, cmd_args, state, results):
self.logger.info(
self.logger.debug(
'Running EnergyPlus with args: {}'.format(cmd_args))

# start simulation
results["exit_code"] = runtime.run_energyplus(state, cmd_args)
self.simulation_complete = True
print('')

# Creating the thread and start execution
self.energyplus_thread = threading.Thread(
Expand All @@ -181,6 +201,10 @@ def stop(self) -> None:
if self.is_running:
# Set simulation as complete and force thread to finish
self.simulation_complete = True
# Kill progress bar
if self.progress_bar is not None:
self.progress_bar.close()
# Flush all queues and wait to thread to finish (without control)
self._flush_queues()
self.energyplus_thread.join()
# Delete thread
Expand Down
10 changes: 8 additions & 2 deletions sinergym/utils/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@
from stable_baselines3.common.env_util import is_wrapped
from stable_baselines3.common.vec_env import VecEnv

from sinergym.utils.constants import LOG_CALLBACK_LEVEL
from sinergym.utils.logger import TerminalLogger
from sinergym.utils.wrappers import (BaseLoggerWrapper, NormalizeObservation,
WandBLogger)


class LoggerEvalCallback(EventCallback):

logger = TerminalLogger().getLogger(
name='EVALUATION',
level=LOG_CALLBACK_LEVEL)

def __init__(
self,
eval_env: Union[gym.Env, VecEnv],
Expand Down Expand Up @@ -133,15 +139,15 @@ def _on_event(self) -> None:

# Terminal information when verbose is active
if self.verbose >= 1:
print(f"Eval num_timesteps={self.num_timesteps}, " f"episode_reward={
self.logger.info(f"Eval num_timesteps={self.num_timesteps}, " f"episode_reward={
evaluation_summary['mean_reward']: .2f} + /- {evaluation_summary['std_reward']: .2f}")

# ------------------------ Save best model if required ----------------------- #

# Condition to determine when a modes is the best
if evaluation_summary['mean_reward'] > self.best_mean_reward:
if self.verbose >= 1:
print("New best mean reward!")
self.logger.info("New best mean reward!")

# Save new best model
self.model.save(
Expand Down
Loading

0 comments on commit b7d9a69

Please sign in to comment.