diff --git a/README.md b/README.md index 7163cc60..945ac0b6 100644 --- a/README.md +++ b/README.md @@ -188,15 +188,8 @@ But if you find that this isn't the case, then don't hesitate to open an issue o The entire public API of `scipy` is **fully annotated** and **verifiably valid**. For the most part, this can also be said about `scipy`'s private API and other internal machinery. -### `Untyped` - -A small portion of the stubs uses the `Untyped` type (an alias of `Any`) as a "placeholder" or "to-do" annotation. -In those cases static type-checkers won't do any type-checking, and won't bother you with errors or warnings, so you probably -won't even notice it. -The current goal of `scipy-stubs` is to replace all `Untyped` annotations with more meaningful ones. - -At the moment, out of the 21 `scipy.*` subpackages, the only one that still has (some) `Untyped` annotations, is `scipy.signal`. -See [scipy-stubs#99](https://github.com/jorenham/scipy-stubs/issues/99) for an overview. +Note that this does not mean that all annotations are optimal, and some might even be incorrect. If you encounter this, it would +help a lot if you could open an issue or a PR for it. ## Contributing diff --git a/scipy-stubs/_typing.pyi b/scipy-stubs/_typing.pyi index 80f69192..21de12e4 100644 --- a/scipy-stubs/_typing.pyi +++ b/scipy-stubs/_typing.pyi @@ -1,7 +1,7 @@ # NOTE: This private(!) module only exists in `if typing.TYPE_CHECKING: ...` and in `.pyi` stubs from os import PathLike -from collections.abc import Callable, Sequence +from collections.abc import Sequence from types import TracebackType from typing import IO, Any, Literal, Protocol, TypeAlias, type_check_only from typing_extensions import LiteralString, Self, TypeVar @@ -28,12 +28,6 @@ __all__ = [ "OrderCF", "OrderKACF", "ToRNG", - "Untyped", - "UntypedArray", - "UntypedCallable", - "UntypedDict", - "UntypedList", - "UntypedTuple", "_FortranFunction", ] @@ -63,14 +57,6 @@ class _FortranFunction(Protocol): def typecode(self, /) -> LiteralString: ... def __call__(self, /, *args: object, **kwargs: object) -> object: ... -# placeholders for missing annotations -Untyped: TypeAlias = Any -UntypedTuple: TypeAlias = tuple[Untyped, ...] -UntypedList: TypeAlias = list[Untyped] -UntypedDict: TypeAlias = dict[Untyped, Untyped] -UntypedCallable: TypeAlias = Callable[..., Untyped] -UntypedArray: TypeAlias = onp.Array[Any, np.generic] - # I/O _ByteSOrStr = TypeVar("_ByteSOrStr", bytes, str) FileName: TypeAlias = str | PathLike[str] diff --git a/scipy-stubs/signal/_lti_conversion.pyi b/scipy-stubs/signal/_lti_conversion.pyi index 90f257aa..38e18d44 100644 --- a/scipy-stubs/signal/_lti_conversion.pyi +++ b/scipy-stubs/signal/_lti_conversion.pyi @@ -1,33 +1,78 @@ -from typing import Any, Literal, TypeAlias +from typing import Literal, TypeAlias, TypeVar, overload import numpy as np import optype.numpy as onp -from ._ltisys import lti +import optype.numpy.compat as npc +from ._ltisys import dlti, lti __all__ = ["abcd_normalize", "cont2discrete", "ss2tf", "ss2zpk", "tf2ss", "zpk2ss"] +### + _ToSystemTF: TypeAlias = tuple[onp.ToComplex2D, onp.ToComplex1D] # (num, den) -_InSystemZPK: TypeAlias = tuple[onp.ToComplex1D, onp.ToComplex1D, onp.ToFloat] # (z, p, k) -_InSystemSS: TypeAlias = tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D] # (A, B, C, D) +_ToSystemZPK: TypeAlias = tuple[onp.ToComplex1D, onp.ToComplex1D, onp.ToFloat] # (z, p, k) +_ToSystemSS: TypeAlias = tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D] # (A, B, C, D) -_Inexact1D: TypeAlias = onp.Array1D[np.inexact[Any]] -_Inexact2D: TypeAlias = onp.Array2D[np.inexact[Any]] +_InexactT = TypeVar("_InexactT", bound=npc.inexact, default=npc.inexact) +_Inexact1D: TypeAlias = onp.Array1D[_InexactT] +_Inexact2D: TypeAlias = onp.Array2D[_InexactT] -_SystemTF: TypeAlias = tuple[_Inexact2D, _Inexact1D] -_SystemZPK: TypeAlias = tuple[_Inexact1D, _Inexact1D, float] -_SystemSS: TypeAlias = tuple[_Inexact2D, _Inexact2D, _Inexact2D, _Inexact2D] +_SystemTF: TypeAlias = tuple[_Inexact2D[_InexactT], _Inexact1D[_InexactT]] +_SystemZPK: TypeAlias = tuple[_Inexact1D[_InexactT], _Inexact1D[_InexactT], float | np.float64] +_SystemSS: TypeAlias = tuple[_Inexact2D[_InexactT], _Inexact2D[_InexactT], _Inexact2D[_InexactT], _Inexact2D[_InexactT]] -_Method: TypeAlias = Literal["gbt", "bilinear", "euler", "backward_diff", "foh", "impulse", "zoh"] +_DiscretizeMethod: TypeAlias = Literal["gbt", "bilinear", "euler", "backward_diff", "foh", "impulse", "zoh"] +### +@overload +def abcd_normalize( + A: onp.ToFloat2D | None = None, + B: onp.ToFloat2D | None = None, + C: onp.ToFloat2D | None = None, + D: onp.ToFloat2D | None = None, +) -> _SystemTF[npc.floating]: ... +@overload def abcd_normalize( A: onp.ToComplex2D | None = None, B: onp.ToComplex2D | None = None, C: onp.ToComplex2D | None = None, D: onp.ToComplex2D | None = None, ) -> _SystemTF: ... + +# +@overload +def tf2ss(num: onp.ToFloat2D, den: onp.ToFloat1D) -> _SystemSS[npc.floating]: ... +@overload def tf2ss(num: onp.ToComplex2D, den: onp.ToComplex1D) -> _SystemSS: ... -def zpk2ss(z: onp.ToComplex1D, p: onp.ToComplex1D, k: onp.ToFloat) -> _SystemSS: ... + +# +@overload +def ss2tf( + A: onp.ToFloat2D, + B: onp.ToFloat2D, + C: onp.ToFloat2D, + D: onp.ToFloat2D, + input: onp.ToInt = 0, +) -> _SystemTF[npc.floating]: ... +@overload def ss2tf(A: onp.ToComplex2D, B: onp.ToComplex2D, C: onp.ToComplex2D, D: onp.ToComplex2D, input: onp.ToInt = 0) -> _SystemTF: ... + +# +@overload +def zpk2ss(z: onp.ToFloat1D, p: onp.ToFloat1D, k: onp.ToFloat) -> _SystemSS[npc.floating]: ... +@overload +def zpk2ss(z: onp.ToComplex1D, p: onp.ToComplex1D, k: onp.ToFloat) -> _SystemSS: ... + +# +@overload +def ss2zpk( + A: onp.ToFloat2D, + B: onp.ToFloat2D, + C: onp.ToFloat2D, + D: onp.ToFloat2D, + input: onp.ToInt = 0, +) -> _SystemZPK[npc.floating]: ... +@overload def ss2zpk( A: onp.ToComplex2D, B: onp.ToComplex2D, @@ -35,13 +80,33 @@ def ss2zpk( D: onp.ToComplex2D, input: onp.ToInt = 0, ) -> _SystemZPK: ... + +# TODO: overload on lti subclasses +@overload +def cont2discrete( + system: lti, + dt: float, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, +) -> dlti: ... +@overload +def cont2discrete( + system: _ToSystemTF, + dt: float, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, +) -> tuple[_Inexact2D[_InexactT], _Inexact1D[_InexactT], float]: ... +@overload +def cont2discrete( + system: _ToSystemZPK, + dt: float, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, +) -> tuple[_Inexact1D[_InexactT], _Inexact1D[_InexactT], float, float]: ... +@overload def cont2discrete( - system: lti | _ToSystemTF | _InSystemZPK | _InSystemSS, + system: _ToSystemSS, dt: float, - method: _Method = "zoh", - alpha: onp.ToFloat | None = None, -) -> ( - tuple[_Inexact2D, _Inexact1D, float] - | tuple[_Inexact1D, _Inexact1D, float, float] - | tuple[_Inexact2D, _Inexact2D, _Inexact2D, _Inexact2D, float] -): ... + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, +) -> tuple[_Inexact2D[_InexactT], _Inexact2D[_InexactT], _Inexact2D[_InexactT], _Inexact2D[_InexactT], float]: ... diff --git a/scipy-stubs/signal/_ltisys.pyi b/scipy-stubs/signal/_ltisys.pyi index 1378aba4..49288174 100644 --- a/scipy-stubs/signal/_ltisys.pyi +++ b/scipy-stubs/signal/_ltisys.pyi @@ -1,7 +1,13 @@ -from abc import abstractmethod -from typing_extensions import Self, override +# mypy: disable-error-code="explicit-override" +import abc +from typing import ClassVar, Final, Literal, TypeAlias, final, overload, type_check_only +from typing_extensions import Never, Self, Unpack, override -from scipy._typing import Untyped +import numpy as np +import optype as op +import optype.numpy as onp +import optype.numpy.compat as npc +from ._lti_conversion import _DiscretizeMethod __all__ = [ "StateSpace", @@ -22,132 +28,550 @@ __all__ = [ "step", ] +### + +# numerator, denominator +_ToTFContReal: TypeAlias = tuple[onp.ToFloat1D | onp.ToFloat2D, onp.ToComplex1D] +_ToTFContComplex: TypeAlias = tuple[onp.ToComplex1D | onp.ToComplex2D, onp.ToComplex1D] +# numerator, denominator, dt +_ToTFDiscReal: TypeAlias = tuple[onp.ToFloat1D | onp.ToFloat2D, onp.ToComplex1D, onp.ToFloat] + +# zeros, poles, gain +_ToZPKContReal: TypeAlias = tuple[onp.ToFloat1D, onp.ToFloat1D, onp.ToFloat] +_ToZPKContComplex: TypeAlias = tuple[onp.ToComplex1D, onp.ToComplex1D, onp.ToFloat] +# zeros, poles, gain, dt +_ToZPKDiscReal: TypeAlias = tuple[onp.ToFloat1D, onp.ToFloat1D, onp.ToFloat, onp.ToFloat] + +# A, B, C, D +_ToSSContReal: TypeAlias = tuple[onp.ToFloat2D, onp.ToFloat2D, onp.ToFloat2D, onp.ToFloat2D] +_ToSSContComplex: TypeAlias = tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D] +# A, B, C, D, dt +_ToSSDiscReal: TypeAlias = tuple[onp.ToFloat2D, onp.ToFloat2D, onp.ToFloat2D, onp.ToFloat2D, onp.ToFloat] + +_ToLTIReal: TypeAlias = _ToTFContReal | _ToZPKContReal | _ToSSContReal +_ToLTIComplex: TypeAlias = _ToTFContComplex | _ToZPKContComplex | _ToSSContComplex +_ToDLTI: TypeAlias = _ToTFDiscReal | _ToZPKDiscReal | _ToSSDiscReal + +### + +# TODO(jorenham): Generic scalar type class LinearTimeInvariant: - inputs: int - outputs: int - @abstractmethod - def __new__(cls, *system: Untyped, **kwargs: Untyped) -> Self: ... + inputs: Final[int] + outputs: Final[int] + + def __new__(cls, *system: Never, **kwargs: Never) -> Self: ... + + # + @abc.abstractmethod + @type_check_only + def to_tf(self, /) -> TransferFunction: ... + @abc.abstractmethod + @type_check_only + def to_zpk(self, /) -> ZerosPolesGain: ... + @abc.abstractmethod + @type_check_only + def to_ss(self, /) -> StateSpace: ... + + # @property - def dt(self, /) -> Untyped: ... + def dt(self, /) -> float | None: ... @property - def zeros(self, /) -> Untyped: ... + def zeros(self, /) -> onp.Array1D[npc.number] | onp.Array2D[npc.number]: ... + @property + def poles(self, /) -> onp.Array1D[npc.number]: ... + +class lti(LinearTimeInvariant, metaclass=abc.ABCMeta): + @overload + def __new__(cls, *system: Unpack[tuple[onp.ToFloat1D | onp.ToFloat2D, onp.ToFloat1D]]) -> TransferFunctionContinuous: ... + @overload + def __new__(cls, *system: Unpack[tuple[onp.ToComplex1D, onp.ToComplex1D, onp.ToFloat]]) -> ZerosPolesGainContinuous: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D]], + ) -> StateSpaceContinuous: ... + + # + def __init__(self, /, *system: Never) -> None: ... + + # + def impulse( + self, + /, + X0: onp.ToFloat1D | None = None, + T: onp.ToFloat1D | None = None, + N: onp.ToJustInt | None = None, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + def step( + self, + /, + X0: onp.ToComplex1D | None = None, + T: onp.ToFloat1D | None = None, + N: onp.ToJustInt | None = None, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.inexact]]: ... + def output( + self, + /, + U: onp.ToFloat1D | onp.ToFloat2D | onp.ToFloat | None, + T: onp.ToFloat1D, + X0: onp.ToComplex1D | None = None, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.inexact], onp.Array1D[npc.inexact] | onp.Array2D[npc.inexact]]: ... + def bode( + self, + /, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 100, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + def freqresp( + self, + /, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 10_000, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.complexfloating]]: ... + + # + @abc.abstractmethod + def to_discrete( + self, + /, + dt: onp.ToFloat, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, + ) -> dlti: ... + +class dlti(LinearTimeInvariant, metaclass=abc.ABCMeta): + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToFloat1D | onp.ToFloat2D, onp.ToFloat1D]], + dt: onp.ToFloat = True, + ) -> TransferFunctionDiscrete: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex1D, onp.ToComplex1D, onp.ToFloat]], + dt: onp.ToFloat = True, + ) -> ZerosPolesGainDiscrete: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D]], + dt: onp.ToFloat = True, + ) -> StateSpaceDiscrete: ... + + # + def __init__(self, /, *system: Never, dt: onp.ToFloat, **kwargs: Never) -> None: ... + + # @property - def poles(self, /) -> Untyped: ... - -class lti(LinearTimeInvariant): - def __new__(cls, *system: Untyped) -> Self: ... - def __init__(self, /, *system: Untyped) -> None: ... - def impulse(self, /, X0: Untyped | None = None, T: Untyped | None = None, N: Untyped | None = None) -> Untyped: ... - def step(self, /, X0: Untyped | None = None, T: Untyped | None = None, N: Untyped | None = None) -> Untyped: ... - def output(self, /, U: Untyped, T: Untyped, X0: Untyped | None = None) -> Untyped: ... - def bode(self, /, w: Untyped | None = None, n: int = 100) -> Untyped: ... - def freqresp(self, /, w: Untyped | None = None, n: int = 10000) -> Untyped: ... - def to_discrete(self, /, dt: Untyped, method: str = "zoh", alpha: Untyped | None = None) -> Untyped: ... - -class dlti(LinearTimeInvariant): - def __new__(cls, *system: Untyped, **kwargs: Untyped) -> Self: ... - def __init__(self, /, *system: Untyped, **kwargs: Untyped) -> None: ... - def impulse(self, /, x0: Untyped | None = None, t: Untyped | None = None, n: Untyped | None = None) -> Untyped: ... - def step(self, /, x0: Untyped | None = None, t: Untyped | None = None, n: Untyped | None = None) -> Untyped: ... - def output(self, /, u: Untyped, t: Untyped, x0: Untyped | None = None) -> Untyped: ... - def bode(self, /, w: Untyped | None = None, n: int = 100) -> Untyped: ... - def freqresp(self, /, w: Untyped | None = None, n: int = 10000, whole: bool = False) -> Untyped: ... + @override + def dt(self, /) -> float: ... + + # + def impulse( + self, + /, + x0: onp.ToFloat1D | None = None, + t: onp.ToFloat1D | None = None, + n: onp.ToJustInt | None = None, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + def step( + self, + /, + x0: onp.ToFloat1D | None = None, + t: onp.ToFloat1D | None = None, + n: onp.ToJustInt | None = None, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + def output( + self, + /, + u: onp.ToFloat1D | onp.ToFloat2D | onp.ToFloat | None, + t: onp.ToFloat1D, + x0: onp.ToFloat1D | None = None, + ) -> tuple[onp.Array1D[np.float64], onp.Array1D[np.float64]]: ... + def bode( + self, + /, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 100, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + def freqresp( + self, + /, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 10_000, + whole: op.CanBool = False, + ) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.complexfloating]]: ... class TransferFunction(LinearTimeInvariant): - def __new__(cls, *system: Untyped, **kwargs: Untyped) -> Self: ... - def __init__(self, /, *system: Untyped, **kwargs: Untyped) -> None: ... + @overload + def __new__(cls, *system: Unpack[tuple[lti]]) -> TransferFunctionContinuous: ... + @overload + def __new__(cls, *system: Unpack[tuple[dlti]]) -> TransferFunctionDiscrete: ... + @overload + def __new__(cls, *system: Unpack[tuple[onp.ToFloat1D | onp.ToFloat2D, onp.ToFloat1D]]) -> TransferFunctionContinuous: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToFloat1D | onp.ToFloat2D, onp.ToFloat1D]], + dt: onp.ToFloat, + ) -> TransferFunctionDiscrete: ... + + # + @overload + def __init__(self, system: LinearTimeInvariant, /) -> None: ... + @overload + def __init__(self, numerator: onp.ToFloat1D | onp.ToFloat2D, denominator: onp.ToFloat1D, /) -> None: ... + + # @property - def num(self, /) -> Untyped: ... + def num(self, /) -> onp.Array1D[npc.number] | onp.Array2D[npc.number]: ... @num.setter - def num(self, /, num: Untyped) -> None: ... + def num(self, /, num: onp.ToComplex1D | onp.ToComplex2D) -> None: ... + + # @property - def den(self, /) -> Untyped: ... + def den(self, /) -> onp.Array1D[npc.number]: ... @den.setter - def den(self, /, den: Untyped) -> None: ... - def to_tf(self, /) -> Untyped: ... - def to_zpk(self, /) -> Untyped: ... - def to_ss(self, /) -> Untyped: ... + def den(self, /, den: onp.ToComplex1D) -> None: ... + # + @override + def to_tf(self, /) -> Self: ... + @override + def to_zpk(self, /) -> ZerosPolesGain: ... + @override + def to_ss(self, /) -> StateSpace: ... + +@final class TransferFunctionContinuous(TransferFunction, lti): @override - def to_discrete(self, /, dt: Untyped, method: str = "zoh", alpha: Untyped | None = None) -> Untyped: ... + def to_discrete( + self, + /, + dt: onp.ToFloat, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, + ) -> TransferFunctionDiscrete: ... -class TransferFunctionDiscrete(TransferFunction, dlti): ... +@final +class TransferFunctionDiscrete(TransferFunction, dlti): + @overload + def __init__(self, system: LinearTimeInvariant, /) -> None: ... + @overload + def __init__( + self, + numerator: onp.ToFloat1D | onp.ToFloat2D, + denominator: onp.ToFloat1D, + /, + *, + dt: onp.ToFloat = ..., + ) -> None: ... class ZerosPolesGain(LinearTimeInvariant): - def __new__(cls, *system: Untyped, **kwargs: Untyped) -> Self: ... - def __init__(self, /, *system: Untyped, **kwargs: Untyped) -> None: ... + @overload + def __new__(cls, *system: Unpack[tuple[lti]]) -> ZerosPolesGainContinuous: ... + @overload + def __new__(cls, *system: Unpack[tuple[dlti]]) -> ZerosPolesGainDiscrete: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex1D | onp.ToComplex2D, onp.ToComplex1D, onp.ToFloat]], + ) -> ZerosPolesGainContinuous: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex1D | onp.ToComplex2D, onp.ToComplex1D, onp.ToFloat]], + dt: onp.ToFloat, + ) -> ZerosPolesGainDiscrete: ... + + # + @overload + def __init__(self, system: LinearTimeInvariant, /) -> None: ... + @overload + def __init__(self, zeros: onp.ToComplex1D | onp.ToComplex2D, poles: onp.ToComplex1D, gain: onp.ToFloat, /) -> None: ... + + # @property - def gain(self, /) -> Untyped: ... + @override + def zeros(self, /) -> onp.Array1D[npc.number] | onp.Array2D[npc.number]: ... + @zeros.setter + def zeros(self, zeros: onp.ToComplex1D | onp.ToComplex2D, /) -> None: ... + + # + @property + @override + def poles(self, /) -> onp.Array1D[npc.number]: ... + @poles.setter + def poles(self, gain: onp.ToComplex1D, /) -> None: ... + + # + @property + def gain(self, /) -> float: ... @gain.setter - def gain(self, /, gain: Untyped) -> None: ... - def to_tf(self, /) -> Untyped: ... - def to_zpk(self, /) -> Untyped: ... - def to_ss(self, /) -> Untyped: ... + def gain(self, gain: float, /) -> None: ... + + # + @override + def to_tf(self, /) -> TransferFunction: ... + @override + def to_zpk(self, /) -> Self: ... + @override + def to_ss(self, /) -> StateSpace: ... +@final class ZerosPolesGainContinuous(ZerosPolesGain, lti): @override - def to_discrete(self, /, dt: Untyped, method: str = "zoh", alpha: Untyped | None = None) -> Untyped: ... + def to_discrete( + self, + /, + dt: onp.ToFloat, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, + ) -> ZerosPolesGainDiscrete: ... -class ZerosPolesGainDiscrete(ZerosPolesGain, dlti): ... +@final +class ZerosPolesGainDiscrete(ZerosPolesGain, dlti): + @overload + def __init__(self, system: ZerosPolesGain, /) -> None: ... + @overload + def __init__( + self, + zeros: onp.ToComplex1D | onp.ToComplex2D, + poles: onp.ToComplex1D, + gain: onp.ToFloat, + /, + *, + dt: onp.ToFloat = ..., + ) -> None: ... class StateSpace(LinearTimeInvariant): - __array_priority__: float - __array_ufunc__: Untyped - def __new__(cls, *system: Untyped, **kwargs: Untyped) -> Self: ... - def __init__(self, /, *system: Untyped, **kwargs: Untyped) -> None: ... - def __mul__(self, /, other: Untyped) -> Untyped: ... - def __rmul__(self, /, other: Untyped) -> Untyped: ... - def __neg__(self, /) -> Untyped: ... - def __add__(self, /, other: Untyped) -> Untyped: ... - def __sub__(self, /, other: Untyped) -> Untyped: ... - def __radd__(self, /, other: Untyped) -> Untyped: ... - def __rsub__(self, /, other: Untyped) -> Untyped: ... - def __truediv__(self, /, other: Untyped) -> Untyped: ... + __array_priority__: ClassVar[float] = 100.0 + __array_ufunc__: ClassVar[None] = None + + @overload + def __new__(cls, *system: Unpack[tuple[lti]]) -> StateSpaceContinuous: ... + @overload + def __new__(cls, *system: Unpack[tuple[dlti]]) -> StateSpaceDiscrete: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D]], + ) -> StateSpaceContinuous: ... + @overload + def __new__( + cls, + *system: Unpack[tuple[onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D, onp.ToComplex2D]], + dt: onp.ToFloat, + ) -> StateSpaceDiscrete: ... + + # + @overload + def __init__(self, system: StateSpace, /) -> None: ... + @overload + def __init__( + self, + A: onp.ToComplex2D, + B: onp.ToComplex2D, + C: onp.ToComplex2D, + D: onp.ToComplex2D, + /, + ) -> None: ... + + # + def __neg__(self, /) -> Self: ... + def __add__(self, other: Self | complex | npc.number | onp.ArrayND[npc.number], /) -> Self: ... + def __sub__(self, other: Self | complex | npc.number | onp.ArrayND[npc.number], /) -> Self: ... + def __mul__(self, other: Self | complex | npc.number | onp.ArrayND[npc.number], /) -> Self: ... + def __truediv__(self, other: complex | npc.number, /) -> Self: ... + # ehh mypy, u ok? + def __radd__(self, other: complex | npc.number | onp.ArrayND[npc.number], /) -> Self: ... # type: ignore[misc] + def __rsub__(self, other: complex | npc.number | onp.ArrayND[npc.number], /) -> Self: ... # type: ignore[misc] + def __rmul__(self, other: complex | npc.number | onp.ArrayND[npc.number], /) -> Self: ... # type: ignore[misc] + + # @property - def A(self, /) -> Untyped: ... + def A(self, /) -> onp.Array2D[npc.number]: ... @A.setter - def A(self, /, A: Untyped) -> None: ... + def A(self, /, A: onp.ToComplex2D) -> None: ... + # @property - def B(self, /) -> Untyped: ... + def B(self, /) -> onp.Array2D[npc.number]: ... @B.setter - def B(self, /, B: Untyped) -> None: ... + def B(self, /, B: onp.ToComplex2D) -> None: ... + # @property - def C(self, /) -> Untyped: ... + def C(self, /) -> onp.Array2D[npc.number]: ... @C.setter - def C(self, /, C: Untyped) -> None: ... + def C(self, /, C: onp.ToComplex2D) -> None: ... + # @property - def D(self, /) -> Untyped: ... + def D(self, /) -> onp.Array2D[npc.number]: ... @D.setter - def D(self, /, D: Untyped) -> None: ... - def to_tf(self, /, **kwargs: Untyped) -> Untyped: ... - def to_zpk(self, /, **kwargs: Untyped) -> Untyped: ... - def to_ss(self, /) -> Untyped: ... + def D(self, /, D: onp.ToComplex2D) -> None: ... + # + @override + def to_tf(self, /, *, input: onp.ToInt = 0) -> TransferFunction: ... + @override + def to_zpk(self, /, *, input: onp.ToInt = 0) -> ZerosPolesGain: ... + @override + def to_ss(self, /) -> Self: ... +@final class StateSpaceContinuous(StateSpace, lti): @override - def to_discrete(self, /, dt: Untyped, method: str = "zoh", alpha: Untyped | None = None) -> Untyped: ... + def to_discrete( + self, + /, + dt: onp.ToFloat, + method: _DiscretizeMethod = "zoh", + alpha: onp.ToJustFloat | None = None, + ) -> StateSpaceDiscrete: ... + +@final +class StateSpaceDiscrete(StateSpace, dlti): + @overload + def __init__(self, system: StateSpace, /) -> None: ... + @overload + def __init__( + self, + A: onp.ToComplex2D, + B: onp.ToComplex2D, + C: onp.ToComplex2D, + D: onp.ToComplex2D, + /, + *, + dt: onp.ToFloat = ..., + ) -> None: ... +# NOTE: Only used as return type of `place_poles`. class Bunch: - def __init__(self, /, **kwds: Untyped) -> None: ... + gain_matrix: onp.Array2D[np.float64] + computed_poles: onp.Array1D[np.float64] + requested_poles: onp.Array1D[np.float64] + X: onp.Array2D[np.complex128] + rtol: float + nb_iter: int -class StateSpaceDiscrete(StateSpace, dlti): ... + def __init__(self, /, **kwds: Never) -> None: ... -def lsim(system: Untyped, U: Untyped, T: Untyped, X0: Untyped | None = None, interp: bool = True) -> Untyped: ... -def impulse(system: Untyped, X0: Untyped | None = None, T: Untyped | None = None, N: Untyped | None = None) -> Untyped: ... -def step(system: Untyped, X0: Untyped | None = None, T: Untyped | None = None, N: Untyped | None = None) -> Untyped: ... -def bode(system: Untyped, w: Untyped | None = None, n: int = 100) -> Untyped: ... -def freqresp(system: Untyped, w: Untyped | None = None, n: int = 10000) -> Untyped: ... +# def place_poles( - A: Untyped, - B: Untyped, - poles: Untyped, - method: str = "YT", + A: onp.ToFloat2D, + B: onp.ToFloat2D, + poles: onp.ToComplex1D, + method: Literal["YT", "KNV0"] = "YT", rtol: float = 0.001, maxiter: int = 30, -) -> Untyped: ... -def dlsim(system: Untyped, u: Untyped, t: Untyped | None = None, x0: Untyped | None = None) -> Untyped: ... -def dimpulse(system: Untyped, x0: Untyped | None = None, t: Untyped | None = None, n: Untyped | None = None) -> Untyped: ... -def dstep(system: Untyped, x0: Untyped | None = None, t: Untyped | None = None, n: Untyped | None = None) -> Untyped: ... -def dfreqresp(system: Untyped, w: Untyped | None = None, n: int = 10000, whole: bool = False) -> Untyped: ... -def dbode(system: Untyped, w: Untyped | None = None, n: int = 100) -> Untyped: ... +) -> Bunch: ... + +# +@overload +def lsim( + system: _ToLTIReal, + U: onp.ToFloat1D | onp.ToFloat2D | onp.ToFloat | None, + T: onp.ToFloat1D, + X0: onp.ToFloat1D | None = None, + interp: op.CanBool = True, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating], onp.Array1D[npc.floating] | onp.Array2D[npc.floating]]: ... +@overload +def lsim( + system: lti | _ToLTIComplex, + U: onp.ToFloat1D | onp.ToFloat2D | onp.ToFloat | None, + T: onp.ToFloat1D, + X0: onp.ToComplex1D | None = None, + interp: op.CanBool = True, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.inexact], onp.Array1D[npc.inexact] | onp.Array2D[npc.inexact]]: ... + +# +@overload +def dlsim( + system: StateSpaceDiscrete, + u: onp.ToFloat1D | onp.ToFloat2D | onp.ToFloat | None, + t: onp.ToFloat1D | None = None, + x0: onp.ToFloat1D | None = None, +) -> tuple[onp.Array1D[np.float64], onp.Array1D[np.float64], onp.Array1D[np.float64]]: ... +@overload +def dlsim( + system: _ToDLTI, + u: onp.ToFloat1D | onp.ToFloat2D | onp.ToFloat | None, + t: onp.ToFloat1D | None = None, + x0: onp.ToFloat1D | None = None, +) -> tuple[onp.Array1D[np.float64], onp.Array1D[np.float64]]: ... + +# +@overload +def impulse( + system: _ToLTIReal, + X0: onp.ToFloat1D | None = None, + T: onp.ToFloat1D | None = None, + N: onp.ToJustInt | None = None, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... +@overload +def impulse( + system: lti | _ToLTIComplex, + X0: onp.ToComplex1D | None = None, + T: onp.ToFloat1D | None = None, + N: onp.ToJustInt | None = None, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.inexact]]: ... + +# +def dimpulse( + system: dlti | _ToDLTI, + x0: onp.ToFloat1D | None = None, + t: onp.ToFloat1D | None = None, + n: onp.ToJustInt | None = None, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + +# +@overload +def step( + system: _ToLTIReal, + X0: onp.ToFloat1D | None = None, + T: onp.ToFloat1D | None = None, + N: onp.ToJustInt | None = None, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... +@overload +def step( + system: lti | _ToLTIComplex, + X0: onp.ToComplex1D | None = None, + T: onp.ToFloat1D | None = None, + N: onp.ToJustInt | None = None, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.inexact]]: ... + +# +def dstep( + system: dlti | _ToDLTI, + x0: onp.ToFloat1D | None = None, + t: onp.ToFloat1D | None = None, + n: onp.ToJustInt | None = None, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + +# +def bode( + system: lti | _ToLTIComplex, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 100, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + +# +def dbode( + system: dlti | _ToDLTI, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 100, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.floating], onp.Array1D[npc.floating]]: ... + +# +def freqresp( + system: lti | _ToLTIComplex, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 10_000, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.complexfloating]]: ... + +# +def dfreqresp( + system: dlti | _ToDLTI, + w: onp.ToFloat1D | None = None, + n: onp.ToJustInt = 10_000, + whole: op.CanBool = False, +) -> tuple[onp.Array1D[npc.floating], onp.Array1D[npc.complexfloating]]: ...