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

class pytcl.core.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: object

Container 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
c: float = 299792458.0

Speed of light in vacuum [m/s]

G: float = 6.6743e-11

Gravitational constant [m^3 kg^-1 s^-2]

h: float = 6.62607015e-34

Planck constant [J s]

k_B: float = 1.380649e-23

Boltzmann constant [J K^-1]

sigma: float = 5.670374419e-08

Stefan-Boltzmann constant [W m^-2 K^-4]

e: float = 1.602176634e-19

Elementary charge [C]

N_A: float = 6.02214076e+23

Avogadro constant [mol^-1]

R: float = 8.314462618

Universal gas constant [J mol^-1 K^-1]

g_0: float = 9.80665

Standard gravity [m/s^2]

__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)
exception pytcl.core.TCLError(message, details=None)[source]

Bases: Exception

Base 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:
  • message (str) – Human-readable error message.

  • details (dict, optional) – Additional context about the error (parameter values, etc.).

Examples

>>> raise TCLError("Something went wrong", details={"value": 42})
__init__(message, details=None)[source]
exception pytcl.core.ValidationError(message, parameter=None, expected=None, actual=None, **kwargs)[source]

Bases: TCLError, ValueError

Base 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:
  • message (str) – Description of the validation failure.

  • parameter (str, optional) – Name of the parameter that failed validation.

  • expected (str, optional) – Description of what was expected.

  • actual (Any, optional) – The actual value that was provided.

Examples

>>> raise ValidationError(
...     "Invalid matrix dimensions",
...     parameter="P",
...     expected="3x3 matrix",
...     actual="2x4 array"
... )
__init__(message, parameter=None, expected=None, actual=None, **kwargs)[source]
exception pytcl.core.DimensionError(message, expected_shape=None, actual_shape=None, parameter=None, **kwargs)[source]

Bases: ValidationError

Exception for array dimension and shape mismatches.

Raised when array dimensions or shapes don’t match requirements or aren’t compatible with each other.

Parameters:
  • message (str) – Description of the dimension mismatch.

  • expected_shape (tuple, optional) – Expected shape.

  • actual_shape (tuple, optional) – Actual shape.

  • parameter (str, optional) – Name of the parameter.

Examples

>>> raise DimensionError(
...     "Covariance matrix must be 3x3",
...     expected_shape=(3, 3),
...     actual_shape=(2, 4),
...     parameter="P"
... )
__init__(message, expected_shape=None, actual_shape=None, parameter=None, **kwargs)[source]
exception pytcl.core.ParameterError(message, parameter=None, value=None, constraint=None, **kwargs)[source]

Bases: ValidationError

Exception for invalid parameter values.

Raised when a parameter value violates constraints (type, value range, allowed values, etc.).

Parameters:
  • message (str) – Description of the invalid parameter.

  • parameter (str, optional) – Name of the invalid parameter.

  • value (Any, optional) – The invalid value.

  • constraint (str, optional) – Description of the constraint that was violated.

Examples

>>> raise ParameterError(
...     "Variance must be positive",
...     parameter="variance",
...     value=-1.0,
...     constraint="must be > 0"
... )
__init__(message, parameter=None, value=None, constraint=None, **kwargs)[source]
exception pytcl.core.RangeError(message, parameter=None, value=None, min_value=None, max_value=None, **kwargs)[source]

Bases: ValidationError

Exception for out-of-range values.

Raised when a numeric value falls outside an allowed range.

Parameters:
  • message (str) – Description of the range violation.

  • parameter (str, optional) – Name of the parameter.

  • value (float, optional) – The out-of-range value.

  • min_value (float, optional) – Minimum allowed value.

  • max_value (float, optional) – Maximum allowed value.

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
... )
__init__(message, parameter=None, value=None, min_value=None, max_value=None, **kwargs)[source]
exception pytcl.core.ComputationError(message, algorithm=None, **kwargs)[source]

Bases: TCLError, RuntimeError

Base 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:
  • message (str) – Description of the computation failure.

  • algorithm (str, optional) – Name of the algorithm that failed.

Examples

>>> raise ComputationError(
...     "Failed to compute eigenvalues",
...     algorithm="numpy.linalg.eig"
... )
__init__(message, algorithm=None, **kwargs)[source]
exception pytcl.core.ConvergenceError(message, algorithm=None, iterations=None, max_iterations=None, residual=None, tolerance=None, **kwargs)[source]

Bases: ComputationError

Exception 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
... )
__init__(message, algorithm=None, iterations=None, max_iterations=None, residual=None, tolerance=None, **kwargs)[source]
exception pytcl.core.NumericalError(message, operation=None, condition_number=None, **kwargs)[source]

Bases: ComputationError

Exception for numerical stability issues.

Raised when numerical operations fail due to precision issues, overflow, underflow, or ill-conditioned computations.

Parameters:
  • message (str) – Description of the numerical issue.

  • operation (str, optional) – The operation that failed.

  • condition_number (float, optional) – Matrix condition number (if applicable).

Examples

>>> raise NumericalError(
...     "Matrix is ill-conditioned",
...     operation="matrix inversion",
...     condition_number=1e16
... )
__init__(message, operation=None, condition_number=None, **kwargs)[source]
exception pytcl.core.SingularMatrixError(message, matrix_name=None, determinant=None, **kwargs)[source]

Bases: ComputationError

Exception 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:
  • message (str) – Description of the singularity issue.

  • matrix_name (str, optional) – Name of the singular matrix.

  • determinant (float, optional) – Determinant value (near zero).

Examples

>>> raise SingularMatrixError(
...     "Covariance matrix is singular",
...     matrix_name="P",
...     determinant=1e-20
... )
__init__(message, matrix_name=None, determinant=None, **kwargs)[source]
exception pytcl.core.StateError(message, object_type=None, current_state=None, required_state=None, **kwargs)[source]

Bases: TCLError

Base 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:
  • message (str) – Description of the state violation.

  • object_type (str, optional) – Type of the object.

  • current_state (str, optional) – Description of the current state.

  • required_state (str, optional) – Description of the required state.

Examples

>>> raise StateError(
...     "Cannot update without prediction",
...     object_type="KalmanFilter",
...     current_state="uninitialized",
...     required_state="predicted"
... )
__init__(message, object_type=None, current_state=None, required_state=None, **kwargs)[source]
exception pytcl.core.UninitializedError(message, object_type=None, required_initialization=None, **kwargs)[source]

Bases: StateError

Exception for uninitialized object access.

Raised when an operation requires an initialized object but the object hasn’t been properly initialized.

Parameters:
  • message (str) – Description of the issue.

  • object_type (str, optional) – Type of the uninitialized object.

  • required_initialization (str, optional) – What initialization is required.

Examples

>>> raise UninitializedError(
...     "Tracker not initialized",
...     object_type="SingleTargetTracker",
...     required_initialization="call initialize() first"
... )
__init__(message, object_type=None, required_initialization=None, **kwargs)[source]
exception pytcl.core.EmptyContainerError(message, container_type=None, operation=None, **kwargs)[source]

Bases: StateError

Exception for empty container operations.

Raised when an operation requires a non-empty container but the container has no elements.

Parameters:
  • message (str) – Description of the issue.

  • container_type (str, optional) – Type of the empty container.

  • operation (str, optional) – The operation that requires non-empty container.

Examples

>>> raise EmptyContainerError(
...     "Cannot query empty RTree",
...     container_type="RTree",
...     operation="nearest neighbor query"
... )
__init__(message, container_type=None, operation=None, **kwargs)[source]
exception pytcl.core.ConfigurationError(message, details=None)[source]

Bases: TCLError

Base 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")
exception pytcl.core.MethodError(message, method=None, valid_methods=None, **kwargs)[source]

Bases: ConfigurationError, ValueError

Exception for invalid method or algorithm selection.

Raised when an unknown or unsupported method/algorithm is specified.

Parameters:
  • message (str) – Description of the issue.

  • method (str, optional) – The invalid method name.

  • valid_methods (sequence of str, optional) – List of valid method names.

Examples

>>> raise MethodError(
...     "Unknown assignment method",
...     method="invalid_method",
...     valid_methods=["hungarian", "auction", "greedy"]
... )
__init__(message, method=None, valid_methods=None, **kwargs)[source]
exception pytcl.core.DependencyError(message, package=None, feature=None, install_command=None, **kwargs)[source]

Bases: ConfigurationError, ImportError

Exception for missing optional dependencies.

Raised when an optional dependency is required but not installed.

Parameters:
  • message (str) – Description of the missing dependency.

  • package (str, optional) – Name of the missing package.

  • feature (str, optional) – Feature that requires the dependency.

  • install_command (str, optional) – Command to install the dependency.

Examples

>>> raise DependencyError(
...     "plotly is required for interactive plotting",
...     package="plotly",
...     feature="3D visualization",
...     install_command="pip install plotly"
... )
__init__(message, package=None, feature=None, install_command=None, **kwargs)[source]
exception pytcl.core.DataError(message, details=None)[source]

Bases: TCLError

Base 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")
exception pytcl.core.FormatError(message, expected_format=None, actual_format=None, **kwargs)[source]

Bases: DataError, ValueError

Exception for invalid data format.

Raised when data doesn’t conform to the expected format.

Parameters:
  • message (str) – Description of the format issue.

  • expected_format (str, optional) – Description of the expected format.

  • actual_format (str, optional) – Description of the actual format.

Examples

>>> raise FormatError(
...     "Invalid TLE format",
...     expected_format="69 characters per line",
...     actual_format="line 1 has 65 characters"
... )
__init__(message, expected_format=None, actual_format=None, **kwargs)[source]
exception pytcl.core.ParseError(message, data_type=None, position=None, reason=None, **kwargs)[source]

Bases: DataError, ValueError

Exception for data parsing failures.

Raised when data cannot be parsed or interpreted.

Parameters:
  • message (str) – Description of the parsing failure.

  • data_type (str, optional) – Type of data being parsed.

  • position (int or tuple, optional) – Position where parsing failed.

  • reason (str, optional) – Reason for the parsing failure.

Examples

>>> raise ParseError(
...     "Failed to parse TLE checksum",
...     data_type="TLE",
...     position=68,
...     reason="invalid checksum digit"
... )
__init__(message, data_type=None, position=None, reason=None, **kwargs)[source]
pytcl.core.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.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

ArraySpec

Specification class for array validation.

ScalarSpec

Specification class for scalar validation.

validate_array

Lower-level array validation function.

pytcl.core.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.check_compatible_shapes(*shapes, names=None, dimension=None)[source]

Check that array shapes are compatible for operations.

Parameters:
  • *shapes (tuple of int) – Shapes to check for compatibility.

  • names (sequence of str, optional) – Names for error messages.

  • dimension (int, optional) – If provided, only check compatibility along this dimension.

Raises:

ValidationError – If shapes are not compatible.

Examples

>>> check_compatible_shapes((3, 4), (4, 5), names=["A", "B"], dimension=0)
# Raises: A has 3 rows but B has 4 rows
>>> check_compatible_shapes((3, 4), (4, 5), names=["A", "B"])
# Passes (inner dimensions compatible for matrix multiply)
class pytcl.core.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: object

Specification 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)
__init__(*, 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]
validate(arr, name)[source]

Validate an array against this specification.

class pytcl.core.ScalarSpec(*, dtype=None, min_value=None, max_value=None, finite=False, positive=False, non_negative=False)[source]

Bases: object

Specification 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]
__init__(*, dtype=None, min_value=None, max_value=None, finite=False, positive=False, non_negative=False)[source]
validate(value, name)[source]

Validate a scalar value against this specification.

pytcl.core.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.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.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.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.ensure_symmetric(arr, name='matrix', rtol=1e-10, atol=1e-10)[source]

Ensure input is a symmetric matrix.

Parameters:
  • arr (array_like) – Input array.

  • name (str, optional) – Name of the parameter (for error messages).

  • rtol (float, optional) – Relative tolerance for symmetry check. Default is 1e-10.

  • atol (float, optional) – Absolute tolerance for symmetry check. Default is 1e-10.

Returns:

Symmetric matrix (symmetrized if nearly symmetric).

Return type:

NDArray

Raises:

ValidationError – If input is not symmetric within tolerance.

pytcl.core.ensure_positive_definite(arr, name='matrix', rtol=1e-10)[source]

Ensure input is a positive definite matrix.

Parameters:
  • arr (array_like) – Input array.

  • name (str, optional) – Name of the parameter (for error messages).

  • rtol (float, optional) – Relative tolerance for eigenvalue check. Default is 1e-10.

Returns:

Positive definite matrix.

Return type:

NDArray

Raises:

ValidationError – If input is not positive definite.

pytcl.core.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.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.wrap_to_range(value, low, high)[source]

Wrap values to a specified interval [low, high).

Parameters:
  • value (array_like) – Value(s) to wrap.

  • low (float) – Lower bound of the interval (inclusive).

  • high (float) – Upper bound of the interval (exclusive).

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.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.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.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:

bool

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.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.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:
  • *packages (str) – One or more package names required by the function.

  • extra (str, optional) – Name of the pytcl extra that provides these dependencies.

  • feature (str, optional) – Description of the feature provided by the function.

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
>>> 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.check_dependencies(*packages, extra=None)[source]

Check that all required packages are available.

Parameters:
  • *packages (str) – Package names to check.

  • extra (str, optional) – pytcl extra for installation hint.

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.LazyModule(module_name, *, package=None, extra=None, feature=None)[source]

Bases: object

A 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:
  • module_name (str) – Full module path to import.

  • package (str, optional) – Package name for error messages.

  • extra (str, optional) – pytcl extra that provides this dependency.

  • feature (str, optional) – Feature description for error messages.

Examples

>>> from pytcl.core.optional_deps import LazyModule
>>> go = LazyModule("plotly.graph_objects", package="plotly")
>>> # No import yet...
>>> fig = go.Figure()  # Import happens here
__init__(module_name, *, package=None, extra=None, feature=None)[source]
__getattr__(name)[source]

Delegate attribute access to the loaded module.

__dir__()[source]

Return module attributes for tab completion.

pytcl.core.get_data_dir()[source]

Get the pytcl data directory for external data files.

The data directory is located at ~/.pytcl/data/ by default. Can be overridden by setting the PYTCL_DATA_DIR environment variable.

Returns:

Path to the data directory.

Return type:

Path

pytcl.core.ensure_data_dir()[source]

Ensure the data directory exists and return its path.

class pytcl.core.MaturityLevel(value)[source]

Bases: IntEnum

Maturity level classification for modules.

DEPRECATED

Level 0. Scheduled for removal.

Type:

int

EXPERIMENTAL

Level 1. Functional but unstable API.

Type:

int

MATURE

Level 2. Production-ready with possible minor changes.

Type:

int

STABLE

Level 3. Production-ready with frozen API.

Type:

int

DEPRECATED = 0
EXPERIMENTAL = 1
MATURE = 2
STABLE = 3
pytcl.core.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:

MaturityLevel

Examples

>>> get_maturity("dynamic_estimation.kalman.linear")
<MaturityLevel.STABLE: 3>
>>> get_maturity("pytcl.core.constants")
<MaturityLevel.STABLE: 3>
pytcl.core.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:

list of str

Examples

>>> stable = get_modules_by_maturity(MaturityLevel.STABLE)
>>> "core.constants" in stable
True
pytcl.core.get_maturity_summary()[source]

Get a summary count of modules at each maturity level.

Returns:

Mapping from MaturityLevel to count of modules.

Return type:

dict

Examples

>>> summary = get_maturity_summary()
>>> summary[MaturityLevel.STABLE] > 0
True
pytcl.core.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:

bool

Examples

>>> is_stable("dynamic_estimation.kalman.linear")
True
>>> is_stable("terrain.dem")
False
pytcl.core.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:

bool

Examples

>>> is_production_ready("dynamic_estimation.kalman.linear")
True
>>> is_production_ready("dynamic_estimation.imm")
True
>>> is_production_ready("terrain.dem")
False

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

pytcl.core.constants.SPEED_OF_LIGHT: Final[float] = 299792458.0

Speed of light in vacuum [m/s]

pytcl.core.constants.GRAVITATIONAL_CONSTANT: Final[float] = 6.6743e-11

Newtonian gravitational constant [m^3 kg^-1 s^-2]

pytcl.core.constants.PLANCK_CONSTANT: Final[float] = 6.62607015e-34

Planck constant [J s]

pytcl.core.constants.BOLTZMANN_CONSTANT: Final[float] = 1.380649e-23

Boltzmann constant [J K^-1]

pytcl.core.constants.STEFAN_BOLTZMANN_CONSTANT: Final[float] = 5.670374419e-08

Stefan-Boltzmann constant [W m^-2 K^-4]

pytcl.core.constants.ELEMENTARY_CHARGE: Final[float] = 1.602176634e-19

Elementary charge [C]

pytcl.core.constants.AVOGADRO_CONSTANT: Final[float] = 6.02214076e+23

Avogadro constant [mol^-1]

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.ABSOLUTE_ZERO_CELSIUS: Final[float] = -273.15

Absolute zero [K] (definition)

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: Final[float] = 0.08181919084262149

First eccentricity

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_DAY: Final[float] = 86400.0

Seconds per day

pytcl.core.constants.SECONDS_PER_JULIAN_CENTURY: Final[float] = 3155760000.0

Seconds per Julian century

pytcl.core.constants.DAYS_PER_JULIAN_YEAR: Final[float] = 365.25

Days per Julian year

pytcl.core.constants.DAYS_PER_JULIAN_CENTURY: Final[float] = 36525.0

Days per Julian century

pytcl.core.constants.J2000_EPOCH_JD: Final[float] = 2451545.0

J2000.0 epoch as Julian Date

pytcl.core.constants.MJD_OFFSET: Final[float] = 2400000.5

Modified Julian Date offset (JD - MJD)

pytcl.core.constants.PI: Final[float] = 3.141592653589793

Pi

pytcl.core.constants.TWO_PI: Final[float] = 6.283185307179586

2*Pi

pytcl.core.constants.HALF_PI: Final[float] = 1.5707963267948966

Pi/2

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: object

Parameters defining a reference ellipsoid.

a

Semi-major axis (equatorial radius) [m]

Type:

float

f

Flattening factor (dimensionless)

Type:

float

GM

Gravitational parameter [m^3/s^2]

Type:

float

omega

Angular rotation rate [rad/s]

Type:

float

name

Name of the ellipsoid

Type:

str

Properties
----------
b

Semi-minor axis (polar radius) [m]

Type:

float

e2

First eccentricity squared

Type:

float

e

First eccentricity

Type:

float

ep2

Second eccentricity squared

Type:

float

a: float
f: float
GM: float
omega: float
name: str = 'Custom'
property b: float

Semi-minor axis [m].

property e2: float

First eccentricity squared.

property e: float

First eccentricity.

property ep2: float

Second eccentricity squared.

property ep: float

Second eccentricity.

__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: object

Container 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
c: float = 299792458.0

Speed of light in vacuum [m/s]

G: float = 6.6743e-11

Gravitational constant [m^3 kg^-1 s^-2]

h: float = 6.62607015e-34

Planck constant [J s]

k_B: float = 1.380649e-23

Boltzmann constant [J K^-1]

sigma: float = 5.670374419e-08

Stefan-Boltzmann constant [W m^-2 K^-4]

e: float = 1.602176634e-19

Elementary charge [C]

N_A: float = 6.02214076e+23

Avogadro constant [mol^-1]

R: float = 8.314462618

Universal gas constant [J mol^-1 K^-1]

g_0: float = 9.80665

Standard gravity [m/s^2]

__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] = 4902869500000.0

Moon gravitational parameter [m^3/s^2]

pytcl.core.constants.EARTH_MOON_MASS_RATIO: Final[float] = 81.30056

Earth-Moon mass ratio

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:
  • value (array_like) – Value(s) to wrap.

  • low (float) – Lower bound of the interval (inclusive).

  • high (float) – Upper bound of the interval (exclusive).

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:
  • v (array_like) – Input vector.

  • shape (tuple of int) – Target shape (rows, cols).

  • order ({'F', 'C'}, optional) – ‘F’ (default): Unstack to columns (MATLAB-style). ‘C’: Unstack to rows.

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:
  • v (array_like) – Vector(s) to normalize.

  • axis (int, optional) – Axis along which to compute norms. If None, normalize the flattened array.

  • return_norm (bool, optional) – If True, also return the original norm(s). Default is False.

Returns:

  • v_normalized (NDArray) – Unit vector(s).

  • norm (NDArray, optional) – Original norm(s), only returned if return_norm=True.

Return type:

ndarray[tuple[Any, …], dtype[floating[Any]]] | tuple[ndarray[tuple[Any, …], dtype[floating[Any]]], 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:
  • arr (array_like) – Input array.

  • m (int) – Number of times to replicate along rows.

  • n (int) – Number of times to replicate along columns.

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, 2, 3],
       [1, 2, 3]])
>>> Y
array([[4, 4, 4],
       [5, 5, 5]])
pytcl.core.array_utils.is_positive_definite(A, tol=1e-10)[source]

Check if a matrix is positive definite.

Parameters:
  • A (array_like) – Square matrix to check.

  • tol (float, optional) – Tolerance for eigenvalue check. Default is 1e-10.

Returns:

True if matrix is positive definite.

Return type:

bool

Examples

>>> A = np.array([[4, 2], [2, 5]])
>>> is_positive_definite(A)
True
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)
>>> np.all(np.linalg.eigvalsh(A_pd) > 0)
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:
  • arr (array_like) – Input array.

  • name (str, optional) – Name of the parameter (for error messages).

  • rtol (float, optional) – Relative tolerance for symmetry check. Default is 1e-10.

  • atol (float, optional) – Absolute tolerance for symmetry check. Default is 1e-10.

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.

Parameters:
  • arr (array_like) – Input array.

  • name (str, optional) – Name of the parameter (for error messages).

  • rtol (float, optional) – Relative tolerance for eigenvalue check. Default is 1e-10.

Returns:

Positive definite matrix.

Return type:

NDArray

Raises:

ValidationError – If input is not positive definite.

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: object

Specification 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)
__init__(*, 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]
validate(arr, name)[source]

Validate an array against this specification.

class pytcl.core.validation.ScalarSpec(*, dtype=None, min_value=None, max_value=None, finite=False, positive=False, non_negative=False)[source]

Bases: object

Specification 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]
__init__(*, dtype=None, min_value=None, max_value=None, finite=False, positive=False, non_negative=False)[source]
validate(value, name)[source]

Validate a scalar value against this specification.

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

ArraySpec

Specification class for array validation.

ScalarSpec

Specification class for scalar validation.

validate_array

Lower-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:
  • *shapes (tuple of int) – Shapes to check for compatibility.

  • names (sequence of str, optional) – Names for error messages.

  • dimension (int, optional) – If provided, only check compatibility along this dimension.

Raises:

ValidationError – If shapes are not compatible.

Examples

>>> check_compatible_shapes((3, 4), (4, 5), names=["A", "B"], dimension=0)
# Raises: A has 3 rows but B has 4 rows
>>> check_compatible_shapes((3, 4), (4, 5), names=["A", "B"])
# Passes (inner dimensions compatible for matrix multiply)