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