Source code for pylorenzmie.lib.LMObject

'''Base class and shared type aliases for pylorenzmie objects.'''

from abc import ABC, abstractmethod
import json
import logging
import numpy as np
import pandas as pd
from .meshgrid import meshgrid
from .lmtypes import (Property, Properties,
                    Image, Images,
                    Coordinates, Coefficients, Field,
                    Result, Results)


[docs] class LMObject(ABC): '''Base class for pylorenzmie objects. Provides the ``properties`` protocol (used for both serialization and optimization), JSON and pandas I/O, equality comparison, and a class-scoped logger. Attributes ---------- properties : dict Dictionary of adjustable object properties. Concrete subclasses must override the getter; the base-class setter applies any key that matches an existing attribute and logs a debug message for unknown keys. Notes ----- ``LMObject`` instances are mutable and therefore unhashable (``__hash__`` is explicitly ``None``). The type aliases below are re-exported at class scope for backward compatibility. Prefer importing them directly from ``pylorenzmie.lib``. ''' # Type aliases re-exported at class scope for backward compatibility. Property = Property Properties = Properties Image = Image Images = Images Coordinates = Coordinates Coefficients = Coefficients Field = Field Result = Result Results = Results # Module-level meshgrid re-exported at class scope for backward # compatibility. Import from pylorenzmie.lib directly instead. meshgrid = staticmethod(meshgrid) # Mutable objects should not be hashable. __hash__ = None @property def logger(self) -> logging.Logger: '''Logger named after the concrete class.''' return logging.getLogger( f'{self.__class__.__module__}.{self.__class__.__qualname__}') def __eq__(self, other: object) -> bool: '''Compare by properties dict rather than identity. Returns ``NotImplemented`` for objects of a different type so that Python can try the reflected operation; this is preferable to returning ``False`` outright. ''' if not isinstance(other, self.__class__): return NotImplemented return self.properties == other.properties @property @abstractmethod def properties(self) -> Properties: '''Adjustable parameters of this object. Returns a flat ``dict`` mapping parameter names to their current values. Only parameters included here are visible to the serialization methods and to ``Optimizer`` during fitting. Subclasses must override the getter using:: @ParentClass.properties.getter def properties(self) -> Properties: props = super().properties props.update(...) return props The base-class getter returns an empty dict; the base-class setter iterates over the supplied dict and calls ``setattr`` for every key that already exists as an attribute. Unknown keys are silently ignored and logged at DEBUG level. ''' return dict() @properties.setter def properties(self, properties: Properties) -> None: for name, value in properties.items(): if hasattr(self, name): setattr(self, name, value) else: self.logger.debug('Ignoring unknown property: %s', name)
[docs] def to_json(self, **kwargs) -> str: '''Serialize properties to a JSON string. NumPy scalars are automatically converted to native Python types before serialization. Parameters ---------- **kwargs Passed through to ``json.dumps``. Returns ------- str JSON-encoded properties. ''' def np_encoder(obj): if isinstance(obj, np.generic): return obj.item() return json.dumps(self.properties, default=np_encoder, **kwargs)
[docs] def from_json(self, s: str) -> None: '''Update properties from a JSON string. Mutates the object in place by assigning to ``self.properties``. Parameters ---------- s : str JSON-encoded properties, as produced by ``to_json``. ''' self.properties = json.loads(s)
[docs] def to_pandas(self, **kwargs) -> pd.Series: '''Serialize properties to a pandas Series. Parameters ---------- **kwargs Passed through to ``pandas.Series``. Returns ------- pandas.Series Index is the property names; values are the property values. ''' return pd.Series(self.properties, **kwargs)
[docs] def from_pandas(self, series: pd.Series) -> None: '''Update properties from a pandas Series. Mutates the object in place by assigning to ``self.properties``. Parameters ---------- series : pandas.Series As produced by ``to_pandas``. ''' self.properties = series.to_dict()
[docs] @classmethod def example(cls) -> None: # pragma: no cover a = cls() print(a)