Source code for ampworks.utils._rich_result

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:  # pragma: no cover
    from typing import Any, Self


# RichResult and its formatters are modified copies from scipy._lib._util
[docs] class RichResult(dict): """Dict-like results container.""" _order_keys = [] __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ def __init__(self, **kwargs) -> None: """ A container class based off the `_RichResult` class in the `scipy` library. It combines a series of formatting functions to make the printed 'repr' easy to read. Use this class directly by passing in any number of keyword arguments, or use it as a base class to have custom classes for different result types. Inheriting classes should define the class attribute `_order_keys` which is a list of strings that defines how output fields are sorted when an instance is printed. Parameters ---------- **kwargs : dict, optional User-specified keyword arguments. Any number of arguments can be given as input. The class simply stores the key/value pairs, makes them accessible as attributes, and provides pretty printing. Examples -------- The example below demonstrates how to define the `_order_keys` class attribute for custom sorting. If arguments are not in the list, they are placed at the end based on the order they were given. Note that `_order_keys` only provides sorting support and that no errors are raised if an argument is not present, e.g., `third` below. .. code-block:: python from ampworks.utils import RichResult class CustomResult(RichResult): _order_keys = ['first', 'second', 'third',] result = CustomResult(second=None, last=None, first=None) print(result) `RichResult` can also be used directly, without any custom sorting. Arguments will print based on the order they were input. Instances will still have a fully formatted 'repr', including formatted arrays. .. code-block:: python import numpy as np from ampworks.utils import RichResult t = np.linspace(0, 1, 1000) y = np.random.rand(1000, 5) y[0] = np.inf y[-1] = np.nan result = RichResult(message='Example.', status=0, t=t, y=y) print(result) After initialization, all key/value pairs are accessible as instance attributes. .. code-block:: python from ampworks.utils import RichResult result = RichResult(a=10, b=20, c=30) print(result.a*(result.b + result.c)) """ for k, v in kwargs.items(): setattr(self, k, v) def __getattr__(self, name: str) -> Any: """Provide attribute-style access to dictionary keys.""" try: return self[name] except KeyError as e: raise AttributeError(name) from e def __repr__(self) -> str: """Return a nicely formatted string representation of the instance.""" order_keys = getattr(self, '_order_keys') def key(item): try: return order_keys.index(item[0].lower()) except ValueError: # item not in list, move to end return np.inf def sorter(d: list[str]) -> list[str]: return sorted(d.items(), key=key) if self.keys(): return '\n' + _format_dict(self, sorter=sorter) + '\n' else: return self.__class__.__name__ + '()' def __dir__(self): """List available attributes, corresponding to dictionary keys.""" return list(self.keys())
[docs] def copy(self) -> Self: """ Returns a copy of the instance. Returns ------- result : Self A deep copy of the current instance. Does not share any memory with the original instance. """ from copy import deepcopy return deepcopy(self)
def _indenter(s, n=0): """Ensures lines after the first are indented by the specified amount.""" split = s.split('\n') return ('\n' + ' '*n).join(split) def _format_float_10(x): """Returns string representation of floats with exactly ten characters.""" if np.isposinf(x): return ' inf' elif np.isneginf(x): return ' -inf' elif np.isnan(x): return ' nan' return np.format_float_scientific(x, precision=3, pad_left=2, unique=False) def _format_dict(d, n=0, mplus=1, sorter=None): """Pretty printer for dictionaries.""" if isinstance(d, dict): m = max(map(len, list(d.keys()))) + mplus # width to print keys # indent and format each value... + 2 for ': ' line_items = [] for k, v in sorter(d): formatted_v = _indenter(_format_dict(v, m+n+2, 0, sorter), m+2) line_items.append(k.rjust(m) + ': ' + formatted_v) output_str = '\n'.join(line_items) else: with np.printoptions(linewidth=76-n, edgeitems=2, threshold=12, formatter={'float_kind': _format_float_10}): output_str = str(d) return output_str