# -*- coding: utf-8 -*-
# Import standard library
import logging
import logging.config
import os
import pprint
# Import modules
import yaml
from tqdm import trange
[docs]class Reporter(object):
"""A Reporter object that abstracts various logging capabilities
To set-up a Reporter, simply perform the following tasks:
.. code-block:: python
from pyswarms.utils import Reporter
rep = Reporter()
rep.log("Here's my message", lvl=logging.INFO)
This will set-up a reporter with a default configuration that
logs to a file, `report.log`, on the current working directory.
You can change the log path by passing a string to the `log_path`
parameter:
.. code-block:: python
from pyswarms.utils import Reporter
rep = Reporter(log_path="/path/to/log/file.log")
rep.log("Here's my message", lvl=logging.INFO)
If you are working on a module and you have an existing logger,
you can pass that logger instance during initialization:
.. code-block:: python
# mymodule.py
from pyswarms.utils import Reporter
# An existing logger in a module
logger = logging.getLogger(__name__)
rep = Reporter(logger=logger)
Lastly, if you have your own logger configuration (YAML file),
then simply pass that to the `config_path` parameter. This
overrides the default configuration (including `log_path`):
.. code-block:: python
from pyswarms.utils import Reporter
rep = Reporter(config_path="/path/to/config/file.yml")
rep.log("Here's my message", lvl=logging.INFO)
"""
[docs] def __init__(
self, log_path=None, config_path=None, logger=None, printer=None
):
"""Initialize the reporter
Attributes
----------
log_path : str, optional
Sets the default log path (overriden when :code:`path` is given to
:code:`_setup_logger()`)
config_path : str, optional
Sets the configuration path for custom loggers
logger : logging.Logger, optional
The logger object. By default, it creates a new :code:`Logger`
instance
printer : pprint.PrettyPrinter, optional
A printer object. By default, it creates a :code:`PrettyPrinter`
instance with default values
"""
self.logger = logger or logging.getLogger(__name__)
self.printer = printer or pprint.PrettyPrinter()
self.log_path = log_path or (os.getcwd() + "/report.log")
self._bar_fmt = "{l_bar}{bar}|{n_fmt}/{total_fmt}{postfix}"
self._env_key = "LOG_CFG"
self._default_config = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"default": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "standard",
},
"file_default": {
"level": "INFO",
"formatter": "standard",
"class": "logging.handlers.RotatingFileHandler",
"filename": self.log_path,
"encoding": "utf8",
"maxBytes": 10485760,
"backupCount": 20,
},
},
"loggers": {
"": {
"handlers": ["default", "file_default"],
"level": "INFO",
"propagate": True,
}
},
}
self._setup_logger(config_path)
[docs] def log(self, msg, lvl=logging.INFO, *args, **kwargs):
"""Log a message within a set level
This method abstracts the logging.Logger.log() method. We use this
method during major state changes, errors, or critical events during
the optimization run.
You can check logging levels on this `link`_. In essence, DEBUG is 10,
INFO is 20, WARNING is 30, ERROR is 40, and CRITICAL is 50.
.. _link: https://docs.python.org/3/library/logging.html#logging-levels
Parameters
----------
msg : str
Message to be logged
lvl : int, optional
Logging level. Default is `logging.INFO`
"""
self.logger.log(lvl, msg, *args, **kwargs)
[docs] def print(self, msg, verbosity, threshold=0):
"""Print a message into console
This method can be called during non-system calls or minor state
changes. In practice, we call this method when reporting the cost
on a given timestep.
Parameters
----------
msg : str
Message to be printed
verbosity : int
Verbosity parameter, prints message when it's greater than the
threshold
threshold : int, optional
Threshold parameter, prints message when it's lesser than the
verbosity. Default is `0`
"""
if verbosity > threshold:
self.printer.pprint(msg)
else:
pass
[docs] def _setup_logger(self, path=None):
"""Set-up the logger with default values
This method is called right after initializing the Reporter module.
If no path is supplied, then it loads a default configuration.
You can view the defaults via the Reporter._default_config attribute.
Parameters
----------
path : str, optional
Path to a YAML configuration. If not supplied, uses
a default config.
"""
value = path or os.getenv(self._env_key, None)
try:
with open(value, "rt") as f:
config = yaml.safe_load(f.read())
logging.config.dictConfig(config)
except (TypeError, FileNotFoundError):
self._load_defaults()
[docs] def _load_defaults(self):
"""Load default logging configuration"""
logging.config.dictConfig(self._default_config)
[docs] def pbar(self, iters, desc=None):
"""Create a tqdm iterable
You can use this method to create progress bars. It uses a set
of abstracted methods from tqdm:
.. code-block:: python
from pyswarms.utils import Reporter
rep = Reporter()
# Create a progress bar
for i in rep.pbar(100, name="Optimizer")
pass
Parameters
----------
iters : int
Maximum range passed to the tqdm instance
desc : str, optional
Name of the progress bar that will be displayed
Returns
-------
:obj:`tqdm._tqdm.tqdm`
A tqdm iterable
"""
self.t = trange(iters, desc=desc, bar_format=self._bar_fmt)
return self.t
[docs] def hook(self, *args, **kwargs):
"""Set a hook on the progress bar
Method for creating a postfix in tqdm. In practice we use this
to report the best cost found during an iteration:
.. code-block:: python
from pyswarms.utils import Reporter
rep = Reporter()
# Create a progress bar
for i in rep.pbar(100, name="Optimizer")
best_cost = compute()
rep.hook(best_cost=best_cost)
"""
self.t.set_postfix(*args, **kwargs)