Source code for Karana.Models._KModel_Py

# Copyright (c) 2024-2025 Karana Dynamics Pty Ltd. All rights reserved.
#
# NOTICE TO USER:
#
# This source code and/or documentation (the "Licensed Materials") is
# the confidential and proprietary information of Karana Dynamics Inc.
# Use of these Licensed Materials is governed by the terms and conditions
# of a separate software license agreement between Karana Dynamics and the
# Licensee ("License Agreement"). Unless expressly permitted under that
# agreement, any reproduction, modification, distribution, or disclosure
# of the Licensed Materials, in whole or in part, to any third party
# without the prior written consent of Karana Dynamics is strictly prohibited.
#
# THE LICENSED MATERIALS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# KARANA DYNAMICS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
# BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND
# FITNESS FOR A PARTICULAR PURPOSE.
#
# IN NO EVENT SHALL KARANA DYNAMICS BE LIABLE FOR ANY DAMAGES WHATSOEVER,
# INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, OR USE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, WHETHER IN CONTRACT, TORT,
# OR OTHERWISE ARISING OUT OF OR IN CONNECTION WITH THE LICENSED MATERIALS.
#
# U.S. Government End Users: The Licensed Materials are a "commercial item"
# as defined at 48 C.F.R. 2.101, and are provided to the U.S. Government
# only as a commercial end item under the terms of this license.
#
# Any use of the Licensed Materials in individual or commercial software must
# include, in the user documentation and internal source code comments,
# this Notice, Disclaimer, and U.S. Government Use Provision.

"""KModel Python base class."""

from numpy.typing import NDArray as _NDArray
import numpy as _np
from Karana.Models._KModel_pybind11_Py import *
from Karana.KUtils.DataStruct import DataStruct as _DataStruct
from Karana.Core import DumpOptionsBase as _DumpOptionsBase
from typing import TYPE_CHECKING, TypeVar, Generic

if TYPE_CHECKING:
    from Karana.Dynamics import StatePropagator as _StatePropagator


class KModelParams(_DataStruct):
    """BaseClass for the model parameters of Python models.

    For a new model parameter class, simply derive from this class
    and add class variables just like you would do for any other
    DataStruct. Users should override the isFinalized if applicable
    to ensure that all model parameters have been defined.
    """

[docs] def isFinalized(self) -> bool: """Return true if all the parameters are finalized. False otherwise.""" return True
[docs] def dumpString(self, prefix: str, options: _DumpOptionsBase | None = None) -> str: """Dump the parameters as a string. Parameters ---------- prefix : str The prefix for the string. options : _DumpOptionsBase | None Options for the string. Returns ------- str The parameters as a string. """ return ""
class NoParams(KModelParams): """Class indicating there are no model params.""" pass class NoScratch(KModelScratch): """Class indicating there is no model scratch.""" pass class NoDiscreteStates(KModelDiscreteStates): """Class indicating there are no model discrete states.""" pass class NoContinuousStates(KModelContinuousStates): """Class indicating there are no model continuous states.""" pass P = TypeVar("P", bound=KModelParams) Sc = TypeVar("Sc", bound=KModelScratch) S = TypeVar("S", bound=KModelDiscreteStates) C = TypeVar("C", bound=KModelContinuousStates) class KModel(Generic[P, Sc, S, C], PyKModelBase): """Base class for all Python models. This is the base class for all Python models. To create a new model, simply create a class that derives from this one. If the model has parameters, then create a parameter class that derives from `KModelParams`. In addition, add ``` params: MyDerivedParamClass ``` as a class variable to ensure type-hinting works as intended. Users can override the pre/postDeriv, pre/postHop, and pre/PostModelStep to run model functions at various points throught the dynamics. For details on these methods and models, see the model documentation. """ params: P scratch: Sc discrete_states: S continuous_states: C def __init__(self, name: str, sp: "_StatePropagator"): """Create KModel class.""" super().__init__(name, sp)
[docs] def isFinalized(self) -> bool: """Return True if the model's parameters are finalized. False otherwise.""" finalized = True if hasattr(self, "params") and self.params: if not self.params.isFinalized(): finalized = False if self.__class__.preModelStep is not PyKModelBase.preModelStep: from Karana.Core import warn if not self.state_propagator.hasRegisteredTimedEvent( f"{self.name()}_pre_model_step_{self.id()}", True ): warn( "A preModelStep method is defined, but the period is 0 nor does the model have a nextModelStepTime method that returns a Ktime. Therefore, this method will never be run." ) finalized = False if self.__class__.postModelStep is not PyKModelBase.postModelStep: from Karana.Core import warn if not self.state_propagator.hasRegisteredTimedEvent( f"{self.name()}_post_model_step_{self.id()}", False ): warn( "A postModelStep method is defined, but the period is 0 nor does the model have a nextModelStepTime method that returns a Ktime. Therefore, this method will never be run." ) finalized = False return finalized
def _getContinuousStates(self) -> _NDArray[_np.float64]: """Return the continuous states of the KModel. Returns ------- _NDArray[_np.float64] The continuous states of the model. """ return self.continuous_states.getX() def _getContinuousStatesDeriv(self) -> _NDArray[_np.float64]: """Return the continuous states' derivatives of the KModel. Returns ------- _NDArray[_np.float64] The continuous states of the model. """ return self.continuous_states.getdX() def _setContinuousStates(self, x: _NDArray[_np.float64]): """Set the continuous states of the KModel. Parameters ---------- x : _NDArray[_np.float64] Value to set the continuous states to. """ return self.continuous_states.setX(x) def _registerModel(self): # Check that classes are all of the correct type # We do this here, since these are not necessarily set when the constructor runs. if hasattr(self, "params") and ( self.params.__class__ is KModelParams or not isinstance(self.params, KModelParams) ): raise ValueError( "The model params should be derived from KModelParams, but must not be KModelParams itself. Use NoParams if your model does not have any model params." ) if hasattr(self, "scratch") and ( self.scratch.__class__ is KModelScratch or not isinstance(self.scratch, KModelScratch) ): raise ValueError( "The model scratch should be derived from KModelScratch, but must not be KModelScratch itself. Use NoScratch if your model does not have any scratch." ) if hasattr(self, "discrete_states") and ( self.discrete_states.__class__ is KModelDiscreteStates or not isinstance(self.discrete_states, KModelDiscreteStates) ): raise ValueError( "The dicrete model states should be derived from KModelDiscreteStates, but must not be KModelDiscreteStates itself. Use NoDiscreteStates if your model does not have any dicrete states." ) if hasattr(self, "continuous_states") and ( self.continuous_states.__class__ is KModelContinuousStates or not isinstance(self.continuous_states, KModelContinuousStates) ): raise ValueError( "The continuous model states should be derived from KModelContinuousStates, but must not be KModelContinuousStates itself. Use NoContinuousStates if your model does not have any continuous states." ) # Register the model super()._registerModel() # Set the _has_continuous_states value via the _setHasContinuousStates Python-only method. # This must be done in _registerModel, so these states get added to the integrator if they are present. if ( hasattr(self, "continuous_states") and self.continuous_states.__class__ is not NoContinuousStates ): # If we have continuous states, then set the boolean appropriately self._setHasContinuousStates(True) else: self._setHasContinuousStates(False)
[docs] def typeString(self) -> str: """Return the type string (class name) of the KModel. Returns ------- str The class name of the KModel. """ return self.__class__.__name__
[docs] def dumpString(self, prefix: str = "", options: _DumpOptionsBase | None = None) -> str: """Return information about the KModel as a string. Parameters ---------- prefix : str A string to use as prefix for each output line. options : _DumpOptionsBase | None Class with options to tailor the output Returns ------- str Information about the KModel as a string. """ result = f"{prefix}Dumping model '{self.name()}' ({self.typeString()})\n" # If we have params, add those to the dumpString if hasattr(self, "params") and not isinstance(self.params, NoParams): result += f"{prefix} Model Params:\n" result += self.params.dumpString(f"{prefix} ") # If we have scratch, add those to the dumpString if hasattr(self, "scratch") and not isinstance(self.scratch, NoScratch): result += f"{prefix} Model Scratch:\n" result += self.scratch.dumpString(f"{prefix} ") # If we have discrete states, add those to the dumpString if hasattr(self, "discrete_states") and not isinstance( self.discrete_states, NoDiscreteStates ): result += f"{prefix} Model Discrete States:\n" result += self.discrete_states.dumpString(f"{prefix} ") # If we have continuous states, add those to the dumpString if hasattr(self, "continuous_states") and not isinstance( self.continuous_states, NoDiscreteStates ): result += f"{prefix} Model Continuous States:\n" result += self.continuous_states.dumpString(f"{prefix} ") return result