Source code for plafosim.util

#
# Copyright (c) 2020-2025 Julian Heinovski <heinovski@ccs-labs.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#

import logging
import math
import os
import textwrap


[docs]def find_resource(path: str) -> str: """ Find the resouces under relpath locally or as a packaged resource. Parameters ---------- path : str The path to search for the ressource Returns ------- str : The path of the resource """ # prioritize local paths if os.path.exists(path): return path resource_path = os.path.join(os.path.dirname(__file__), path) if not os.path.exists(resource_path): raise ValueError( f"Path is not a local file or packaged resource: {path}." ) return str(resource_path)
[docs]def round_to_next_base(value: float, base: float) -> float: """ Round a value to the next base value. Parameters ---------- value : float The value to round base : float The base value to round to Returns ------- float : The rounded value """ return base * math.ceil(value / base)
[docs]def rgb2hex(c_rgb: tuple) -> str: """ Convert a color in RGB values to a color in hex values. Parameters ---------- c_rgb : tuple(int, int, int) The color in RGB values Returns ------- str : The color in hex values """ assert len(c_rgb) == 3 for c in c_rgb: # pylint: disable=C0103 assert isinstance(c, int) and 0 <= c <= 255 return '#' + ''.join(f'{i:02X}' for i in c_rgb)
[docs]def hex2rgb(c_hex: str) -> tuple: """ Convert a color in hex values to a color in RGB values. Parameters ---------- c_hex : str The color in hex values Returns ------- tuple(int, int, int) : The color in RGB values """ assert c_hex[0] == '#' assert len(c_hex[1:]) == 6 return tuple(int(i, 16) for i in textwrap.wrap(c_hex[1:], 2))
[docs]def assert_index_equal(one, two) -> bool: """ Ensure the indices of two Sequences/DataFrames are equal. Parameters ---------- one : pandas.Sequence / pandas.DataFrame The first object for the comparison two : pandas.Sequence / pandas.DataFrame The second object for the comparsion Returns ------- bool : Whether the two indices are equal """ # TODO use pandas.testing.assert_index_equal? assert list(one.index) == list(two.index)
[docs]def speed2distance(speed: float, time_interval: float = 1.0) -> float: """ Convert a driving speed to a distance driven within a given time interval. Parameters ---------- speed : float The speed to be converted time_interval : float, optional The time to consider Returns ------- float : The converted value """ return speed * time_interval
[docs]def distance2speed(distance: float, time_interval: float = 1.0) -> float: """ Convert a driven distance to a driving speed for a given time interval. Parameters ---------- distance : float The distance to be converted time_interval : float, optional The time to consider Returns ------- float : The converted value """ return distance / time_interval
[docs]def acceleration2speed(acceleration: float, time_interval: float = 1.0) -> float: """ Convert an acceleration to a driving speed for a given time interval. Parameters ---------- acceleration : float The acceleration to be converted time_interval : float, optional The time to consider Returns ------- float : The converted value """ return acceleration * time_interval
[docs]def speed2acceleration(speed_from: float, speed_to: float, time_interval: float = 1.0) -> float: """ Convert a speed range to an acceleration within a given time interval. Parameters ---------- speed_from : float The initial speed speed_to : float The target speed time_interval : float, optional The time to consider Returns ------- float : The converted value """ return (speed_to - speed_from) / time_interval
[docs]def add_logging_level(level_name: str, level_num: int, method_name: str = None): """ Comprehensively adds a new logging level to the `logging` module and the currently configured logging class. `level_name` becomes an attribute of the `logging` module with the value `level_num`. `method_name` becomes a convenience method for both `logging` itself and the class returned by `logging.getLoggerClass()` (usually just `logging.Logger`). If `method_name` is not specified, `level_name.lower()` is used. To avoid accidental clobberings of existing attributes, this method will raise an `AttributeError` if the level name is already an attribute of the `logging` module or if the method name is already present. Taken from https://stackoverflow.com/a/35804945. Parameters ---------- level_name : str The name of the level to add level_num : int The number of the level to add method_name : str The name of the method for the level to add """ if not method_name: method_name = level_name.lower() if hasattr(logging, level_name): raise AttributeError('{} already defined in logging module'.format(level_name)) if hasattr(logging, method_name): raise AttributeError('{} already defined in logging module'.format(method_name)) if hasattr(logging.getLoggerClass(), method_name): raise AttributeError('{} already defined in logger class'.format(method_name)) # This method was inspired by the answers to Stack Overflow post # http://stackoverflow.com/q/2183233/2988730, especially # http://stackoverflow.com/a/13638084/2988730 def log_for_level(self, message, *args, **kwargs): if self.isEnabledFor(level_num): self._log(level_num, message, args, **kwargs) def log_to_root(message, *args, **kwargs): logging.log(level_num, message, *args, **kwargs) logging.addLevelName(level_num, level_name) setattr(logging, level_name, level_num) setattr(logging.getLoggerClass(), method_name, log_for_level) setattr(logging, method_name, log_to_root)
[docs]class FakeLog: """ A fake logger, hide LOG behind this and be happy. """
[docs] def trace(self, *arg, **kwd): # noqa D102 pass
[docs] def debug(self, *arg, **kwd): # noqa D102 pass
[docs] def info(self, *arg, **kwd): # noqa D102 pass
[docs] def warning(self, *arg, **kwd): # noqa D102 pass
[docs] def warn(self, *arg, **kwd): # noqa D102 pass
[docs] def error(self, *arg, **kwd): # noqa D102 pass
[docs] def fatal(self, *arg, **kwd): # noqa D102 pass
FAKELOG = FakeLog() # default fake logger