Indirect Temperature Estimation Using Kalman Filter (Two Sensors)

Tran Hoai Nhan — Le Hong Nhat Tan

Probability Course Fall 2025 — Professor: Tran Vinh Linh

Problem Overview

In Computational Fluid Dynamics (CFD) simulations of building environments, accurate boundary conditions are critical. Dr. Nguyen Hop Minh's experimental setup requires precise knowledge of a hot bulb's surface temperature (200–300°C) to validate CFD models. However, direct measurement is impractical—sensors would melt at such temperatures.

We developed an indirect measurement approach using two air temperature sensors positioned at different distances from the heat source, combining their readings through a Kalman Filter to estimate the bulb's temperature.

Methodology

Experimental Setup

Real experimental setup

Figure 1a: Real experimental setup showing the heat source box with two ambient temperature sensors at different distances.

Sensor placement diagram

Figure 1b: Schematic diagram - Hot bulb (center) radiates heat to Sensor A (close, dA = 5 cm) and Sensor B (far, dB = 15 cm).

Sensor A (Close): Positioned at dA = 5 cm from the bulb. Captures near-field temperature with higher signal but more noise (RA = 2.0). Experiences stronger thermal fluctuations due to convection effects.

Sensor B (Far): Positioned at dB = 15 cm from the bulb. Measures far-field temperature with lower signal but higher stability (RB = 0.5). Provides reliable baseline measurements.

Mathematical Framework: Bayesian Inference

The Kalman Filter implements optimal Bayesian estimation using Gaussian distributions:

Prior Belief: p(x) = N(x; x̂, P)
where x = Tbulb is the hidden state (true bulb temperature)
Likelihood: p(z|x) = N(z; Hx, R)
where z = sensor measurement, H = observation coefficient, R = measurement noise variance
Posterior (Bayes' Rule): p(x|z) ∝ p(z|x) · p(x)
Results in: p(x|z) = N(x; μpost, σ²post)

The Kalman Filter computes the posterior mean (μpost) and variance (σ²post) in closed form through two steps:

Heat Diffusion Model

We model heat diffusion from the bulb using a distance-based attenuation formula with characteristic length ℓ = 10 cm:

Tsensor(d) = Tbulb/(1 + d/ℓ) + noise

This leads to the observation model z = Hx + v where:

HA = 1/(1 + dA/ℓ) = 1/(1 + 5/10) = 1/1.5 ≈ 0.667 (Sensor A attenuation factor)
HB = 1/(1 + dB/ℓ) = 1/(1 + 15/10) = 1/2.5 = 0.4 (Sensor B attenuation factor)

where vA ~ N(0, RA = 2.0°C²) and vB ~ N(0, RB = 0.5°C²) represent Gaussian measurement noise. The process noise Q = 0.1°C² accounts for natural bulb temperature fluctuations.

CSV Data Input: The raw measurements come from temperature_data.csv with columns:

zA = Middle_Heat_Source (Sensor A, dA = 5 cm)
zB = Air_Tube_Output (Sensor B, dB = 15 cm)
Sensor Placement Diagram

Figure: Schematic diagram showing sensor geometry and distances from the heat source.

Kalman Filter Algorithm

Prediction Step (Prior Propagation)

Purpose: Propagate previous estimate forward in time, accounting for process uncertainty.

pred = x̂prev (assume bulb temp stays constant)
Ppred = Pprev + Q (add process noise: uncertainty grows)

Interpretation: Since we have no control input, the best prediction is the previous estimate. However, uncertainty increases by Q = 0.1°C² due to natural temperature fluctuations. This gives the prior p(x) = N(x; x̂pred, Ppred) before incorporating new measurements.

Update Step (Posterior via Bayes)

Purpose: Fuse prediction with sensor measurements to reduce uncertainty.

Sensor A Update: Combine prior p(x) = N(x; x̂pred, Ppred) with likelihood p(zA|x) = N(zA; HAx, RA)

KA = PpredHA/(HAPpredHA + RA) = Ppred/(Ppred + RA) (Kalman Gain: optimal weight)
A = x̂pred + KA(zA - HApred) (weighted average of prediction & measurement)
PA = (1 - KAHA)Ppred (uncertainty reduced by measurement)

Key Insight: KA balances trust in prediction vs measurement:

Sensor B Update: Sequentially update with second sensor p(zB|x) = N(zB; HBx, RB)

KB = PA/(PA + RB) (Kalman Gain for Sensor B)
new = x̂A + KB(zB - HBA) (final posterior mean)
Pnew = (1 - KBHB)PA (final posterior variance)

Result: Final estimate x̂new optimally fuses both sensors. Since RB < RA (Sensor B more reliable), it receives higher weight in fusion.

Experimental Results

Experimental results graph

Figure 2: Real experimental data over 600 seconds showing Sensor A (orange), Sensor B (blue), and Kalman Filter estimate (green).

Data Statistics

Sensor A (Close)

36.4 ± 2.1°C

Sensor B (Far)

30.5 ± 0.6°C

Bulb Estimate

67.2 ± 2.2°C

Key Observations

Understanding Sensor Noise Impact

What Does High Noise Mean?

The noise parameters RA and RB represent the variance of the measurement error. High noise means the sensor readings have large random fluctuations around the true value.

🔴 High Noise Scenario (RA = 10.0, RB = 8.0)

What Happens:

Mathematical Impact: With high R, the Kalman Gain K ≈ 0, so the update equation becomes:

new ≈ x̂pred + 0 × (z - Hx̂pred) ≈ x̂pred

This means the filter ignores the measurements and just uses its prediction!

🟢 Low Noise Scenario (RA = 0.1, RB = 0.05)

What Happens:

Mathematical Impact: With low R, the Kalman Gain K ≈ 1, so:

new ≈ x̂pred + 1 × (z - Hx̂pred) ≈ z

The filter directly uses the measurements!

⚖️ Balanced Noise (Our Setup: RA = 2.0, RB = 0.5)

Optimal Trade-off:

Key Insight: The Kalman Filter is self-tuning — it automatically adjusts how much to trust each sensor based on their noise levels!

Why This Matters for Our Application

In the building environment experiment:

Interactive Simulation Tool

Try the Kalman Filter with your own parameters or simulate custom sensor data:

Configure Parameters

Measurement Distributions (Random Variables)

Each sensor measurement is a random variable following a Gaussian distribution N(μ, R). Use the slider to see the distribution at different time steps:

t = 0

Python Implementation

Below is the complete Python code used to analyze the experimental data. This implementation matches the mathematical framework described above.

import numpy as np
import pandas as pd

# Load data
data = pd.read_csv('temperature_data.csv')
time = data['Time'].values
sensor_a = data['Middle_Heat_Source'].values  # Close to heat source
sensor_b = data['Air_Tube_Output'].values     # Farther from heat source

# Kalman Filter parameters (simulating bulb temperature estimation)
# True bulb temperature would be higher than both measurements
Q = 0.1  # Process noise
R_A = 2.0  # Sensor A noise (more noisy, closer)
R_B = 0.5  # Sensor B noise (less noisy, farther)

# Distance factors (heat diffusion model)
d_A = 5.0  # cm, close to bulb
d_B = 15.0  # cm, farther from bulb

# Initialize
x_est = 45.0  # Initial bulb temperature estimate
P = 10.0  # Initial uncertainty

# Store results
bulb_estimates = []

# Kalman Filter loop
for i in range(len(time)):
    # Prediction step
    x_pred = x_est
    P_pred = P + Q
    
    # Update with Sensor A (accounting for distance)
    H_A = 1.0 / (1 + d_A/10)  # Observation model (heat decreases with distance)
    innovation_A = sensor_a[i] - H_A * x_pred
    S_A = H_A * P_pred * H_A + R_A
    K_A = P_pred * H_A / S_A
    x_est = x_pred + K_A * innovation_A
    P = (1 - K_A * H_A) * P_pred
    
    # Update with Sensor B
    H_B = 1.0 / (1 + d_B/10)  # Heat decreases more with distance
    innovation_B = sensor_b[i] - H_B * x_est
    S_B = H_B * P * H_B + R_B
    K_B = P * H_B / S_B
    x_est = x_est + K_B * innovation_B
    P = (1 - K_B * H_B) * P
    
    bulb_estimates.append(x_est)

# Save results
results = pd.DataFrame({
    'Time': time,
    'Sensor_A_Close': sensor_a,
    'Sensor_B_Far': sensor_b,
    'Bulb_Estimate': bulb_estimates
})
results.to_csv('kalman_results.csv', index=False)

# Calculate statistics
print('=== Temperature Statistics ===')
print(f'Sensor A (Close): Mean={sensor_a.mean():.2f}C, Std={sensor_a.std():.2f}C')
print(f'Sensor B (Far): Mean={sensor_b.mean():.2f}C, Std={sensor_b.std():.2f}C')
print(f'Bulb Estimate: Mean={np.mean(bulb_estimates):.2f}C, Std={np.std(bulb_estimates):.2f}C')
print(f'\nAt t=100s: Sensor A={sensor_a[10]:.1f}C, Sensor B={sensor_b[10]:.1f}C, Estimate={bulb_estimates[10]:.1f}C')
print(f'At t=300s: Sensor A={sensor_a[30]:.1f}C, Sensor B={sensor_b[30]:.1f}C, Estimate={bulb_estimates[30]:.1f}C')
print(f'At t=600s: Sensor A={sensor_a[60]:.1f}C, Sensor B={sensor_b[60]:.1f}C, Estimate={bulb_estimates[60]:.1f}C')

Code Explanation

Data Loading

The CSV file contains three columns: Time, Middle_Heat_Source (Sensor A), and Air_Tube_Output (Sensor B). These represent measurements from the building environment performance evaluation box.

Parameters

Kalman Filter Loop

For each time step, the algorithm performs:

  1. Prediction: Propagate previous estimate forward, add process noise to uncertainty
  2. Sensor A Update: Compute innovation (measurement - prediction), calculate Kalman Gain, update estimate and uncertainty
  3. Sensor B Update: Sequentially apply second sensor measurement using updated state from Sensor A

Output

The code produces a CSV file with all estimates and prints statistics showing the bulb temperature is estimated at 67.2°C on average, significantly higher than both sensor readings (36.4°C and 30.5°C).

Discussion

This project demonstrates three key concepts:

Future work could incorporate thermal dynamics modeling (heat capacity, cooling rates) and extend to multi-sensor arrays for spatial temperature field reconstruction.

References

[1] Kalman, R. E. (1960). "A New Approach to Linear Filtering and Prediction Problems." Transactions of the ASME–Journal of Basic Engineering, 82(Series D): 35-45.
[2] Welch, G., & Bishop, G. (2006). "An Introduction to the Kalman Filter." University of North Carolina at Chapel Hill, Department of Computer Science, TR 95-041.
[3] Simon, D. (2006). Optimal State Estimation: Kalman, H∞, and Nonlinear Approaches. Wiley-Interscience.