0.1.0 RunAvgWeight

This commit is contained in:
Rob Tillaart 2024-06-30 15:59:33 +02:00
parent 668c7ce8cf
commit 272cd7e583
16 changed files with 919 additions and 0 deletions

View File

@ -0,0 +1,29 @@
platforms:
rpipico:
board: rp2040:rp2040:rpipico
package: rp2040:rp2040
gcc:
features:
defines:
- ARDUINO_ARCH_RP2040
warnings:
flags:
packages:
rp2040:rp2040:
url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
compile:
# Choosing to run compilation tests on 2 different Arduino platforms
# selected only those that work
platforms:
- uno
# - due
# - zero
# - leonardo
- m4
- esp32
- esp8266
# - mega2560
- rpipico

View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: RobTillaart
custom: "https://www.paypal.me/robtillaart"

View File

@ -0,0 +1,13 @@
name: Arduino-lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: arduino/arduino-lint-action@v1
with:
library-manager: update
compliance: strict

View File

@ -0,0 +1,17 @@
name: Arduino CI
on: [push, pull_request]
jobs:
runTest:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6
- run: |
gem install arduino_ci
arduino_ci.rb

View File

@ -0,0 +1,18 @@
name: JSON check
on:
push:
paths:
- '**.json'
pull_request:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: json-syntax-check
uses: limitusus/json-syntax-check@v2
with:
pattern: "\\.json$"

View File

@ -0,0 +1,11 @@
# Change Log RunAvgWeight
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.1.0] - 2024-06-30
- initial version, based upon **RunningAverage**

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-2024 Rob Tillaart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,121 @@
[![Arduino CI](https://github.com/RobTillaart/RunAvgWeight/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)
[![Arduino-lint](https://github.com/RobTillaart/RunAvgWeight/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/RunAvgWeight/actions/workflows/arduino-lint.yml)
[![JSON check](https://github.com/RobTillaart/RunAvgWeight/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/RunAvgWeight/actions/workflows/jsoncheck.yml)
[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/RunAvgWeight.svg)](https://github.com/RobTillaart/RunAvgWeight/issues)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/RunAvgWeight/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/RobTillaart/RunAvgWeight.svg?maxAge=3600)](https://github.com/RobTillaart/RunAvgWeight/releases)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/RunAvgWeight.svg)](https://registry.platformio.org/libraries/robtillaart/RunAvgWeight)
# RunAvgWeight
Arduino library to calculate the running average with weights by means of a circular buffer.
## Description
**Experimental**
The RunAvgWeight (RAW) object gives a running average of the last N floating point numbers,
while giving them a different weight.
This is done by adding new data to internal circular buffers, removing the oldest and
replace it by the newest.
The size of the internal buffer can be set in the constructor.
The interface of the RunAvgWeight object is strongly based upon **RunningAverage**.
However not all functions are implemented.
**Warning**
However the constant adding and subtracting when adding new elements to the RAW object
possibly introduces an ever increasing error.
In tests with **RunningAverage** class with equal weights, adding up to 1500000 numbers
this error was always small. Be aware of this.
#### Related
- https://github.com/RobTillaart/Correlation
- https://github.com/RobTillaart/GST - Golden standard test metrics
- https://github.com/RobTillaart/Histogram
- https://github.com/RobTillaart/RunningAngle
- https://github.com/RobTillaart/RunAvgWeight
- https://github.com/RobTillaart/RunningAverage
- https://github.com/RobTillaart/RunningMedian
- https://github.com/RobTillaart/statHelpers - combinations & permutations
- https://github.com/RobTillaart/printHelpers - print scientific format
- https://github.com/RobTillaart/Statistic
## Interface
```cpp
#include "RunAvgWeight.h"
```
### Constructor
- **RunAvgWeight(uint16_t size)** allocates dynamic memory, one float (4 bytes) per element.
No default size (yet).
- **~RunAvgWeight()** destructor to free the memory allocated.
### Basic
- **void clear()** empties the internal buffers.
- **void addValue(float value, float weight)** adds a new value to the object,
if the internal buffer is full, the oldest element is removed.
- **float getValue(uint16_t position)** returns the value at **position** from the additions.
Position 0 is the first one to disappear.
- **float getAverage()** iterates over all elements to get the average, slower but accurate.
Updates the variables used by **getFastAverage()** to improve its accuracy again.
- **float getFastAverage()** reuses previous calculated values, therefore faster. Accuracy can drift.
### Extended functions
- **float getStandardDeviation()** returns the standard deviation of the current content.
Needs more than one element to be calculable.
- **float getStandardError()** returns the standard error of the current content.
- **float getMin()** returns minimum since last clear, does not need to be in the buffer any more.
- **float getMax()** returns maximum since last clear, does not need to be in the buffer any more.
- **float getMinInBuffer()** returns minimum in the internal buffer.
- **float getMaxInBuffer()** returns maximum in the internal buffer.
### Admin functions
- **bool bufferIsFull()** returns true if buffer is full.
- **float getElement(uint16_t index)** get element directly from internal buffer at index. (debug)
- **uint16_t getSize()** returns the size of the internal array.
- **uint16_t getCount()** returns the number of slots used of the internal array.
## Future
#### Must
- update documentation
- keep in sync with RunningAverage
#### Should
- elaborate unit test
#### Could
#### Wont
## Support
If you appreciate my libraries, you can support the development and maintenance.
Improve the quality of the libraries by providing issues and Pull Requests, or
donate through PayPal or GitHub sponsors.
Thank you,

View File

@ -0,0 +1,253 @@
//
// FILE: RunAvgWeight.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// DATE: 2024-06-30
// PURPOSE: Arduino library to calculate the running average with weights by means of a circular buffer
// URL: https://github.com/RobTillaart/RunAvgWeight
//
// based upon: https://github.com/RobTillaart/RunningAverage
#include "RunAvgWeight.h"
RunAvgWeight::RunAvgWeight(const uint16_t size)
{
_size = size;
_values = (float*) malloc(_size * sizeof(float));
_weights = (float *) malloc(_size * sizeof(float));
if (_values == NULL) _size = 0;
if (_weights == NULL) _size = 0;
clear();
}
RunAvgWeight::~RunAvgWeight()
{
if (_values != NULL) free(_values);
if (_weights != NULL) free(_weights);
}
// resets all counters
void RunAvgWeight::clear()
{
_count = 0;
_index = 0;
_sumValues = 0.0;
_sumWeights = 0.0;
_min = NAN;
_max = NAN;
for (uint16_t i = _size; i > 0; )
{
_values[--i] = 0.0; // keeps addValue simpler
_weights[--i] = 0.0; // keeps addValue simpler
}
}
// adds a new value to the data-set
void RunAvgWeight::addValue(const float value, const float weight)
{
if ((_values == NULL) || (_weights == NULL))
{
return;
}
_sumValues -= _values[_index];
_values[_index] = value;
_sumValues += _values[_index];
_sumWeights -= _weights[_index];
_weights[_index] = weight;
_sumWeights += _weights[_index];
_index++;
if (_index == _size) _index = 0; // faster than %
// handle min max
if (_count == 0) _min = _max = value;
else if (value < _min) _min = value;
else if (value > _max) _max = value;
// update count as last otherwise if ( _count == 0) above will fail
if (_count < _size) _count++;
}
float RunAvgWeight::getValue(const uint16_t position)
{
if (_count == 0)
{
return NAN;
}
if (position >= _count)
{
return NAN; // cannot ask more than is added
}
uint16_t pos = position + _index;
if (pos >= _count) pos -= _count;
return _values[pos];
}
// returns the weight of an element if exist, NAN otherwise
// "partner" of getValue()
float RunAvgWeight::getWeight(const uint16_t position)
{
if (_count == 0)
{
return NAN;
}
if (position >= _count)
{
return NAN; // cannot ask more than is added
}
uint16_t pos = position + _index;
if (pos >= _count) pos -= _count;
return _weights[pos];
}
// returns the average of the data-set added so far, NAN if no elements.
float RunAvgWeight::getAverage()
{
if (_count == 0)
{
return NAN;
}
// OPTIMIZE local variable for sums.
_sumValues = 0;
_sumWeights = 0;
for (uint16_t i = 0; i < _count; i++)
{
_sumValues += _values[i] * _weights[i];
_sumWeights += _weights[i];
}
return _sumValues / _sumWeights; // multiplication is faster ==> extra admin
}
// the larger the size of the internal buffer
// the greater the gain with respect to getAverage()
float RunAvgWeight::getFastAverage()
{
if (_count == 0)
{
return NAN;
}
return _sumValues / _sumWeights;
}
// returns the minimum value in the buffer
float RunAvgWeight::getMinInBuffer()
{
if (_count == 0)
{
return NAN;
}
float _min = _values[0];
for (uint16_t i = 1; i < _count; i++)
{
if (_values[i] < _min) _min = _values[i];
}
return _min;
}
// returns the maximum value in the buffer
float RunAvgWeight::getMaxInBuffer()
{
if (_count == 0)
{
return NAN;
}
float _max = _values[0];
for (uint16_t i = 1; i < _count; i++)
{
if (_values[i] > _max) _max = _values[i];
}
return _max;
}
// returns the value of an element if exist, NAN otherwise
float RunAvgWeight::getElementValue(uint16_t index)
{
if (_count == 0)
{
return NAN;
}
return _values[index];
}
// returns the value of an element if exist, NAN otherwise
float RunAvgWeight::getElementWeight(uint16_t index)
{
if (_count == 0)
{
return NAN;
}
return _weights[index];
}
// What is standard deviation in weighted average context?
// https://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance
//
// https://stackoverflow.com/questions/61831353/how-can-i-calculate-weighted-standard-errors-and-plot-them-in-a-bar-plot
// pop_v = sum( w * (x-mean)^2 ) / sum( w )
// if the weights are frequency pop_v = sum( w * (x-mean)^2 ) / (sum( w ) - 1)
/*
weighted.var = function(x,w,type="reliability") {
m=weighted.mean(x,w)
if(type=="frequency"){ return( sum(w*(x-m)^2)/(sum(w)-1) ) }
else { return( sum(w*(x-m)^2)*sum(w)/(sum(w)^2-sum(w^2)) ) }
}
weighted.sem = function(x,w,...) { return( sqrt(weighted.var(x,w,...)*sum(w^2)/sum(w)^2) ) }
*/
// If buffer is empty or has only one element, return NAN.
float RunAvgWeight::getStandardDeviation()
{
if (_count <= 1) return NAN;
float average = getFastAverage();
float variance = 0;
for (uint16_t i = 0; i < _count; i++)
{
float t = _values[i] - average;
variance += t * t * _weights[i];
}
variance /= _sumWeights;
return sqrt(variance);
}
// TODO: What is standard error in weighted average context?
// see above.
// If buffer is empty or has only one element, return NAN.
float RunAvgWeight::getStandardError()
{
float temp = getStandardDeviation();
if (temp == NAN) return NAN;
float n;
if (_count >= 30) n = _count;
else n = _count - 1;
temp = temp/sqrt(n);
return temp;
}
// -- END OF FILE --

View File

@ -0,0 +1,80 @@
#pragma once
//
// FILE: RunAvgWeight.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// DATE: 2024-06-30
// PURPOSE: Arduino library to calculate the running average with weights by means of a circular buffer
// URL: https://github.com/RobTillaart/RunAvgWeight
//
// based upon: https://github.com/RobTillaart/RunningAverage
#include "Arduino.h"
#define RUNAVGWEIGHT_LIB_VERSION (F("0.1.0"))
class RunAvgWeight
{
public:
explicit RunAvgWeight(const uint16_t size);
~RunAvgWeight();
void clear();
void addValue(const float value, const float weight = 1.0);
float getValue(const uint16_t position);
float getWeight(const uint16_t position);
// getAverage() iterates over all elements.
float getAverage();
// getFastAverage() reuses previous calculated values.
float getFastAverage();
// return statistical characteristics of the running average.
// TODO: investigate correctness of these two functions.
// weight == 1
// weight <> 1
float getStandardDeviation();
float getStandardError();
// returns min/max added to the data-set since last clear.
// values without weight!
float getMin() { return _min; };
float getMax() { return _max; };
// returns min/max from the values in the internal buffer.
// values without weight!
float getMinInBuffer();
float getMaxInBuffer();
// return true if buffer is full
bool bufferIsFull() { return _count == _size; };
// helper functions.
float getElementValue(uint16_t index);
float getElementWeight(uint16_t index);
float getSumValues() { return _sumValues; };
float getSumWeights() { return _sumWeights; };
uint16_t getSize() { return _size; }
uint16_t getCount() { return _count; }
protected:
uint16_t _size;
uint16_t _count;
uint16_t _index;
float * _values;
float * _weights;
float _sumValues;
float _sumWeights;
float _min;
float _max;
};
// -- END OF FILE --

View File

@ -0,0 +1,59 @@
//
// FILE: raw_demo_equal_weight.ino
// AUTHOR: Rob Tillaart
// DATE: 2024-06-30
// PURPOSE: show working of RunAvgWeight with equal weights
// URL: https://github.com/RobTillaart/RunAvgWeight
#include "RunAvgWeight.h"
RunAvgWeight myRA(10);
int samples = 0;
void setup(void)
{
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.print("RUNAVGWEIGHT_LIB_VERSION: ");
Serial.println(RUNAVGWEIGHT_LIB_VERSION);
myRA.clear(); // explicitly start clean
for (int i = 0; i < 10; i++)
{
myRA.addValue(i * 0.01 + 1 );
// Serial.print(myRA.getCount());
// Serial.print("\t");
// Serial.print(myRA.getAverage(), 3);
// Serial.print("\t");
Serial.print(myRA.getStandardDeviation(), 3);
Serial.print("\t");
Serial.println(myRA.getMaxInBuffer(), 3);
}
}
void loop(void)
{
long rn = random(0, 1000);
myRA.addValue(rn * 0.001);
samples++;
Serial.print(samples);
Serial.print("\t Running Average: ");
Serial.println(myRA.getAverage(), 3);
if (samples == 300)
{
samples = 0;
myRA.clear();
Serial.println();
}
delay(10);
}
// -- END OF FILE --

View File

@ -0,0 +1,95 @@
//
// FILE: raw_test.ino
// AUTHOR: Rob Tillaart
// DATE: 2024-06-30
// PURPOSE: show working of RunAvgWeight
// URL: https://github.com/RobTillaart/RunAvgWeight
#include "RunAvgWeight.h"
RunAvgWeight myRA(10);
int samples = 0;
void setup(void)
{
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
Serial.print("RUNAVGWEIGHT_LIB_VERSION: ");
Serial.println(RUNAVGWEIGHT_LIB_VERSION);
Serial.println();
myRA.clear(); // explicitly start clean
for (int i = 0; i < 20; i++)
{
float value = i * 0.01 + 1;
float weight = sqrt(i);
myRA.addValue(value, weight);
Serial.print(myRA.bufferIsFull());
}
Serial.println();
Serial.println();
for (int i = 0; i < 10; i++)
{
Serial.print(i);
Serial.print("\t");
Serial.print(myRA.getValue(i), 3);
Serial.print("\t");
Serial.print(myRA.getWeight(i), 3);
Serial.println();
}
Serial.println();
Serial.print("SIZE:\t");
Serial.println(myRA.getSize());
Serial.print("COUNT:\t");
Serial.println(myRA.getCount());
Serial.println();
Serial.print("AVG:\t");
Serial.println(myRA.getAverage(), 5);
Serial.print("AVG(f):\t");
Serial.println(myRA.getFastAverage(), 5);
Serial.println();
Serial.print("STDDEV:\t");
Serial.println(myRA.getStandardDeviation(), 5);
Serial.print("ERROR:\t");
Serial.println(myRA.getStandardError(), 5);
Serial.println();
Serial.print("MIN:\t");
Serial.println(myRA.getMin(), 3);
Serial.print("MININB:\t");
Serial.println(myRA.getMinInBuffer(), 3);
Serial.println();
Serial.print("MAX:\t");
Serial.println(myRA.getMax(), 3);
Serial.print("MAXINB:\t");
Serial.println(myRA.getMaxInBuffer(), 3);
Serial.println();
Serial.print("SUMVAL:\t");
Serial.println(myRA.getSumValues(), 3);
Serial.print("SUMWGT:\t");
Serial.println(myRA.getSumWeights(), 3);
Serial.print("RATIO:\t");
Serial.println(myRA.getSumValues() / myRA.getSumWeights(), 3);
Serial.println();
}
void loop(void)
{
}
// -- END OF FILE --

View File

@ -0,0 +1,39 @@
# Syntax Colouring Map For RunningAverage
# Data types (KEYWORD1)
RunAvgWeight KEYWORD1
# Methods and Functions (KEYWORD2)
clear KEYWORD2
addValue KEYWORD2
getValue KEYWORD2
getWeight KEYWORD2
getAverage KEYWORD2
getFastAverage KEYWORD2
getStandardDeviation KEYWORD2
getStandardError KEYWORD2
getMin KEYWORD2
getMax KEYWORD2
getMinInBuffer KEYWORD2
getMaxInBuffer KEYWORD2
bufferIsFull KEYWORD2
getElementValue KEYWORD2
getElementWeight KEYWORD2
getSumValues KEYWORD2
getSumWeights KEYWORD2
getSize KEYWORD2
getCount KEYWORD2
# Instances (KEYWORD2)
# Constants (LITERAL1)
RUNAVGWEIGHT_LIB_VERSION LITERAL1

View File

@ -0,0 +1,23 @@
{
"name": "RunAvgWeight",
"keywords": "running average, moving average, average",
"description": "Running Average with weight of N elements. Supports min max average.",
"authors":
[
{
"name": "Rob Tillaart",
"email": "Rob.Tillaart@gmail.com",
"maintainer": true
}
],
"repository":
{
"type": "git",
"url": "https://github.com/RobTillaart/RunAvgWeight.git"
},
"version": "0.1.0",
"license": "MIT",
"frameworks": "*",
"platforms": "*",
"headers": "RunAvgWeight.h"
}

View File

@ -0,0 +1,11 @@
name=RunAvgWeight
version=0.1.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Running Average with weight of N elements.
paragraph=Supports min max average.
category=Data Processing
url=https://github.com/RobTillaart/RunAvgWeight
architectures=*
includes=RunAvgWeight.h
depends=

View File

@ -0,0 +1,125 @@
//
// FILE: unit_test_001.cpp
// AUTHOR: Rob Tillaart
// VERSION: 0.1.0
// DATE: 2024-06-30
// PURPOSE: unit tests for RunAvgWeight
// https://github.com/RobTillaart/RunAvgWeight
//
// supported assertions
// ----------------------------
// assertEqual(expected, actual); // a == b
// assertNotEqual(unwanted, actual); // a != b
// assertComparativeEquivalent(expected, actual); // abs(a - b) == 0 or (!(a > b) && !(a < b))
// assertComparativeNotEquivalent(unwanted, actual); // abs(a - b) > 0 or ((a > b) || (a < b))
// assertLess(upperBound, actual); // a < b
// assertMore(lowerBound, actual); // a > b
// assertLessOrEqual(upperBound, actual); // a <= b
// assertMoreOrEqual(lowerBound, actual); // a >= b
// assertTrue(actual);
// assertFalse(actual);
// assertNull(actual);
// // special cases for floats
// assertEqualFloat(expected, actual, epsilon); // fabs(a - b) <= epsilon
// assertNotEqualFloat(unwanted, actual, epsilon); // fabs(a - b) >= epsilon
// assertInfinity(actual); // isinf(a)
// assertNotInfinity(actual); // !isinf(a)
// assertNAN(arg); // isnan(a)
// assertNotNAN(arg); // !isnan(a)
#include <ArduinoUnitTests.h>
#include "Arduino.h"
#include "RunAvgWeight.h"
unittest_setup()
{
fprintf(stderr, "RUNAVGWEIGHT_LIB_VERSION: %s\n", (char *) RUNAVGWEIGHT_LIB_VERSION);
}
unittest_teardown()
{
}
unittest(test_zero_elements)
{
RunAvgWeight myRA(10);
myRA.clear();
int size = myRA.getSize();
assertEqual(10, size);
int cnt = myRA.getCount();
assertEqual(0, cnt);
float x = myRA.getAverage();
assertNAN(x);
}
unittest(test_min_max)
{
RunAvgWeight myRA(10);
myRA.clear();
for (int i = -5; i < 6; i++)
{
myRA.addValue(i);
}
float mi = myRA.getMin();
assertEqual(-5, mi);
float ma = myRA.getMax();
assertEqual(5, ma);
mi = myRA.getMinInBuffer();
assertEqual(-4, mi);
ma = myRA.getMaxInBuffer();
assertEqual(5, ma);
}
unittest(test_buffer_full)
{
RunAvgWeight myRA(10);
myRA.clear();
assertFalse(myRA.bufferIsFull());
for (int i = 0; i < 9; i++)
{
myRA.addValue(i);
assertFalse(myRA.bufferIsFull());
}
myRA.addValue(42);
assertTrue(myRA.bufferIsFull());
}
unittest(test_large)
{
RunAvgWeight myRA(300);
myRA.clear();
assertFalse(myRA.bufferIsFull());
for (int i = 0; i < 299; i++)
{
myRA.addValue(i);
assertFalse(myRA.bufferIsFull());
}
myRA.addValue(42);
assertTrue(myRA.bufferIsFull());
}
unittest_main()
// --------