Source code for andes.core.observable

#  [ANDES] (C)2015-2025 Hantao Cui
#
#  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
#  (at your option) any later version.

"""
Observable variable class for explicit algebraic assignments.
"""

from typing import Optional

import numpy as np

from andes.core.var import BaseVar


[docs]class Observable(BaseVar): """ A variable computed by explicit assignment rather than solved in the DAE system. At code generation time, the Observable's expression is substituted into all referencing equations so that Jacobians are correctly computed via direct differentiation. Post-solve, values are evaluated and stored in ``dae.b`` for recording. Parameters ---------- e_str : str, optional Assignment expression string. Unlike ``Algeb`` where ``e_str`` means residual (``0 = e_str``), for Observable it means direct assignment (``b = e_str``). Examples -------- To define an observable ``vd`` computed from bus voltage and angle: .. code-block:: python self.vd = Observable(e_str='v * cos(delta - a)', info='d-axis voltage', tex_name='V_d') The symbol ``vd`` can then be used in other equations. At code generation time, ``vd`` will be replaced by ``v * cos(delta - a)`` in those equations. Attributes ---------- e_code : None Observable has no equation residual. v_code : str Variable code string, equals string literal ``b``. """ e_code = None v_code = 'b'
[docs] def __init__(self, e_str: Optional[str] = None, name: Optional[str] = None, tex_name: Optional[str] = None, info: Optional[str] = None, unit: Optional[str] = None, discrete=None, ): super().__init__(name=name, tex_name=tex_name, info=info, unit=unit, e_str=e_str, discrete=discrete)
[docs] def set_address(self, addr: np.ndarray, contiguous=False): """ Set the address of this Observable in ``dae.b``. Overrides BaseVar to ensure ``e_inplace`` is always False (no equation array for Observable). """ self.a = addr self.n = len(self.a) self._contiguous = contiguous # Observable values can be in-place views into dae.b if self._contiguous: self.v_inplace = True # Never in-place for equation (no equation array) self.e_inplace = False
[docs] def set_arrays(self, dae, inplace=True, alloc=True): """ Set the value array for this Observable. Only sets ``self.v`` (into ``dae.b``). No equation array is allocated since Observable has no residual. """ if inplace and self.v_inplace and self.n > 0: slice_idx = slice(self.a[0], self.a[-1] + 1) self.v = dae.__dict__[self.v_code][slice_idx] elif alloc: if not self.v_inplace: self.v = np.zeros(self.n)
# No equation array — skip e allocation entirely
[docs] def reset(self): """ Reset the internal numpy arrays and flags. """ self.n = 0 self.a[:] = 0 self.v[:] = 0 self._contiguous = False self.v_inplace = False self.e_inplace = False