Core Module
Core utilities and constants for the Tracker Component Library.
This module provides foundational functionality used throughout the library: - Physical and mathematical constants - Input validation utilities - Array manipulation helpers compatible with MATLAB conventions - Custom exception hierarchy for consistent error handling - Optional dependency management - Module maturity classification system
Constants
Physical and mathematical constants used throughout the Tracker Component Library.
This module provides standardized values for physical constants, with references to their sources. Constants are provided as module-level variables for convenience and as a PhysicalConstants class for documentation and grouping.
References
NIST CODATA 2018 - https://physics.nist.gov/cuu/Constants/
IERS Conventions (2010) - IERS Technical Note 36
WGS84 - https://earth-info.nga.mil/GandG/update/index.php?dir=wgs84&action=wgs84
- pytcl.core.constants.GRAVITATIONAL_CONSTANT: Final[float] = 6.6743e-11
Newtonian gravitational constant [m^3 kg^-1 s^-2]
- pytcl.core.constants.STEFAN_BOLTZMANN_CONSTANT: Final[float] = 5.670374419e-08
Stefan-Boltzmann constant [W m^-2 K^-4]
- pytcl.core.constants.UNIVERSAL_GAS_CONSTANT: Final[float] = 8.314462618
Universal gas constant [J mol^-1 K^-1]
- pytcl.core.constants.STANDARD_ATMOSPHERE: Final[float] = 101325.0
Standard atmosphere pressure [Pa]
- pytcl.core.constants.EARTH_SEMI_MAJOR_AXIS: Final[float] = 6378137.0
Semi-major axis (equatorial radius) [m]
- pytcl.core.constants.EARTH_SEMI_MINOR_AXIS: Final[float] = 6356752.314245
Semi-minor axis (polar radius) [m]
- pytcl.core.constants.EARTH_FLATTENING: Final[float] = 0.0033528106647474805
Flattening factor (dimensionless)
- pytcl.core.constants.EARTH_ECCENTRICITY_SQ: Final[float] = 0.0066943799901413165
First eccentricity squared
- pytcl.core.constants.EARTH_ECCENTRICITY_PRIME_SQ: Final[float] = 0.006739496742276434
Second eccentricity squared
- pytcl.core.constants.EARTH_ROTATION_RATE: Final[float] = 7.292115e-05
Earth rotation rate [rad/s] (IERS Conventions 2010)
- pytcl.core.constants.EARTH_GM: Final[float] = 398600441800000.0
Earth’s gravitational parameter GM [m^3/s^2] (WGS84)
- pytcl.core.constants.EARTH_GM_EGM2008: Final[float] = 398600441500000.0
Earth’s gravitational parameter GM [m^3/s^2] (EGM2008, includes atmosphere)
- pytcl.core.constants.EARTH_MEAN_ANGULAR_VELOCITY: Final[float] = 7.2921151467e-05
Mean angular velocity of Earth [rad/s]
- pytcl.core.constants.EARTH_MEAN_RADIUS: Final[float] = 6371000.0
Nominal mean Earth radius [m] (IUGG)
- pytcl.core.constants.STANDARD_GRAVITY: Final[float] = 9.80665
Standard gravitational acceleration at sea level [m/s^2]
- pytcl.core.constants.SECONDS_PER_JULIAN_CENTURY: Final[float] = 3155760000.0
Seconds per Julian century
- pytcl.core.constants.DEG_TO_RAD: Final[float] = 0.017453292519943295
Degrees to radians conversion factor
- pytcl.core.constants.RAD_TO_DEG: Final[float] = 57.29577951308232
Radians to degrees conversion factor
- pytcl.core.constants.ARCSEC_TO_RAD: Final[float] = 4.84813681109536e-06
Arcseconds to radians conversion factor
- pytcl.core.constants.RAD_TO_ARCSEC: Final[float] = 206264.80624709636
Radians to arcseconds conversion factor
- class pytcl.core.constants.EllipsoidParameters(a, f, GM, omega, name='Custom')[source]
Bases:
objectParameters defining a reference ellipsoid.
- Variables:
a (float) – Semi-major axis (equatorial radius) [m]
f (float) – Flattening factor (dimensionless)
GM (float) – Gravitational parameter [m^3/s^2]
omega (float) – Angular rotation rate [rad/s]
name (str) – Name of the ellipsoid
Properties
----------
b (float) – Semi-minor axis (polar radius) [m]
e2 (float) – First eccentricity squared
e (float) – First eccentricity
ep2 (float) – Second eccentricity squared
- __init__(a, f, GM, omega, name='Custom')
- class pytcl.core.constants.PhysicalConstants(c=299792458.0, G=6.6743e-11, h=6.62607015e-34, k_B=1.380649e-23, sigma=5.670374419e-08, e=1.602176634e-19, N_A=6.02214076e+23, R=8.314462618, g_0=9.80665)[source]
Bases:
objectContainer for fundamental physical constants.
This class groups physical constants for convenient access and documentation. All values follow CODATA 2018 recommendations.
Examples
>>> from pytcl.core.constants import PhysicalConstants >>> pc = PhysicalConstants() >>> print(f"Speed of light: {pc.c} m/s") Speed of light: 299792458.0 m/s
- __init__(c=299792458.0, G=6.6743e-11, h=6.62607015e-34, k_B=1.380649e-23, sigma=5.670374419e-08, e=1.602176634e-19, N_A=6.02214076e+23, R=8.314462618, g_0=9.80665)
- pytcl.core.constants.WGS84: Final[EllipsoidParameters] = EllipsoidParameters(a=6378137.0, f=0.0033528106647474805, GM=398600441800000.0, omega=7.292115e-05, name='WGS84')
WGS84 ellipsoid parameters
- pytcl.core.constants.GRS80: Final[EllipsoidParameters] = EllipsoidParameters(a=6378137.0, f=0.003352810681182319, GM=398600500000000.0, omega=7.292115e-05, name='GRS80')
GRS80 ellipsoid parameters
- pytcl.core.constants.CLARKE1866: Final[EllipsoidParameters] = EllipsoidParameters(a=6378206.4, f=0.0033900753039276207, GM=398600500000000.0, omega=7.292115e-05, name='Clarke1866')
Clarke 1866 ellipsoid (NAD27)
- pytcl.core.constants.SPHERE_EARTH: Final[EllipsoidParameters] = EllipsoidParameters(a=6371000.0, f=0.0, GM=398600441800000.0, omega=7.292115e-05, name='Sphere')
Sphere with Earth mean radius
- pytcl.core.constants.ASTRONOMICAL_UNIT: Final[float] = 149597870700.0
Astronomical Unit [m] (IAU 2012)
- pytcl.core.constants.SUN_GM: Final[float] = 1.32712440018e+20
Sun gravitational parameter [m^3/s^2]
- pytcl.core.constants.MOON_GM: Final[float] = 4902800118000.0
Moon gravitational parameter [m^3/s^2] (DE430/GRAIL, 4902.800118 km^3/s^2)
Must stay consistent with
EARTH_GM / EARTH_MOON_MASS_RATIO. The previous value, 4.9028695e12, disagreed with that quotient and with DE430 by 1.4e-5 relative, and with the valuepytcl.gravity.tideswas independently using.
- pytcl.core.constants.c = 299792458.0
Alias for SPEED_OF_LIGHT
- pytcl.core.constants.G = 6.6743e-11
Alias for GRAVITATIONAL_CONSTANT
Array Utilities
Array utility functions for the Tracker Component Library.
This module provides array manipulation functions that mirror MATLAB behavior, making it easier to port algorithms while maintaining Pythonic interfaces.
- pytcl.core.array_utils.wrap_to_pi(angle)[source]
Wrap angles to the interval [-π, π).
This is equivalent to MATLAB’s wrapToPi function.
- Parameters:
angle (array_like) – Angle(s) in radians.
- Returns:
Angle(s) wrapped to [-π, π).
- Return type:
NDArray
Examples
>>> wrap_to_pi(3 * np.pi) -3.141592653589793
>>> wrap_to_pi([-4, -3, -2, -1, 0, 1, 2, 3, 4]) array([ 2.28318531, -3. , -2. , -1. , 0. , 1. , 2. , 3. , -2.28318531])
- pytcl.core.array_utils.wrap_to_2pi(angle)[source]
Wrap angles to the interval [0, 2π).
This is equivalent to MATLAB’s wrapTo2Pi function.
- Parameters:
angle (array_like) – Angle(s) in radians.
- Returns:
Angle(s) wrapped to [0, 2π).
- Return type:
NDArray
Examples
>>> wrap_to_2pi(-np.pi/2) 4.71238898038469
>>> wrap_to_2pi(3 * np.pi) 3.141592653589793
- pytcl.core.array_utils.wrap_to_range(value, low, high)[source]
Wrap values to a specified interval [low, high).
- Parameters:
- Returns:
Value(s) wrapped to [low, high).
- Return type:
NDArray
Examples
>>> wrap_to_range(370, 0, 360) 10.0
>>> wrap_to_range(-10, 0, 360) 350.0
- pytcl.core.array_utils.wrap_to_pm180(angle)[source]
Wrap angles in degrees to the interval [-180, 180).
- Parameters:
angle (array_like) – Angle(s) in degrees.
- Returns:
Angle(s) wrapped to [-180, 180) degrees.
- Return type:
NDArray
Examples
>>> wrap_to_pm180(270) -90.0
- pytcl.core.array_utils.wrap_to_360(angle)[source]
Wrap angles in degrees to the interval [0, 360).
- Parameters:
angle (array_like) – Angle(s) in degrees.
- Returns:
Angle(s) wrapped to [0, 360) degrees.
- Return type:
NDArray
Examples
>>> wrap_to_360(-90) 270.0
- pytcl.core.array_utils.column_vector(arr)[source]
Convert an array-like to a column vector (n, 1).
- Parameters:
arr (array_like) – Input array.
- Returns:
Column vector with shape (n, 1).
- Return type:
NDArray
Examples
>>> column_vector([1, 2, 3]) array([[1], [2], [3]])
>>> column_vector([[1, 2, 3]]) array([[1], [2], [3]])
- pytcl.core.array_utils.row_vector(arr)[source]
Convert an array-like to a row vector (1, n).
- Parameters:
arr (array_like) – Input array.
- Returns:
Row vector with shape (1, n).
- Return type:
NDArray
Examples
>>> row_vector([1, 2, 3]) array([[1, 2, 3]])
>>> row_vector([[1], [2], [3]]) array([[1, 2, 3]])
- pytcl.core.array_utils.vec(arr, order='F')[source]
Vectorize a matrix (stack columns or rows into a single column).
This mirrors MATLAB’s vec operator which stacks columns.
- Parameters:
arr (array_like) – Input matrix.
order ({'F', 'C'}, optional) – ‘F’ (default): Stack columns (MATLAB-style, column-major). ‘C’: Stack rows (row-major).
- Returns:
Column vector with shape (m*n, 1).
- Return type:
NDArray
Examples
>>> A = np.array([[1, 2], [3, 4]]) >>> vec(A) # Stack columns: [1, 3, 2, 4] array([[1], [3], [2], [4]])
>>> vec(A, order='C') # Stack rows: [1, 2, 3, 4] array([[1], [2], [3], [4]])
- pytcl.core.array_utils.unvec(v, shape, order='F')[source]
Reshape a vector back into a matrix.
Inverse of the vec operation.
- Parameters:
- Returns:
Matrix with specified shape.
- Return type:
NDArray
Examples
>>> import numpy as np >>> from pytcl.core.array_utils import unvec >>> v = np.array([1, 2, 3, 4, 5, 6]) >>> M = unvec(v, (2, 3)) >>> M array([[1, 3, 5], [2, 4, 6]])
- pytcl.core.array_utils.block_diag(*arrays)[source]
Create a block diagonal matrix from provided arrays.
Equivalent to MATLAB’s blkdiag function.
- Parameters:
*arrays (array_like) – Input arrays to place on the diagonal.
- Returns:
Block diagonal matrix.
- Return type:
NDArray
Examples
>>> A = np.array([[1, 2], [3, 4]]) >>> B = np.array([[5]]) >>> block_diag(A, B) array([[1, 2, 0], [3, 4, 0], [0, 0, 5]])
- pytcl.core.array_utils.skew_symmetric(v)[source]
Create a 3x3 skew-symmetric matrix from a 3D vector.
The skew-symmetric matrix [v]× satisfies: [v]× @ u = v × u (cross product).
- Parameters:
v (array_like) – 3-element vector.
- Returns:
3x3 skew-symmetric matrix.
- Return type:
NDArray
Examples
>>> v = [1, 2, 3] >>> S = skew_symmetric(v) >>> S array([[ 0., -3., 2.], [ 3., 0., -1.], [-2., 1., 0.]])
>>> u = [4, 5, 6] >>> np.allclose(S @ u, np.cross(v, u)) True
- pytcl.core.array_utils.unskew(S)[source]
Extract the vector from a 3x3 skew-symmetric matrix.
Inverse of skew_symmetric.
- Parameters:
S (array_like) – 3x3 skew-symmetric matrix.
- Returns:
3-element vector.
- Return type:
NDArray
Examples
>>> import numpy as np >>> from pytcl.core.array_utils import unskew, skew_symmetric >>> v = np.array([1, 2, 3]) >>> S = skew_symmetric(v) >>> v_recovered = unskew(S) >>> np.allclose(v, v_recovered) True
- pytcl.core.array_utils.normalize_vector(v, axis=None, return_norm=False)[source]
Normalize vector(s) to unit length.
- Parameters:
- Returns:
v_normalized (NDArray) – Unit vector(s).
norm (float or NDArray, optional) – Original norm(s), only returned if return_norm=True. A scalar float when
vis 1-D andaxisis None, otherwise an array.
- Return type:
ndarray[tuple[Any, …], dtype[floating[Any]]] | tuple[ndarray[tuple[Any, …], dtype[floating[Any]]], float | ndarray[tuple[Any, …], dtype[floating[Any]]]]
Examples
>>> normalize_vector([3, 4]) array([0.6, 0.8])
>>> v_unit, norm = normalize_vector([3, 4], return_norm=True) >>> norm 5.0
- pytcl.core.array_utils.outer_product(a, b)[source]
Compute the outer product of two vectors.
- Parameters:
a (array_like) – First vector (m,).
b (array_like) – Second vector (n,).
- Returns:
Outer product matrix (m, n).
- Return type:
NDArray
Examples
>>> outer_product([1, 2], [3, 4, 5]) array([[ 3, 4, 5], [ 6, 8, 10]])
- pytcl.core.array_utils.repmat(arr, m, n)[source]
Replicate and tile an array.
Equivalent to MATLAB’s repmat function.
- Parameters:
- Returns:
Tiled array.
- Return type:
NDArray
Examples
>>> repmat([1, 2], 2, 3) array([[1, 2, 1, 2, 1, 2], [1, 2, 1, 2, 1, 2]])
- pytcl.core.array_utils.meshgrid_ij(*xi, indexing='ij')[source]
Create coordinate matrices from coordinate vectors.
Wrapper around np.meshgrid with ‘ij’ indexing as default (MATLAB-style).
- Parameters:
*xi (array_like) – 1-D arrays representing coordinates.
indexing ({'ij', 'xy'}, optional) – Cartesian (‘xy’, default numpy) or matrix (‘ij’, MATLAB-style) indexing. Default is ‘ij’.
- Returns:
Coordinate matrices.
- Return type:
tuple of NDArray
Examples
>>> import numpy as np >>> from pytcl.core.array_utils import meshgrid_ij >>> x = np.array([1, 2, 3]) >>> y = np.array([4, 5]) >>> X, Y = meshgrid_ij(x, y) >>> X array([[1, 1], [2, 2], [3, 3]]) >>> Y array([[4, 5], [4, 5], [4, 5]])
- pytcl.core.array_utils.make_readonly(*arrays)[source]
Mark arrays read-only so they can be shared between callers safely.
Use this on anything handed out from behind a cache. A cached loader gives every caller the same object, so a writable array means one caller can silently change what every other holder sees. Marking it read-only turns that into an immediate
ValueErrorat the assignment instead of a wrong answer discovered much later, or not at all.Two cached DEM loaders had exactly this defect: two callers loading the same region received the same grid, and one write changed an elevation from 1287 m to -99999 m for the other, with no exception raised (gh-51).
A caller that needs to modify the data copies it first, which costs nothing for the majority who only read. Copying inside the loader instead would be correct but would duplicate a potentially large array on every call, which is most of what a cache exists to avoid.
- Parameters:
*arrays (ndarray) – Arrays to mark. Modified in place; nothing is returned, to make it obvious at the call site that this is not a copy.
Examples
>>> import numpy as np >>> shared = np.array([1.0, 2.0, 3.0]) >>> make_readonly(shared) >>> shared.flags.writeable False
A caller that needs to modify it works on a copy:
>>> working = shared.copy() >>> working[0] = 99.0 >>> float(shared[0]) 1.0
- pytcl.core.array_utils.is_positive_definite(A, tol=1e-10)[source]
Check if a matrix is positive definite.
Every eigenvalue must be strictly positive. A singular matrix is positive semi-definite, not positive definite – use
is_positive_semidefinite()for that.- Parameters:
A (array_like) – Square matrix to check.
tol (float, optional) – Relative tolerance on the eigenvalues, scaled by the largest magnitude eigenvalue. Default is 1e-10.
- Returns:
True if the matrix is symmetric and every eigenvalue is positive.
- Return type:
Examples
>>> A = np.array([[4, 2], [2, 5]]) >>> is_positive_definite(A) True
A singular matrix is not positive definite:
>>> is_positive_definite(np.diag([1.0, 0.0])) False
Notes
This previously tested
eigenvalues > -tol * max|lambda|, which admits zero and small negative eigenvalues, sodiag(1, 0)returned True. The name promises a stronger guarantee than that test provided.
- pytcl.core.array_utils.is_positive_semidefinite(A, tol=1e-10)[source]
Check if a matrix is positive semidefinite.
Every eigenvalue must be non-negative to within a relative tolerance. This is the right check for a covariance, which may legitimately be singular – a perfectly known state component gives a zero eigenvalue.
- Parameters:
A (array_like) – Square matrix to check.
tol (float, optional) – Relative tolerance on the eigenvalues, scaled by the largest magnitude eigenvalue. Default is 1e-10.
- Returns:
True if the matrix is symmetric and has no negative eigenvalue.
- Return type:
Examples
>>> is_positive_semidefinite(np.diag([1.0, 0.0])) True >>> is_positive_semidefinite(np.diag([1.0, -1.0])) False
- pytcl.core.array_utils.nearest_positive_definite(A)[source]
Find the nearest positive definite matrix.
Uses the method from Higham (1988) “Computing a Nearest Symmetric Positive Semidefinite Matrix”.
- Parameters:
A (array_like) – Input matrix.
- Returns:
Nearest positive definite matrix.
- Return type:
NDArray
Examples
>>> import numpy as np >>> from pytcl.core.array_utils import nearest_positive_definite >>> A = np.array([[1, -2], [-2, 1]]) # Not PD >>> A_pd = nearest_positive_definite(A) >>> bool(np.min(np.linalg.eigvalsh(A_pd)) > -1e-10) True
- pytcl.core.array_utils.safe_cholesky(A, max_attempts=10)[source]
Compute Cholesky decomposition with fallback for near-singular matrices.
If standard Cholesky fails, attempts to find nearest positive definite matrix.
- Parameters:
A (array_like) – Positive definite matrix.
max_attempts (int, optional) – Maximum regularization attempts. Default is 10.
- Returns:
Lower triangular Cholesky factor L such that A = L @ L.T
- Return type:
NDArray
- Raises:
np.linalg.LinAlgError – If Cholesky decomposition fails after all attempts.
Examples
>>> import numpy as np >>> from pytcl.core.array_utils import safe_cholesky >>> A = np.array([[4, 2], [2, 3]]) # PD matrix >>> L = safe_cholesky(A) >>> np.allclose(L @ L.T, A) True
Validation
Input validation utilities for the Tracker Component Library.
This module provides decorators and functions for validating input arrays, ensuring consistent behavior across the library and providing helpful error messages when inputs don’t meet requirements.
- pytcl.core.validation.validate_array(arr, name='array', *, dtype=None, ndim=None, shape=None, min_ndim=None, max_ndim=None, finite=False, non_negative=False, positive=False, allow_empty=True)[source]
Validate and convert an array-like input to a NumPy array.
- Parameters:
arr (array_like) – Input to validate and convert.
name (str, optional) – Name of the parameter (for error messages). Default is “array”.
dtype (type or np.dtype, optional) – If provided, ensure the array has this dtype (or can be safely cast).
ndim (int or tuple of int, optional) – If provided, ensure the array has exactly this number of dimensions. Can be a tuple to allow multiple valid dimensionalities.
shape (tuple, optional) – If provided, validate the shape. Use None for dimensions that can be any size. Example: (3, None) requires first dimension to be 3, second can be any size.
min_ndim (int, optional) – Minimum number of dimensions required.
max_ndim (int, optional) – Maximum number of dimensions allowed.
finite (bool, optional) – If True, ensure all elements are finite (no inf or nan). Default is False.
non_negative (bool, optional) – If True, ensure all elements are >= 0. Default is False.
positive (bool, optional) – If True, ensure all elements are > 0. Default is False.
allow_empty (bool, optional) – If False, raise an error for empty arrays. Default is True.
- Returns:
Validated NumPy array.
- Return type:
NDArray
- Raises:
ValidationError – If the input fails any validation check.
Examples
>>> validate_array([1, 2, 3], "position", ndim=1, finite=True) array([1, 2, 3])
>>> validate_array([[1, 2], [3, 4]], "matrix", shape=(2, 2)) array([[1, 2], [3, 4]])
- pytcl.core.validation.ensure_2d(arr, name='array', axis='auto')[source]
Ensure an array is 2D, promoting 1D arrays as needed.
- Parameters:
arr (array_like) – Input array.
name (str, optional) – Name of the parameter (for error messages).
axis ({'row', 'column', 'auto'}, optional) – How to promote 1D arrays: - ‘row’: Make 1D array a row vector (1, n) - ‘column’: Make 1D array a column vector (n, 1) - ‘auto’: Preserve as-is for 2D, use ‘column’ for 1D
- Returns:
2D array.
- Return type:
NDArray
Examples
>>> ensure_2d([1, 2, 3], axis='column') array([[1], [2], [3]])
>>> ensure_2d([1, 2, 3], axis='row') array([[1, 2, 3]])
- pytcl.core.validation.ensure_column_vector(arr, name='vector')[source]
Ensure input is a column vector (n, 1).
- Parameters:
arr (array_like) – Input array, must be 1D or a column vector.
name (str, optional) – Name of the parameter (for error messages).
- Returns:
Column vector with shape (n, 1).
- Return type:
NDArray
Examples
>>> ensure_column_vector([1, 2, 3]) array([[1], [2], [3]])
- pytcl.core.validation.ensure_row_vector(arr, name='vector')[source]
Ensure input is a row vector (1, n).
- Parameters:
arr (array_like) – Input array, must be 1D or a row vector.
name (str, optional) – Name of the parameter (for error messages).
- Returns:
Row vector with shape (1, n).
- Return type:
NDArray
Examples
>>> ensure_row_vector([1, 2, 3]) array([[1, 2, 3]])
- pytcl.core.validation.ensure_square_matrix(arr, name='matrix')[source]
Ensure input is a square matrix.
- Parameters:
arr (array_like) – Input array.
name (str, optional) – Name of the parameter (for error messages).
- Returns:
Square matrix.
- Return type:
NDArray
- Raises:
ValidationError – If input is not a 2D square array.
- pytcl.core.validation.ensure_symmetric(arr, name='matrix', rtol=1e-10, atol=1e-10)[source]
Ensure input is a symmetric matrix.
- Parameters:
- Returns:
Symmetric matrix (symmetrized if nearly symmetric).
- Return type:
NDArray
- Raises:
ValidationError – If input is not symmetric within tolerance.
- pytcl.core.validation.ensure_positive_definite(arr, name='matrix', rtol=1e-10)[source]
Ensure input is a positive definite matrix.
Every eigenvalue must be strictly positive. A singular matrix is positive semi-definite, not positive definite – use
ensure_positive_semidefinite()for that, which is usually the right check for a covariance, since a perfectly known state component gives a zero eigenvalue.- Parameters:
- Returns:
Positive definite matrix.
- Return type:
NDArray
- Raises:
ValidationError – If input is not symmetric, or has any eigenvalue that is not strictly positive.
Notes
This previously tested
min(eigenvalues) >= -rtol * max|lambda|. That threshold is negative, so it admitted zero and small negative eigenvalues and accepteddiag(1, 0)– the semidefinite test, under a name and a docstring promising definiteness.pytcl.core.is_positive_definite()carried the identical defect and was corrected earlier; this is that fix applied to the validating counterpart, which the earlier pass missed.
- pytcl.core.validation.ensure_positive_semidefinite(arr, name='matrix', rtol=1e-10)[source]
Ensure input is a positive semidefinite matrix.
Every eigenvalue must be non-negative to within a relative tolerance. This is the right check for a covariance, which may legitimately be singular – a perfectly known state component gives a zero eigenvalue. For the stricter check use
ensure_positive_definite().- Parameters:
- Returns:
Positive semidefinite matrix.
- Return type:
NDArray
- Raises:
ValidationError – If input is not symmetric, or has a negative eigenvalue beyond tolerance.
Examples
>>> import numpy as np >>> from pytcl.core.validation import ensure_positive_semidefinite >>> _ = ensure_positive_semidefinite(np.diag([1.0, 0.0]))
- pytcl.core.validation.validate_same_shape(*arrays, names=None)[source]
Validate that all input arrays have the same shape.
- Parameters:
*arrays (array_like) – Arrays to compare.
names (sequence of str, optional) – Names for error messages. If not provided, uses “array_0”, “array_1”, etc.
- Raises:
ValidationError – If arrays have different shapes.
- pytcl.core.validation.validated_array_input(param_name, *, dtype=None, ndim=None, shape=None, finite=False)[source]
Decorator factory for validating a specific array parameter.
- Parameters:
param_name (str) – Name of the parameter to validate.
dtype (type or np.dtype, optional) – Required dtype.
ndim (int or tuple of int, optional) – Required number of dimensions.
shape (tuple, optional) – Required shape (None for any size in a dimension).
finite (bool, optional) – If True, require all finite values.
- Returns:
Decorator that validates the specified parameter.
- Return type:
Callable
Examples
>>> @validated_array_input("x", ndim=1, finite=True) ... def my_func(x, y=1): ... return np.sum(x) + y
- class pytcl.core.validation.ArraySpec(*, dtype=None, ndim=None, shape=None, min_ndim=None, max_ndim=None, finite=False, non_negative=False, positive=False, allow_empty=True, square=False, symmetric=False, positive_definite=False)[source]
Bases:
objectSpecification for array validation in @validate_inputs decorator.
- Parameters:
dtype (type or np.dtype, optional) – Required dtype.
ndim (int or tuple of int, optional) – Required dimensionality.
shape (tuple, optional) – Required shape (None for any size).
min_ndim (int, optional) – Minimum dimensions required.
max_ndim (int, optional) – Maximum dimensions allowed.
finite (bool, optional) – Require all finite values.
non_negative (bool, optional) – Require all values >= 0.
positive (bool, optional) – Require all values > 0.
allow_empty (bool, optional) – Allow empty arrays. Default True.
square (bool, optional) – Require square matrix.
symmetric (bool, optional) – Require symmetric matrix.
positive_definite (bool, optional) – Require positive definite matrix.
Examples
>>> spec = ArraySpec(ndim=2, finite=True, square=True) >>> @validate_inputs(matrix=spec) ... def process_matrix(matrix): ... return np.linalg.inv(matrix)
- class pytcl.core.validation.ScalarSpec(*, dtype=None, min_value=None, max_value=None, finite=False, positive=False, non_negative=False)[source]
Bases:
objectSpecification for scalar validation in @validate_inputs decorator.
- Parameters:
dtype (type, optional) – Required type (int, float, etc.).
min_value (float, optional) – Minimum allowed value (inclusive).
max_value (float, optional) – Maximum allowed value (inclusive).
finite (bool, optional) – Require finite value.
positive (bool, optional) – Require value > 0.
non_negative (bool, optional) – Require value >= 0.
Examples
>>> spec = ScalarSpec(dtype=int, min_value=1, max_value=10) >>> @validate_inputs(k=spec) ... def get_k_nearest(k, data): ... return data[:k]
- pytcl.core.validation.validate_inputs(**param_specs)[source]
Decorator for validating multiple function parameters.
This decorator enables declarative input validation using specification objects (ArraySpec, ScalarSpec) or dictionaries of validation options.
- Parameters:
**param_specs (ArraySpec | ScalarSpec | dict) – Keyword arguments mapping parameter names to validation specs. Each spec can be: - ArraySpec: For array validation - ScalarSpec: For scalar validation - dict: Options passed to ArraySpec (for convenience)
- Returns:
Decorated function with input validation.
- Return type:
Callable
Examples
>>> @validate_inputs( ... x=ArraySpec(ndim=2, finite=True), ... P=ArraySpec(ndim=2, positive_definite=True), ... k=ScalarSpec(dtype=int, min_value=1), ... ) ... def kalman_update(x, P, z, H, R, k=1): ... # x and P are guaranteed valid here ... pass
Using dict shorthand:
>>> @validate_inputs( ... state={"ndim": 1, "finite": True}, ... covariance={"ndim": 2, "positive_definite": True}, ... ) ... def predict(state, covariance, dt): ... pass
Notes
Validation happens in the order parameters are defined in the decorator. If any validation fails, a ValidationError is raised with a descriptive message identifying the parameter and the constraint violated.
See also
ArraySpecSpecification class for array validation.
ScalarSpecSpecification class for scalar validation.
validate_arrayLower-level array validation function.
- pytcl.core.validation.check_compatible_shapes(*shapes, names=None, dimension=None)[source]
Check that array shapes are compatible for operations.
- Parameters:
- Raises:
ValidationError – If shapes are not compatible.
Examples
Incompatible sizes along a specific dimension raise:
>>> check_compatible_shapes((3, 4), (4, 5), names=["A", "B"], dimension=0) Traceback (most recent call last): ... pytcl.core.exceptions.ValidationError: Arrays have incompatible sizes along dimension 0: A=3, B=4
Without a dimension constraint this passes (inner dimensions are compatible for matrix multiply):
>>> check_compatible_shapes((3, 4), (4, 5), names=["A", "B"])
Exceptions
Custom exception hierarchy for the Tracker Component Library.
This module provides a structured exception hierarchy for consistent error handling across the library. Custom exceptions enable more specific error catching, better error messages, and improved debugging.
Exception Hierarchy
TCLError (base)
├── ValidationError (input validation failures)
│ ├── DimensionError (array shape/dimension mismatches)
│ ├── ParameterError (invalid parameter values)
│ └── RangeError (out-of-range values)
├── ComputationError (numerical computation failures)
│ ├── ConvergenceError (iterative algorithm non-convergence)
│ ├── NumericalError (numerical stability issues)
│ └── SingularMatrixError (singular/non-invertible matrix)
├── StateError (object state violations)
│ ├── UninitializedError (object not initialized)
│ └── EmptyContainerError (container has no elements)
├── ConfigurationError (configuration/setup issues)
│ ├── MethodError (invalid method/algorithm selection)
│ └── DependencyError (missing optional dependency)
└── DataError (data format/structure issues)
├── FormatError (invalid data format)
└── ParseError (data parsing failures)
Examples
Catching specific exception types:
>>> from pytcl.core.exceptions import ConvergenceError, ParameterError
>>> try:
... result = solve_kepler(M=1.5, e=1.5) # Invalid eccentricity
... except ParameterError as e:
... print(f"Invalid parameter: {e}")
Catching all TCL errors:
>>> from pytcl.core.exceptions import TCLError
>>> try:
... result = compute_orbit(...)
... except TCLError as e:
... print(f"TCL error: {e}")
- exception pytcl.core.exceptions.TCLError(message, details=None)[source]
Bases:
ExceptionBase exception for all Tracker Component Library errors.
All custom exceptions in the library inherit from this class, allowing users to catch all TCL-specific errors with a single except clause.
- Parameters:
Examples
>>> raise TCLError("Something went wrong", details={"value": 42}) Traceback (most recent call last): ... pytcl.core.exceptions.TCLError: Something went wrong (value=42)
- exception pytcl.core.exceptions.ValidationError(message, parameter=None, expected=None, actual=None, **kwargs)[source]
Bases:
TCLError,ValueErrorBase exception for input validation failures.
Raised when function inputs fail validation checks. This extends both TCLError (for TCL-specific catching) and ValueError (for compatibility with code expecting standard Python exceptions).
- Parameters:
Examples
>>> raise ValidationError( ... "Invalid matrix dimensions", ... parameter="P", ... expected="3x3 matrix", ... actual="2x4 array" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.ValidationError: Invalid matrix dimensions (parameter=P, expected=3x3 matrix, actual=2x4 array)
- exception pytcl.core.exceptions.DimensionError(message, expected_shape=None, actual_shape=None, parameter=None, **kwargs)[source]
Bases:
ValidationErrorException for array dimension and shape mismatches.
Raised when array dimensions or shapes don’t match requirements or aren’t compatible with each other.
- Parameters:
Examples
>>> raise DimensionError( ... "Covariance matrix must be 3x3", ... expected_shape=(3, 3), ... actual_shape=(2, 4), ... parameter="P" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.DimensionError: Covariance matrix must be 3x3 (parameter=P, expected_shape=(3, 3), actual_shape=(2, 4))
- exception pytcl.core.exceptions.ParameterError(message, parameter=None, value=None, constraint=None, **kwargs)[source]
Bases:
ValidationErrorException for invalid parameter values.
Raised when a parameter value violates constraints (type, value range, allowed values, etc.).
- Parameters:
Examples
>>> raise ParameterError( ... "Variance must be positive", ... parameter="variance", ... value=-1.0, ... constraint="must be > 0" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.ParameterError: Variance must be positive (parameter=variance, value=-1.0, constraint=must be > 0)
- exception pytcl.core.exceptions.RangeError(message, parameter=None, value=None, min_value=None, max_value=None, **kwargs)[source]
Bases:
ValidationErrorException for out-of-range values.
Raised when a numeric value falls outside an allowed range.
- Parameters:
Examples
>>> raise RangeError( ... "Eccentricity must be in [0, 1) for elliptic orbits", ... parameter="e", ... value=1.5, ... min_value=0.0, ... max_value=1.0 ... ) Traceback (most recent call last): ... pytcl.core.exceptions.RangeError: Eccentricity must be in [0, 1) for elliptic orbits (parameter=e, value=1.5, min=0.0, max=1.0)
- exception pytcl.core.exceptions.ComputationError(message, algorithm=None, **kwargs)[source]
Bases:
TCLError,RuntimeErrorBase exception for numerical computation failures.
Raised when a numerical algorithm fails to produce a valid result. This extends RuntimeError for compatibility with code that catches standard computation errors.
- Parameters:
Examples
>>> raise ComputationError( ... "Failed to compute eigenvalues", ... algorithm="numpy.linalg.eig" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.ComputationError: Failed to compute eigenvalues (algorithm=numpy.linalg.eig)
- exception pytcl.core.exceptions.ConvergenceError(message, algorithm=None, iterations=None, max_iterations=None, residual=None, tolerance=None, **kwargs)[source]
Bases:
ComputationErrorException for iterative algorithm convergence failures.
Raised when an iterative algorithm fails to converge within the maximum number of iterations.
- Parameters:
message (str) – Description of the convergence failure.
algorithm (str, optional) – Name of the algorithm.
iterations (int, optional) – Number of iterations performed.
max_iterations (int, optional) – Maximum iterations allowed.
residual (float, optional) – Final residual or error value.
tolerance (float, optional) – Convergence tolerance.
Examples
>>> raise ConvergenceError( ... "Kepler's equation did not converge", ... algorithm="Newton-Raphson", ... iterations=100, ... max_iterations=100, ... residual=1e-5, ... tolerance=1e-12 ... ) Traceback (most recent call last): ... pytcl.core.exceptions.ConvergenceError: Kepler's equation did not converge (algorithm=Newton-Raphson, iterations=100, max_iterations=100, residual=1e-05, tolerance=1e-12)
- exception pytcl.core.exceptions.NumericalError(message, operation=None, condition_number=None, **kwargs)[source]
Bases:
ComputationErrorException for numerical stability issues.
Raised when numerical operations fail due to precision issues, overflow, underflow, or ill-conditioned computations.
- Parameters:
Examples
>>> raise NumericalError( ... "Matrix is ill-conditioned", ... operation="matrix inversion", ... condition_number=1e16 ... ) Traceback (most recent call last): ... pytcl.core.exceptions.NumericalError: Matrix is ill-conditioned (operation=matrix inversion, condition_number=1e+16)
- exception pytcl.core.exceptions.SingularMatrixError(message, matrix_name=None, determinant=None, **kwargs)[source]
Bases:
ComputationErrorException for singular or non-invertible matrix operations.
Raised when a matrix operation requires an invertible matrix but the matrix is singular or nearly singular.
- Parameters:
Examples
>>> raise SingularMatrixError( ... "Covariance matrix is singular", ... matrix_name="P", ... determinant=1e-20 ... ) Traceback (most recent call last): ... pytcl.core.exceptions.SingularMatrixError: Covariance matrix is singular (matrix=P, determinant=1e-20)
- exception pytcl.core.exceptions.StateError(message, object_type=None, current_state=None, required_state=None, **kwargs)[source]
Bases:
TCLErrorBase exception for object state violations.
Raised when an operation is attempted on an object that is not in a valid state for that operation.
- Parameters:
Examples
>>> raise StateError( ... "Cannot update without prediction", ... object_type="KalmanFilter", ... current_state="uninitialized", ... required_state="predicted" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.StateError: Cannot update without prediction (object_type=KalmanFilter, current_state=uninitialized, required_state=predicted)
- exception pytcl.core.exceptions.UninitializedError(message, object_type=None, required_initialization=None, **kwargs)[source]
Bases:
StateErrorException for uninitialized object access.
Raised when an operation requires an initialized object but the object hasn’t been properly initialized.
- Parameters:
Examples
>>> raise UninitializedError( ... "Tracker not initialized", ... object_type="SingleTargetTracker", ... required_initialization="call initialize() first" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.UninitializedError: Tracker not initialized (object_type=SingleTargetTracker, current_state=uninitialized, required_state=call initialize() first)
- exception pytcl.core.exceptions.EmptyContainerError(message, container_type=None, operation=None, **kwargs)[source]
Bases:
StateErrorException for empty container operations.
Raised when an operation requires a non-empty container but the container has no elements.
- Parameters:
Examples
>>> raise EmptyContainerError( ... "Cannot query empty RTree", ... container_type="RTree", ... operation="nearest neighbor query" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.EmptyContainerError: Cannot query empty RTree (object_type=RTree, current_state=empty, operation=nearest neighbor query)
- exception pytcl.core.exceptions.ConfigurationError(message, details=None)[source]
Bases:
TCLErrorBase exception for configuration and setup issues.
Raised when there are problems with algorithm configuration, method selection, or dependency availability.
- Parameters:
message (str) – Description of the configuration issue.
Examples
>>> raise ConfigurationError("Invalid filter configuration") Traceback (most recent call last): ... pytcl.core.exceptions.ConfigurationError: Invalid filter configuration
- exception pytcl.core.exceptions.MethodError(message, method=None, valid_methods=None, **kwargs)[source]
Bases:
ConfigurationError,ValueErrorException for invalid method or algorithm selection.
Raised when an unknown or unsupported method/algorithm is specified.
- Parameters:
Examples
>>> raise MethodError( ... "Unknown assignment method", ... method="invalid_method", ... valid_methods=["hungarian", "auction", "greedy"] ... ) Traceback (most recent call last): ... pytcl.core.exceptions.MethodError: Unknown assignment method (method=invalid_method, valid_methods=['hungarian', 'auction', 'greedy'])
- exception pytcl.core.exceptions.DependencyError(message, package=None, feature=None, install_command=None, **kwargs)[source]
Bases:
ConfigurationError,ImportErrorException for missing optional dependencies.
Raised when an optional dependency is required but not installed.
- Parameters:
Examples
>>> raise DependencyError( ... "plotly is required for interactive plotting", ... package="plotly", ... feature="3D visualization", ... install_command="pip install plotly" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.DependencyError: plotly is required for interactive plotting (package=plotly, feature=3D visualization, install=pip install plotly)
- exception pytcl.core.exceptions.DataError(message, details=None)[source]
Bases:
TCLErrorBase exception for data format and structure issues.
Raised when input data has format or structural problems.
- Parameters:
message (str) – Description of the data issue.
Examples
>>> raise DataError("Invalid input data format") Traceback (most recent call last): ... pytcl.core.exceptions.DataError: Invalid input data format
- exception pytcl.core.exceptions.FormatError(message, expected_format=None, actual_format=None, **kwargs)[source]
Bases:
DataError,ValueErrorException for invalid data format.
Raised when data doesn’t conform to the expected format.
- Parameters:
Examples
>>> raise FormatError( ... "Invalid TLE format", ... expected_format="69 characters per line", ... actual_format="line 1 has 65 characters" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.FormatError: Invalid TLE format (expected=69 characters per line, actual=line 1 has 65 characters)
- exception pytcl.core.exceptions.ParseError(message, data_type=None, position=None, reason=None, **kwargs)[source]
Bases:
DataError,ValueErrorException for data parsing failures.
Raised when data cannot be parsed or interpreted.
- Parameters:
Examples
>>> raise ParseError( ... "Failed to parse TLE checksum", ... data_type="TLE", ... position=68, ... reason="invalid checksum digit" ... ) Traceback (most recent call last): ... pytcl.core.exceptions.ParseError: Failed to parse TLE checksum (data_type=TLE, position=68, reason=invalid checksum digit)
Maturity
Module maturity classification system for the Tracker Component Library.
This module provides a standardized way to indicate the production-readiness and stability of different modules within pyTCL. The maturity levels help users understand which APIs are stable and which may change.
Maturity Levels
- STABLE (3)
Production-ready. Thoroughly tested, well-documented, and API is frozen. Breaking changes only in major version bumps.
- MATURE (2)
Ready for production use. Good test coverage and documentation. Minor API adjustments possible in minor versions.
- EXPERIMENTAL (1)
Functional but may change. Limited testing or documentation. API may change in any release.
- DEPRECATED (0)
Scheduled for removal. Use the recommended replacement.
Examples
Check the maturity level of a module:
>>> from pytcl.core.maturity import get_maturity, MaturityLevel
>>> level = get_maturity("pytcl.dynamic_estimation.kalman.linear")
>>> level == MaturityLevel.STABLE
True
List all stable modules:
>>> from pytcl.core.maturity import get_modules_by_maturity, MaturityLevel
>>> stable_modules = get_modules_by_maturity(MaturityLevel.STABLE)
Notes
How modules are classified. The levels are a promise about breakage, so they are assigned from evidence rather than impression:
STABLE is never assigned automatically. Freezing an API is a release commitment, not something a coverage number implies; promoting a module here is a deliberate act.
MATURE requires at least 90% line coverage, no behaviour change in the current release, and a code path CI can actually execute.
EXPERIMENTAL covers everything else: below that coverage bar, changed behaviour this release, or – for
pytcl.gpu– a CuPy branch that no CI runner can reach, so the reported coverage reflects only the MLX half.
Two invariants are enforced by tests/unit/test_maturity_comprehensive.py:
every registered path must import, and every module must be registered. The
first exists because 32 of 78 entries once named modules from a pre-2.0
layout, and since lookup is by exact path they silently reported
EXPERIMENTAL instead of their recorded level. The second exists because whole
subsystems shipped – io, diagnostics, transponders, gpu –
without ever being classified.
See also
pytcl.core.optional_depsOptional dependency management.
- class pytcl.core.maturity.MaturityLevel(value)[source]
Bases:
IntEnumMaturity level classification for modules.
- Variables:
- DEPRECATED = 0
- EXPERIMENTAL = 1
- MATURE = 2
- STABLE = 3
- pytcl.core.maturity.get_maturity(module_path)[source]
Get the maturity level of a module.
- Parameters:
module_path (str) – Module path relative to pytcl (e.g., “dynamic_estimation.kalman.linear”) or full path (e.g., “pytcl.dynamic_estimation.kalman.linear”).
- Returns:
The module’s maturity level. Returns EXPERIMENTAL if not classified.
- Return type:
Examples
>>> get_maturity("dynamic_estimation.kalman.linear") <MaturityLevel.STABLE: 3> >>> get_maturity("pytcl.core.constants") <MaturityLevel.STABLE: 3>
- pytcl.core.maturity.get_modules_by_maturity(level)[source]
Get all modules at a specific maturity level.
- Parameters:
level (MaturityLevel) – The maturity level to filter by.
- Returns:
Module paths at the specified maturity level.
- Return type:
Examples
>>> stable = get_modules_by_maturity(MaturityLevel.STABLE) >>> "core.constants" in stable True
- pytcl.core.maturity.get_maturity_summary()[source]
Get a summary count of modules at each maturity level.
- Returns:
Mapping from MaturityLevel to count of modules.
- Return type:
Examples
>>> summary = get_maturity_summary() >>> summary[MaturityLevel.STABLE] > 0 True
- pytcl.core.maturity.is_stable(module_path)[source]
Check if a module is stable (production-ready with frozen API).
- Parameters:
module_path (str) – Module path to check.
- Returns:
True if the module is stable.
- Return type:
Examples
>>> is_stable("dynamic_estimation.kalman.linear") True >>> is_stable("terrain.dem") False
- pytcl.core.maturity.is_production_ready(module_path)[source]
Check if a module is production-ready (STABLE or MATURE).
- Parameters:
module_path (str) – Module path to check.
- Returns:
True if the module is STABLE or MATURE.
- Return type:
Examples
>>> is_production_ready("dynamic_estimation.kalman.linear") True >>> is_production_ready("dynamic_estimation.imm") True >>> is_production_ready("terrain.dem") False
- pytcl.core.maturity.format_maturity_badge(level)[source]
Get a formatted badge string for a maturity level.
- Parameters:
level (MaturityLevel) – The maturity level.
- Returns:
A badge string suitable for documentation.
- Return type:
Examples
>>> format_maturity_badge(MaturityLevel.STABLE) '|stable|'
Optional Deps
Optional dependencies management for the Tracker Component Library.
This module provides a unified system for handling optional dependencies, including lazy imports, availability checks, and helpful error messages.
The system supports: - Lazy imports that only load modules when accessed - Availability flags for conditional code paths - Decorators for functions requiring optional dependencies - Helpful error messages with installation instructions
Examples
Check if a dependency is available:
>>> from pytcl.core.optional_deps import is_available
>>> if is_available("plotly"):
... import plotly.graph_objects as go
Use a decorator to require a dependency:
>>> from pytcl.core.optional_deps import requires
>>> @requires("plotly", extra="visualization")
... def create_3d_plot(data):
... import plotly.graph_objects as go
... return go.Figure(data)
Import with a helpful error on failure:
>>> from pytcl.core.optional_deps import import_optional
>>> go = import_optional("plotly.graph_objects", package="plotly", extra="visualization")
- pytcl.core.optional_deps.is_available(package)[source]
Check if an optional package is available.
- Parameters:
package (str) – Name of the package to check (e.g., “plotly”, “pywt”).
- Returns:
True if the package is installed and can be imported.
- Return type:
Examples
>>> from pytcl.core.optional_deps import is_available >>> if is_available("plotly"): ... from plotly import graph_objects as go ... # use plotly ... else: ... print("Plotly not available")
Notes
Results are cached for performance. Use
_clear_cache()if you need to re-check availability (e.g., after installing a package).
- pytcl.core.optional_deps.import_optional(module_name, *, package=None, extra=None, feature=None)[source]
Import an optional module with a helpful error message on failure.
- Parameters:
module_name (str) – Full module path to import (e.g., “plotly.graph_objects”).
package (str, optional) – Package name for error message. If not provided, extracted from module_name.
extra (str, optional) – Name of the pytcl extra that provides this dependency (e.g., “visualization”, “astronomy”).
feature (str, optional) – Description of the feature requiring this dependency.
- Returns:
module – The imported module.
- Return type:
ModuleType
- Raises:
DependencyError – If the module cannot be imported.
Examples
>>> go = import_optional( ... "plotly.graph_objects", ... package="plotly", ... extra="visualization", ... feature="3D plotting" ... )
- pytcl.core.optional_deps.requires(*packages, extra=None, feature=None)[source]
Decorator to mark a function as requiring optional dependencies.
When the decorated function is called, it checks if the required packages are available. If not, it raises a DependencyError with a helpful message.
- Parameters:
- Returns:
decorator – Decorator that wraps the function with dependency checking.
- Return type:
callable
Examples
>>> from pytcl.core.optional_deps import requires >>> >>> @requires("plotly", extra="visualization") ... def create_plot(data): ... import plotly.graph_objects as go ... return go.Figure(data) >>> >>> # This will raise DependencyError if plotly is not installed >>> fig = create_plot([1, 2, 3])
Multiple packages:
>>> @requires("astropy", "jplephem", extra="astronomy") ... def compute_ephemeris(body, time): ... from astropy.time import Time ... import jplephem ... # ...
Notes
The decorator checks availability at call time, not at definition time. This allows the module to be imported even if the optional dependencies are not installed.
- pytcl.core.optional_deps.check_dependencies(*packages, extra=None)[source]
Check that all required packages are available.
- Parameters:
- Raises:
DependencyError – If any package is not available.
Examples
>>> from pytcl.core.optional_deps import check_dependencies >>> check_dependencies("plotly", extra="visualization") >>> # Raises DependencyError if plotly is not installed
- class pytcl.core.optional_deps.LazyModule(module_name, *, package=None, extra=None, feature=None)[source]
Bases:
objectA lazy module loader that imports the module on first access.
This allows optional dependencies to be “imported” at module level without triggering an import error until they’re actually used.
- Parameters:
Examples
>>> from pytcl.core.optional_deps import LazyModule >>> go = LazyModule("plotly.graph_objects", package="plotly") >>> # No import yet... >>> fig = go.Figure() # Import happens here
Paths
Shared data directory utilities for pytcl.
All modules that load external data files (terrain, magnetism, gravity) use this common utility for locating the data directory.