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:
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.
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
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)
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:
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
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)
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:
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
Not vectorizing operations
# ❌ Slow: Python loop for measurement in measurements: predict_filter(measurement) # ✅ Fast: Batch predict_filter_batch(measurements)
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:
Check the documentation at https://readthedocs.org/
Review examples in
examples/folderCheck similar issues on GitHub: https://github.com/nedonatelli/TCL/issues
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
Kalman Filter Tuning Guide - Filter parameter tuning
Performance Optimization Guide - Performance profiling
GPU Acceleration Guide - GPU acceleration setup
API Navigation Guide - Function discovery
Examples:
examples/folder