Troubleshooting Guide

Overview

This guide covers common issues, errors, and their solutions. See Library Architecture for module structure and API Navigation Guide for function discovery.

Installation Issues

ImportError: No module named ‘pytcl’

Cause: pytcl not installed or wrong Python environment

Solution:

# Check if installed
python -c "import pytcl; print(pytcl.__version__)"

# If not installed, install from PyPI
pip install nrl-tracker

# Or install from source
git clone https://github.com/nedonatelli/TCL.git
cd TCL
pip install -e .

Using Wrong Python Version

Cause: Project requires Python 3.10+, but you’re using an older version

Solution:

# Check your Python version
python --version

# If too old, create a virtual environment with Python 3.10+
python3.11 -m venv venv
source venv/bin/activate
pip install nrl-tracker

GPU Backend Issues

ImportError: No module named ‘cupy’

Cause: CuPy not installed (GPU acceleration is optional)

Solution:

# CuPy is optional - you can use CPU version
# Or install CuPy for GPU acceleration
pip install cupy-cuda11x  # Replace 11x with your CUDA version

# Check if GPU backend works
python -c "from pytcl.gpu.cupy_backend import to_gpu; print('CuPy OK')"

See GPU Acceleration Guide for detailed GPU setup.

Import and Usage Issues

ImportError: cannot import name ‘function_name’

Cause: Incorrect import path or function doesn’t exist

Solution:

# ❌ Wrong: Function not in this location
from pytcl.kalman import kf_predict

# ✅ Correct: Check the right module
from pytcl.dynamic_estimation.kalman import kf_predict

# Or use interactive discovery
from pytcl.dynamic_estimation import kalman
print([x for x in dir(kalman) if not x.startswith('_')])

TypeError: Expected array-like input

Cause: Function expects NumPy array, got Python list

Solution:

import numpy as np
from pytcl.coordinate_systems.conversions import cart2sphere

# ❌ Wrong: Python list
result = cart2sphere([1.0, 0.0, 0.0])

# ✅ Correct: NumPy array
result = cart2sphere(np.array([1.0, 0.0, 0.0]))

AttributeError: module ‘pytcl’ has no attribute ‘xyz’

Cause: Function not re-exported at top level

Solution:

# ✅ Use full import path
from pytcl.dynamic_estimation.kalman import kf_predict

# Instead of trying
from pytcl import kf_predict  # Won't work

Check available functions:

from pytcl import dynamic_estimation
print(dir(dynamic_estimation.kalman))

Array Shape and Dimension Issues

ValueError: Shapes (3,) and (4,4) not aligned

Cause: Dimension mismatch in matrix operations

Solution: Ensure shapes are compatible

import numpy as np
from pytcl.dynamic_estimation.kalman import kf_predict

# State vector must match F rows
F = np.eye(4)      # 4×4 motion model
Q = np.eye(4) * 0.1
x = np.array([0, 1, 0, 1])  # Must be length 4
P = np.eye(4)      # Must be 4×4

# ✅ Correct dimensions
x_pred, P_pred = kf_predict(x, P, F, Q)

Check dimensions:

def check_shapes(x, P, F, Q, H, R):
    print(f"x shape: {x.shape}")
    print(f"P shape: {P.shape}")
    print(f"F shape: {F.shape}")
    print(f"Q shape: {Q.shape}")
    print(f"H shape: {H.shape}")
    print(f"R shape: {R.shape}")

# Rules:
# x.shape = (n,)
# P.shape = (n, n)
# F.shape = (n, n)
# Q.shape = (n, n)
# H.shape = (m, n)
# R.shape = (m, m)

RuntimeError: Expected 2-D array, got 1-D array instead

Cause: Function expects 2D array but got 1D (or vice versa)

Solution:

import numpy as np

# ❌ Wrong: 1D array
x = np.array([1, 2, 3])
x_2d = x.reshape(-1, 1)  # Convert to column vector

# ✅ Correct: Use as-is if function expects 1D
# Check function documentation
from pytcl.mathematical_functions.special_functions import marcum_q
a = 2.0
b = 3.0
q = marcum_q(a, b)  # Scalar inputs OK

Filter and Estimation Issues

Filter Divergence: Covariance Grows Over Time

Symptoms: P increases instead of decreases, filter loses track

Causes and Solutions:

  1. Q too small (process noise underestimated)

    # ❌ Q too small
    Q = np.eye(4) * 0.001  # Too small!
    
    # ✅ Reasonable Q
    Q = np.eye(4) * 0.1  # Depends on system
    

    See Kalman Filter Tuning Guide for proper Q estimation.

  2. R too large (measurement noise overestimated)

    # ❌ R too large
    R = np.eye(2) * 1000  # Measurements are ignored
    
    # ✅ Match sensor accuracy
    R = np.eye(2) * 0.1
    
  3. Motion model wrong (F matrix incorrect)

    # Verify motion model matches system
    T = 0.1  # time step
    F = f_constant_velocity(T, num_dims=2)
    # If system does accelerate, use:
    # F = f_constant_acceleration(T, num_dims=2)
    
  4. Measurement outliers (sensor gives bad data)

    # Add outlier rejection
    innovation = z - H @ x_pred
    
    if np.linalg.norm(innovation) > gate_threshold:
        # Skip this measurement or downweight it
        pass
    

Diagnostic Code:

import numpy as np

def diagnose_divergence(x_sequence, P_sequence):
    """Check if filter is diverging"""
    trace_P = [np.trace(P) for P in P_sequence]
    det_P = [np.linalg.det(P) for P in P_sequence]

    print("Trace of P (should decrease): ", trace_P)
    print("Det of P (should decrease): ", det_P)

    # Check if increasing
    if trace_P[-1] > trace_P[0]:
        print("⚠ Filter diverging: P trace increased")

See Kalman Filter Tuning Guide for detailed diagnostics.

Filter Lags Behind Measurements

Symptom: Filter predictions are always 1-2 steps behind actual motion

Causes:

  1. Q too large (filter doesn’t trust model)

    # ❌ Q too large
    Q = np.eye(4) * 10  # Filter doubts the model
    
    # ✅ Reduce Q
    Q = np.eye(4) * 0.1
    
  2. Motion model too simple

    # If system has acceleration, need better model
    # Constant velocity model:
    F = f_constant_velocity(T, num_dims=2)
    
    # Better: constant acceleration
    F = f_constant_acceleration(T, num_dims=2)
    
  3. Measurement delay

    Ensure your time stamps are accurate:

    # Check if measurements lag
    for i, (t, z) in enumerate(zip(times, measurements)):
        delay = current_time - t
        if delay > expected_delay:
            print(f"Measurement {i} delayed by {delay}s")
    

Filter Too Jerky: Follows Noise

Symptom: Filter output wiggles even when target not moving

Cause: Q too small (filter over-trusts measurements)

Solution:

# ❌ Q too small: filter tries to match every measurement
Q = np.eye(4) * 0.001

# ✅ Increase Q to smooth
Q = np.eye(4) * 0.5

Data Association Issues

“No assignment found for measurement”

Cause: All measurements beyond gating distance

Solution:

from scipy.spatial.distance import cdist

# Check gating distance
positions = np.array([[0, 0], [1, 1]])
measurements = np.array([[10, 10], [20, 20]])  # Far away!

cost = cdist(positions, measurements)
gate = 5.0  # Maximum distance

# Mark out-of-range
cost[cost > gate] = np.inf

from pytcl.assignment.optimization import assignment_nd
assignments = assignment_nd(cost)
# Result: mostly unassigned measurements

Fix:

# 1. Increase gate threshold
gate = 10.0  # More permissive

# 2. Or check if measurements are in right coordinate frame
# Ensure measurements converted to same frame as state

Multiple incorrect assignments

Cause: Cost matrix has poor gradation between good/bad matches

Solution:

from pytcl.assignment.optimization import assignment_nd

# Compute cost matrix more carefully
# Option 1: Mahalanobis distance (includes covariance)
from scipy.spatial.distance import cdist

# Standard Euclidean
cost = cdist(positions, measurements)

# Better: Mahalanobis (weighted by covariance)
from scipy.spatial.distance import cdist
cost = cdist(positions, measurements, metric='mahalanobis')

assignments = assignment_nd(cost)

Coordinate Conversion Issues

“Result is NaN or Inf”

Cause: Invalid input to conversion function (e.g., negative range, ±π issues)

Solution:

from pytcl.coordinate_systems.conversions import cart2sphere
import numpy as np

# ❌ Invalid: origin
result = cart2sphere(np.array([0, 0, 0]))  # Undefined azimuth!

# ✅ Valid: non-zero
result = cart2sphere(np.array([1, 0, 0.1]))

# ❌ Invalid: negative range
result = cart2sphere(np.array([-1, 0, 0]))  # Range must be positive!

Check input validity:

def is_valid_spherical(spherical_coords):
    r, az, el = spherical_coords

    if r < 0:
        print("Range must be positive")
        return False

    if not (-np.pi <= az <= np.pi):
        print("Azimuth must be in [-π, π]")
        return False

    if not (-np.pi/2 <= el <= np.pi/2):
        print("Elevation must be in [-π/2, π/2]")
        return False

    return True

Angle Wrapping Issues

Problem: Angles greater than 2π or negative angles

Solution:

import numpy as np

# Wrap angle to [-π, π]
def wrap_angle(angle):
    return np.arctan2(np.sin(angle), np.cos(angle))

# Test
large_angle = 3 * np.pi
wrapped = wrap_angle(large_angle)
print(f"{large_angle}{wrapped}")  # 9.42 → -2.57

Performance Issues

“Program is too slow” or “Using too much memory”

Solution: Profile first to find bottleneck

import cProfile
import pstats

def my_tracking_loop():
    for z in measurements:
        x, P = kf_predict(x, P, F, Q)
        x, P = kf_update(x, P, z, H, R)

profiler = cProfile.Profile()
profiler.enable()
my_tracking_loop()
profiler.disable()

stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10)  # Top 10 functions

See Performance Optimization Guide for detailed optimization techniques.

“Processing measurements too slowly”

Causes and Solutions:

  1. Too many array allocations

    # ❌ Slow: allocate in loop
    for z in measurements:
        cost = np.zeros((n_targets, n_measurements))  # Allocate each time
        # ...
    
    # ✅ Fast: allocate once
    cost = np.zeros((n_targets, n_measurements))
    for z in measurements:
        cost[:] = compute_distances(...)  # Reuse
    
  2. Not vectorizing operations

    # ❌ Slow: Python loop
    for measurement in measurements:
        predict_filter(measurement)
    
    # ✅ Fast: Batch
    predict_filter_batch(measurements)
    
  3. Using GPU backend without GPU

    # If no GPU, CPU version is faster (no transfer overhead)
    import pytcl.gpu.cupy_backend as gpu
    
    # Check if GPU actually available
    try:
        gpu.check_gpu_available()
    except RuntimeError:
        print("GPU not available, using CPU")
        # Use CPU version instead
    

See Performance Optimization Guide and GPU Acceleration Guide.

“Memory usage keeps growing”

Cause: Memory leaks or unbounded data accumulation

Solution:

import tracemalloc

tracemalloc.start()

# Run your code
for i in range(1000):
    do_tracking_step()

current, peak = tracemalloc.get_traced_memory()
print(f"Current: {current / 1024 / 1024:.1f} MB")
print(f"Peak: {peak / 1024 / 1024:.1f} MB")
tracemalloc.stop()

Fix:

# Don't accumulate history indefinitely
class Tracker:
    def __init__(self, max_history=100):
        self.history = []

    def update(self, measurement):
        self.history.append(measurement)

        # Keep only recent history
        if len(self.history) > self.max_history:
            self.history = self.history[-self.max_history:]

Numerical Issues

Covariance Matrix Not Positive Definite

Symptom: Eigenvalues have negative or very small values

Cause: Numerical drift, poor initialization, or invalid update

import numpy as np

def check_positive_definite(P):
    eigs = np.linalg.eigvalsh(P)
    min_eig = np.min(eigs)

    if min_eig < 0:
        print(f"⚠ P not positive definite: min eigenvalue = {min_eig}")
        return False

    if min_eig < 1e-10:
        print(f"⚠ P nearly singular: min eigenvalue = {min_eig}")
        return False

    return True

# Check P after initialization
check_positive_definite(P)

Solution:

import numpy as np
from scipy.linalg import cholesky

# Fix: Enforce positive-definiteness
def fix_covariance(P):
    try:
        # Try Cholesky decomposition
        L = cholesky(P, lower=True)
        return L @ L.T  # Rebuild
    except np.linalg.LinAlgError:
        # If that fails, add small regularization
        epsilon = 1e-8
        P_fixed = P + epsilon * np.eye(len(P))
        return P_fixed

P = fix_covariance(P)

“Numerical underflow/overflow”

Cause: Very small or very large numbers in calculations

Solution:

import numpy as np

# Use higher precision
x = np.array([1e-300, 2e-300], dtype=np.float64)  # Not np.float32

# Or use log-space for very small numbers
log_prob = np.log(probability)  # -1000 is OK, but 1e-300 underflows

Debugging Tools

Print Debug Information:

import numpy as np
from pytcl.dynamic_estimation.kalman import kf_predict, kf_update

def debug_filter(x, P, z, F, Q, H, R):
    print(f"State before: {x}")
    print(f"Cov trace before: {np.trace(P):.4f}")

    x_p, P_p = kf_predict(x, P, F, Q)
    print(f"State after predict: {x_p}")
    print(f"Cov trace after predict: {np.trace(P_p):.4f}")

    innovation = z - H @ x_p
    print(f"Innovation: {innovation}")

    x_u, P_u = kf_update(x_p, P_p, z, H, R)
    print(f"State after update: {x_u}")
    print(f"Cov trace after update: {np.trace(P_u):.4f}")

    return x_u, P_u

Use Assertions:

import numpy as np

def track_measurement(x, P, z):
    # Assertions catch bugs early
    assert isinstance(x, np.ndarray), "x must be array"
    assert x.ndim == 1, "x must be 1D"
    assert len(x) == 4, "x must be 4D state"
    assert np.isfinite(x).all(), "x contains NaN or Inf"
    assert np.isfinite(P).all(), "P contains NaN or Inf"

    # ... rest of function

Use Logging:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def update_filter(x, P, z):
    logger.debug(f"State: {x}")
    logger.debug(f"Measurement: {z}")

    x_pred, P_pred = kf_predict(x, P, F, Q)
    logger.debug(f"After predict: {x_pred}")

    x_upd, P_upd = kf_update(x_pred, P_pred, z, H, R)
    logger.debug(f"After update: {x_upd}")

    return x_upd, P_upd

Getting Help

If you still can’t find the answer:

  1. Check the documentation at https://readthedocs.org/

  2. Review examples in examples/ folder

  3. Check similar issues on GitHub: https://github.com/nedonatelli/TCL/issues

  4. Create an issue describing: - What you’re trying to do - Error message (full traceback) - Minimal reproducible example - Your environment (Python version, pytcl version)

Minimal Reproducible Example:

# Minimal code that shows the problem
import numpy as np
from pytcl.dynamic_estimation.kalman import kf_predict

# Minimum setup to reproduce
x = np.array([0.0, 1.0, 0.0, 1.0])
P = np.eye(4) * 0.1
F = np.eye(4)
Q = np.eye(4) * 0.01

# This fails with: [error message]
x_pred, P_pred = kf_predict(x, P, F, Q)

See Also