Skip to content

Commit

Permalink
added path filter to log parser
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasWeise committed Jun 14, 2024
1 parent 2978265 commit 9018d31
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 25 deletions.
23 changes: 16 additions & 7 deletions moptipy/evaluation/end_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,8 @@ def from_logs(
max_time_millis: int | None | Callable[
[str, str], int | None] = None,
goal_f: int | float | None | Callable[
[str, str], int | float | None] = None) -> None:
[str, str], int | float | None] = None,
path_filter: Callable[[Path], bool] | None = None) -> None:
"""
Parse a given path and pass all end results found to the consumer.
Expand Down Expand Up @@ -523,6 +524,9 @@ def from_logs(
:param goal_f: the goal objective value, a callable to compute the
goal objective value from the algorithm and instance name, or
`None` if unspecified
:param path_filter: a filter allowing us to skip paths or files. If
this :class:`Callable` returns `True`, the file or directory is
considered for parsing. If it returns `False`, it is skipped.
"""
need_goals: bool = False
if max_fes is not None:
Expand All @@ -549,9 +553,10 @@ def from_logs(
raise ValueError(f"goal_f={goal_f} is not permissible.")
if need_goals:
__InnerProgressLogParser(
max_fes, max_time_millis, goal_f, consumer).parse(path)
max_fes, max_time_millis, goal_f, consumer,
path_filter).parse(path)
else:
__InnerLogParser(consumer).parse(path)
__InnerLogParser(consumer, path_filter).parse(path)


def to_csv(results: Iterable[EndResult], file: str) -> Path:
Expand Down Expand Up @@ -865,13 +870,15 @@ def parse_row(self, data: list[str]) -> EndResult:
class __InnerLogParser(SetupAndStateParser):
"""The internal log parser class."""

def __init__(self, consumer: Callable[[EndResult], Any]):
def __init__(self, consumer: Callable[[EndResult], Any],
path_filter: Callable[[Path], bool] | None = None):
"""
Create the internal log parser.
:param consumer: the consumer accepting the parsed data
:param path_filter: the path filter
"""
super().__init__()
super().__init__(path_filter)
if not callable(consumer):
raise type_error(consumer, "consumer", call=True)
self.__consumer: Final[Callable[[EndResult], Any]] = consumer
Expand Down Expand Up @@ -909,7 +916,8 @@ def __init__(
max_time_millis: int | None | Callable[[str, str], int | None],
goal_f: int | float | None | Callable[
[str, str], int | float | None],
consumer: Callable[[EndResult], Any]):
consumer: Callable[[EndResult], Any],
path_filter: Callable[[Path], bool] | None = None):
"""
Create the internal log parser.
Expand All @@ -918,8 +926,9 @@ def __init__(
:param max_time_millis: the maximum runtime in milliseconds, or
`None` if unspecified
:param goal_f: the goal objective value, or `None` if unspecified
:param path_filter: the path filter
"""
super().__init__()
super().__init__(path_filter=path_filter)
if not callable(consumer):
raise type_error(consumer, "consumer", call=True)
self.__consumer: Final[Callable[[EndResult], Any]] = consumer
Expand Down
77 changes: 60 additions & 17 deletions moptipy/evaluation/log_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
"""

from math import inf, isfinite, isinf
from typing import Final
from typing import Callable, Final

from pycommons.io.console import logger
from pycommons.io.csv import COMMENT_START, SCOPE_SEPARATOR
from pycommons.io.path import Path, directory_path, file_path
from pycommons.strings.string_conv import str_to_num
from pycommons.types import check_to_int_range
from pycommons.types import check_to_int_range, type_error

from moptipy.api.logging import (
ERROR_SECTION_PREFIX,
Expand Down Expand Up @@ -84,6 +84,15 @@
f"{SCOPE_ENCODING}{SCOPE_SEPARATOR}{KEY_NAME}"


def _true(_) -> bool:
"""
Get `True` as return value, always.
:retval `True`: always
"""
return True


class LogParser:
"""
A log parser can parse a log file and separate the sections.
Expand All @@ -98,7 +107,8 @@ def __init__(self,
print_file_start: bool = False,
print_file_end: bool = False,
print_dir_start: bool = True,
print_dir_end: bool = True):
print_dir_end: bool = True,
path_filter: Callable[[Path], bool] | None = None):
"""
Initialize the log parser.
Expand All @@ -108,16 +118,39 @@ def __init__(self,
:param print_file_end: log to stdout when closing a file
:param print_dir_start: log to stdout when entering a directory
:param print_dir_end: log to stdout when leaving a directory
"""
:param path_filter: a filter allowing us to skip paths or files. If
this :class:`Callable` returns `True`, the file or directory is
considered for parsing. If it returns `False`, it is skipped.
"""
if not isinstance(print_begin_end, bool):
raise type_error(print_begin_end, "print_begin_end", bool)
if not isinstance(print_file_start, bool):
raise type_error(print_file_start, "print_file_start", bool)
if not isinstance(print_file_end, bool):
raise type_error(print_file_end, "print_file_end", bool)
if not isinstance(print_dir_start, bool):
raise type_error(print_dir_start, "print_dir_start", bool)
if not isinstance(print_dir_end, bool):
raise type_error(print_dir_end, "print_dir_end", bool)
if path_filter is None:
path_filter = _true
elif not callable(path_filter):
raise type_error(path_filter, "path_filter", call=True)
#: print the overall begin and end
self.__print_begin_end: Final[bool] = print_begin_end
#: print when beginning a file
self.__print_file_start: Final[bool] = print_file_start
#: print after finishing a file
self.__print_file_end: Final[bool] = print_file_end
#: print when starting a directory
self.__print_dir_start: Final[bool] = print_dir_start
#: print when leaving a directory
self.__print_dir_end: Final[bool] = print_dir_end
#: the current depth in terms of directories
self.__depth: int = 0
#: the path filter
self.__path_filter: Final[Callable[[Path], bool]] = path_filter

# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
def start_dir(self, path: Path) -> bool:
"""
Enter a directory to parse all files inside.
Expand All @@ -142,8 +175,7 @@ def start_dir(self, path: Path) -> bool:
be skipped and parsing should continue with the next sibling
directory
"""
del path
return True
return self.__path_filter(path)

# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
Expand All @@ -167,8 +199,6 @@ def end_dir(self, path: Path) -> bool:
del path
return True

# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
def start_file(self, path: Path) -> bool:
"""
Decide whether to start parsing a file.
Expand All @@ -186,7 +216,7 @@ def start_file(self, path: Path) -> bool:
:meth:`~moptipy.evaluation.log_parser.LogParser.parse_file` should
return `True`).
"""
return path.endswith(FILE_SUFFIX)
return path.endswith(FILE_SUFFIX) and self.__path_filter(path)

# noinspection PyMethodMayBeStatic
def start_section(self, title: str) -> bool:
Expand Down Expand Up @@ -475,9 +505,16 @@ def parse(self, path: str) -> bool:
class ExperimentParser(LogParser):
"""A log parser following our pre-defined experiment structure."""

def __init__(self):
"""Initialize the experiment parser."""
super().__init__(print_begin_end=True, print_dir_start=True)
def __init__(self, path_filter: Callable[[Path], bool] | None = None):
"""
Initialize the experiment parser.
:param path_filter: a filter allowing us to skip paths or files. If
this :class:`Callable` returns `True`, the file or directory is
considered for parsing. If it returns `False`, it is skipped.
"""
super().__init__(print_begin_end=True, print_dir_start=True,
path_filter=path_filter)

#: The name of the algorithm to which the current log file belongs.
self.algorithm: str | None = None
Expand Down Expand Up @@ -535,9 +572,15 @@ class SetupAndStateParser(ExperimentParser):
stores the performance-related information in member variables.
"""

def __init__(self):
"""Create the basic data parser."""
super().__init__()
def __init__(self, path_filter: Callable[[Path], bool] | None = None):
"""
Create the basic data parser.
:param path_filter: a filter allowing us to skip paths or files. If
this :class:`Callable` returns `True`, the file or directory is
considered for parsing. If it returns `False`, it is skipped.
"""
super().__init__(path_filter=path_filter)
#: the total consumed runtime, in objective function evaluations
self.total_fes: int | None = None
#: the total consumed runtime in milliseconds
Expand Down
2 changes: 1 addition & 1 deletion moptipy/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from typing import Final

#: the version string of `moptipy`
__version__: Final[str] = "0.9.110"
__version__: Final[str] = "0.9.111"

0 comments on commit 9018d31

Please sign in to comment.