Math Question

To smooth out temperature readings in a control system, I was playing with a couple of averaging techniques and put them to a side by side test to see how they worked. They both did well at smoothing out the averaged values, but I thought it would be interesting to find a mathematician to shed some light on how they differ in response to changing temperatures.

//Averaging #1
If sensor_Avg > 0 Then
sensor_Avg = ((sensor_Avg * 10)+ Sensor_Value)/11
Else
sensor_Avg = Sensor_Value
End
Return sensor_Avg

//Averaging #2 (10 numbers in array divided by 10)
Var v As Double = 0.0
If Data.Count = 0 Then Return v
For Each d As Double In Data // Data is array property
v = v + d
Next
Var c As Double = Data.Count
Return v / c

Average #1 will never be accurate. Given enough values, it will start to converge on accurate, but never make it. It may be “close enough” for your purposes.

Average #2 will be accurate, but will only represent the last x data points. You will need to decide if you want a rolling average of the last x readings, or if you want a longer term average and keep more values.

1 Like

The first type is what is known as an Infinite Impulse Response (IIR) filter, and the second is a Finite Impulse Response (FIR) filter. The difference is that the IIR filter has the output signal fed back into the calculation, and the FIR filter does not.

More info here:

1 Like

If you don’t want to keep all the separate values, you could save the number of values represented by the average.

if sensor_count > 0 then
   sensor_avg = ((sensor_avg * 10) + sensor_value) / (sensor_count + 1)
else
   sensor_avg = sensor_value
end
sensor_count = sensor_count + 1
1 Like

Very interesting analysis of FIR and IIR filters. I must say it is interesting to watch the results side by side in real life. I do believe you are correct Tim in believing the FIR would be a little more accurate but they both track in their way with an ebb and tide fashion.

That would be another approach but since temperature measurements are retained and never ending I would think sensor_count might get out of hand.

A bit of background: Traditionally, when filters were implemented in analog hardware we used IIR filters because FIR filters were impractical to implement. However, FIR filters offer many advantages over IIR filters, and are the more popular choice in digital signal processing.

The usual method of implementing a moving average filter is to use a circular buffer:

Public Function MovingAverage(sample As Double) as Double
  'Calculate a 10 sample moving average
  '(The first 9 samples are averaged correctly using the proper divisor)
  Static buffer(10) As Double
  static sampleCount As Integer = 0
  static bPtr As Integer = 0
  'Add new sample to buffer
  buffer(bPtr) = sample
  sampleCount = min(10,sampleCount+1)
  'Calculate average
  dim sSum As Double = 0
  for i as Integer = 0 to sampleCount-1
    sSum = sSum + buffer(i)
  next
  'Increment buffer pointer (wrapping around from 9 to 0)
  bPtr=(bPtr+1) mod 10
  return sSum/sampleCount
End Function
1 Like

I think you want a rolling average.

VAR SampleSize AS INTEGER = 10 ' The number of samples in the rolling average
VAR NewSample AS INTEGER ' The next sample to be factored in the rolling average
VAR RollingAverage AS INTEGER = 0
STATIC SampleData (-1) AS INTEGER ' The array of sampled data points in the rolling average

SampleData.AddRow (NewSample) ' Add the latest data point
IF SampleData.Ubound > SampleSize - 1 THEN ' If the data points exceed the sample size, delete the oldest data point
  SampleData.RemoveRowAt (0)
END IF

IF SampleData.Ubound = SampleSize - 1 THEN ' Report the average only if the sample size is complete
  FOR i AS INTEGER = 0 to SampleSize - 1
    RollingAverage = RollingAverage + SampleData (i)
  NEXT i
  
  RollingAverage = RollingAverage / SampleSize
  
  RETURN RollingAverage ' Return the rolling average
  
ELSE
  RETURN 0 ' Sample size not full
END IF

see the lerp method there.
https://en.wikipedia.org/wiki/Linear_interpolation
it make a transition between last and new value.

I wanted to implement this circular buffer to see how it compared in an A/B test to a rolling average. I am having some difficulty when creating multiple sensor instances because the buffer(10) ends up containing all the sensor values rather than 10 values of the same sensor. I’m not sure if it’s an issue using static variables or my technique. Without over detailing these are my basic steps. I created two-class, Temp_Sensors which holds my read operations, averaging, and conversion methods, and Temp_Sensor which is a subclass of Temp_Sensors and has a constructor in which sensor instances send properties about the sensor. There are four flavors that require different processing. My constructor looks like this:

Self.Sensor_Name = Sensor_Name
Self.Sensor_Number = Sensor_Number
Self.Sensor_Bank = Sensor_Bank
Self.Sensor_Conversion_Type = Sensor_Conversion_Type

The instance of the sensors sent to the constructor look like this:
Sensors.Append(New Temp_Sensor(“Storage Area”,2,“PWA2”,“LowTemp”))
Sensors.Append(New Temp_Sensor(“Floor Intake Temp”,3,“PWA2”,“LowTemp”))
Sensors.Append(New Temp_Sensor(“Ceiling Temp”,4,“PWA2”,“LowTemp”))
Sensors.Append(New Temp_Sensor(“Kitchen Temp”,5,“PWA2”,“LowTemp”))
Sensors.Append(New Temp_Sensor(“Upstairs Temp”,6,“PWA2”,“LowTemp”))
Sensors.Append(New Temp_Sensor(“Bedroom Temp”,7,“PWA2”,“LowTemp”))

Sensors.Append(New Temp_Sensor(“Boiler Temp”,0,“PWA8”,“HighTemp”))
Sensors.Append(New Temp_Sensor(“Loop Pump Temp”,1,“PWA8”,“HighTemp”))
Sensors.Append(New Temp_Sensor(“DHW Tank Temp”,2,“PWA8”,“HighTemp”))
Sensors.Append(New Temp_Sensor(“Boiler Line Temp”,3,“PWA8”,“HighTemp”))
Sensors.Append(New Temp_Sensor(“Outside Temp”,7,“PWA8”,“HighTemp”))

Sensors.Append(New Temp_Sensor(“Cloud Sensor”,8,“PWA8”,“HighLight”))
Sensors.Append(New Temp_Sensor(“Dusk Sensor”,9,“PWA8”,“Dusk”))

Sensors.Append(New Temp_Sensor(“Cal Sensor 14”,14,“PWA8”,“None”))
Sensors.Append(New Temp_Sensor(“Cal Sensor 15”,9,“PWA8”,“None”))

The circular averaging method in Temp_Sensors looks like this:

Static buffer(10) As Double
Static sampleCount As Integer = 0
Static bPtr As Integer = 0

buffer(bPtr) = Sensor_Value
sampleCount = Min(10,sampleCount+1)

Dim sSum As Double = 0
For i As Integer = 0 To sampleCount-1
sSum = sSum + buffer(i)
Next

bPtr=(bPtr+1) Mod 10
Sensor_CircularAvg = sSum/sampleCount

I was thinking (Static buffer(10)) in the MovingAverage method in Temp_Sensors would hold a buffer for each individual sensor and that is not the case.

I need to have each static buffer(10) hold only one sensors values.

Make it a property of the class, not a static variable. Statics are shared between all instances.

I wasn’t familiar with Statics so thanks for letting me know they are shared.

Yes. Sorry about that. I should have pointed out that the my routine can only be used for averaging a single input source. Making it a class is the proper way to do it. Change the static variables to properties of the class. The rest of the code should then work correctly.

No issue, it’s all a wonderful learning curve for me.

I would say this is highly dependent on what you are actually measuring and how different the readings can actually be between samples. For example, measuring water temperature where any significant fluctuation could take a few seconds versus measuring the exhaust gas of an internal combustion engine or gas turbine where the temperature can swing a few thousand degrees in a second.

Both of those scenarios will present very differently in the two methods above and knowing this might dictate what method you use.

If using to control something it might be worth reviewing PID controller functions. https://en.wikipedia.org/wiki/PID_controller

The main concern with these temperature readings is to smooth out temperature reading errors that would cause values to overshoot the built-in software hysteresis in-turn causing outputs to oscillate between on and off. Not rocket science but I see averaging is an interesting subject.

When someone with a good understanding of proportional–integral–derivative controllers wants to take on an interesting project, I’m looking for a software algorithm that takes into account passive solar gain and energy requirements for a home that incorporates a radiant floor zoned heating system. Adjustable by a mixing valve and controlled by software heated water is pumped into the homes’ floor heating system. The challenge is to anticipate solar gain to not overshoot desired room temps when the sun shines through early in the day. The floor has a considerable mass so anticipation is desirable. Currently, I use two light sensors, one sensitive to cloud density that helps anticipate potential radiant gain and one for oncoming daylight. Other sensors that come into play are outdoor and indoor room temperature sensors for determining temperature differential and energy requirements. Another consideration is the time of year which affects solar gain. I’m currently using an algorithm I built years ago but I thought it would be interesting to design a utility window that incorporated adjustable sliders to better tweak the algorithm in real-time. ANYONE NEED A PROJECT?

The algorithm looks like this so for. Each of these inputs is given a weight and is additive to the next input. Room temps added together and averaged get 0-15, outside light or cloud density gets 0-8 and outside temp gets 0-10. All of these input weights are adjustable with a popup menu to allow testing different values. The inputs are also limited in usable ranges with a clever formula Thom McGrath gave me Value = Max(Min(Value, 70), 30). The values are added together and limited to 15 since this is the max for the boiler input range of 0 - 15. It’s been working very well but I’m still curious how to add the azimuth of the sun to the light input since it affects the sun penetrate into the rooms at different times of the year.
2020-11-15_11-03-22

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.