0.2.0 MultiMap

This commit is contained in:
Rob Tillaart 2023-11-13 12:13:02 +01:00
parent 2a20a25190
commit 78669581ce
14 changed files with 396 additions and 85 deletions

View File

@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.2.0] - 2023-11-12
- add multi type **multiMap<T1, T2>** to reduce RAM and speed up lookup.
- add multi type **multiMapBS<T1, T2>** binary search version.
- add example for multi type
- update examples
- update readme.md
- minor edits
----
## [0.1.7] - 2023-06-24
- add **multiMapCache()**, experimental version that caches the last value.
to be used with input that do not change often.
@ -14,7 +24,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- add examples
- major rewrite readme.md
## [0.1.6] - 2022-11-17
- add RP2040 in build-CI
- add changelog.md

View File

@ -2,7 +2,7 @@
//
// FILE: MultiMap.h
// AUTHOR: Rob Tillaart
// VERSION: 0.1.7
// VERSION: 0.2.0
// DATE: 2011-01-26
// PURPOSE: Arduino library for fast non-linear mapping or interpolation of values
// URL: https://github.com/RobTillaart/MultiMap
@ -10,12 +10,16 @@
#define MULTIMAP_LIB_VERSION (F("0.1.7"))
#define MULTIMAP_LIB_VERSION (F("0.2.0"))
#include "Arduino.h"
////////////////////////////////////////////////////////////////////////
//
// SINGLE TYPE MULTIMAP - LINEAR SEARCH - the reference
//
// note: the in array must have increasing values
template<typename T>
T multiMap(T value, T* _in, T* _out, uint8_t size)
@ -36,11 +40,14 @@ T multiMap(T value, T* _in, T* _out, uint8_t size)
}
////////////////////////////////////////////////////////////////////////
//
// SINGLE TYPE MULTIMAP CACHE - LINEAR SEARCH
//
// note: the in array must have increasing values
// performance optimized version if inputs do not change often
// e.g. 2 2 2 2 2 3 3 3 3 5 5 5 5 5 5 8 8 8 8 5 5 5 5 5
// implements a minimal cache of the lastValue.
//
// note: the in array must have increasing values
template<typename T>
T multiMapCache(T value, T* _in, T* _out, uint8_t size)
{
@ -83,18 +90,74 @@ T multiMapCache(T value, T* _in, T* _out, uint8_t size)
}
// binary search version, should be faster for size > 10
////////////////////////////////////////////////////////////////////////
//
// SINGLE TYPE MULTIMAP - BINARY SEARCH
//
// should be faster for size >= 10
// (rule of thumb)
//
// note: the in array must have increasing values
template<typename T>
T multiMapBS(T value, T* _in, T* _out, uint16_t size)
T multiMapBS(T value, T* _in, T* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];
// Binary Search
// Binary Search, uint16_t needed to prevent overflow.
uint16_t lower = 0;
uint16_t upper = size - 1;
while (lower < upper - 1)
{
uint8_t mid = (lower + upper) / 2;
if (value >= _in[mid]) lower = mid;
else upper = mid;
}
return (value - _in[lower]) * (_out[upper] - _out[lower]) / (_in[upper] - _in[lower]) + _out[lower];
}
////////////////////////////////////////////////////////////////////////
//
// MULTITYPE MULTIMAP - LINEAR SEARCH
//
// note: the in array must have increasing values
template<typename T1, typename T2>
T2 multiMap(T1 value, T1* _in, T2* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];
// search right interval
uint16_t pos = 1; // _in[0] already tested
while(value > _in[pos]) pos++;
// this will handle all exact "points" in the _in array
if (value == _in[pos]) return _out[pos];
// interpolate in the right segment for the rest
return (value - _in[pos-1]) * (_out[pos] - _out[pos-1]) / (_in[pos] - _in[pos-1]) + _out[pos-1];
}
////////////////////////////////////////////////////////////////////////
//
// MULTITYPE MULTIMAP - BINARY SEARCH
// should be faster for size >= 10
// (rule of thumb)
//
// note: the in array must have increasing values
template<typename T1, typename T2>
T2 multiMapBS(T1 value, T1* _in, T2* _out, uint8_t size)
{
// output is constrained to out array
if (value <= _in[0]) return _out[0];
if (value >= _in[size-1]) return _out[size-1];
// Binary Search, uint16_t needed to prevent overflow.
uint16_t lower = 0;
uint16_t upper = size - 1;
while (lower < upper - 1)

View File

@ -2,8 +2,11 @@
[![Arduino CI](https://github.com/RobTillaart/MultiMap/workflows/Arduino%20CI/badge.svg)](https://github.com/marketplace/actions/arduino_ci)
[![Arduino-lint](https://github.com/RobTillaart/MultiMap/actions/workflows/arduino-lint.yml/badge.svg)](https://github.com/RobTillaart/MultiMap/actions/workflows/arduino-lint.yml)
[![JSON check](https://github.com/RobTillaart/MultiMap/actions/workflows/jsoncheck.yml/badge.svg)](https://github.com/RobTillaart/MultiMap/actions/workflows/jsoncheck.yml)
[![GitHub issues](https://img.shields.io/github/issues/RobTillaart/MultiMap.svg)](https://github.com/RobTillaart/MultiMap/issues)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/RobTillaart/MultiMap/blob/master/LICENSE)
[![GitHub release](https://img.shields.io/github/release/RobTillaart/MultiMap.svg?maxAge=3600)](https://github.com/RobTillaart/MultiMap/releases)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/robtillaart/library/MultiMap.svg)](https://registry.platformio.org/libraries/robtillaart/MultiMap)
# MultiMap
@ -39,7 +42,25 @@ Of course this approximation introduces an error.
By increasing the number of points and choose their position strategically the average error will be reduced.
Note: some functions are hard to approximate with multiMap as they go to infinity or have a singularity.
Think of **tan(x)** around x = PI/2 (90°) or **sin(1/x)** around zero.
Think of **tan(x)** around x = PI/2 (90°) or **sin(1/x)** around zero.
#### Related
Other mapping libraries
- https://github.com/RobTillaart/FastMap
- https://github.com/RobTillaart/Gamma
- https://github.com/RobTillaart/map2colour
- https://github.com/RobTillaart/moduloMap
- https://github.com/RobTillaart/MultiMap
## Interface
```cpp
#include "MultiMap.h"
```
#### Usage
@ -66,7 +87,7 @@ This is a explicit difference with the **map()** function.
Therefore it is important to extend the range of the arrays to cover all possible values.
#### Performance
## Performance
**multiMap()** does a linear search for the inputValue in the inputArray.
This implies that usage of larger and more precise arrays will take more time.
@ -96,7 +117,8 @@ Experimental 0.1.7 => use with care.
**multiMapCache()** MMC for short, is a very similar function as **multiMap()**.
The main difference is that MMC caches the last input and output value.
The goal is to improve the performance by preventing
The goal is to improve the performance by preventing searching the same
value again and again.
If the input sequence has a lot of repeating values e.g. 2 2 2 2 2 2 5 5 5 5 5 4 4 4 4 2 2 2 2 2 2
MMC will be able to return the value from cache often.
@ -104,16 +126,50 @@ Otherwise keeping cache is overhead.
Be sure to do your own tests to see if MMC improves your performance.
A possible variation is to cache the last interval - lower and upper index.
It would allow a to test that value and improve the linear search.
(to be investigated).
#### Related
Other mapping libraries
#### MultiMap two types
- https://github.com/RobTillaart/FastMap
- https://github.com/RobTillaart/Gamma
- https://github.com/RobTillaart/map2colour
- https://github.com/RobTillaart/moduloMap
- https://github.com/RobTillaart/MultiMap
Experimental 0.2.0 => use with care.
**multiMap<T1, T2>()** MMTT for short, is a very similar function as **multiMap()**.
The main difference is that MMTT uses two different types, typical the input
is an integer type and the output is a float or double type.
It is expected that there will be a gain if two different sized integer types are used.
This is not tested.
See the example **multimap_distance_two_types.ino**
```cpp
// for a sharp distance range finder
float sharp2cm2(int val)
{
// out[] holds the distances in cm
float out[] = {150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20};
// in[] holds the measured analogRead() values for that distance
int in[] = { 90, 97, 105, 113, 124, 134, 147, 164, 185, 218, 255, 317, 408, 506};
float dist = multiMap<int, float>(val, in, out, 14);
return dist;
}
```
A first test indicate that using the int type for the input in the example
is substantial (~37%) faster per call. Test on UNO, time in micros per call.
| types | time us | call |
|:-------:|:---------:|:-------|
| 1 | 194.93 | ```float dist = multiMap<float>(val, in, out, 14);``` |
| 2 | 121.97 | ```float dist = multiMap<int, float>(val, in, out, 14);``` |
Furthermore it is obvious that there is less need for RAM if the integer type is smaller
in size than the float type.
Be sure to do your own tests to see if MMTT improves your performance.
## Operation
@ -129,22 +185,17 @@ Please note the fail example as this shows that in the intern math overflow can
- improve documentation
#### Should
- investigate multiMapCache behaviour
- determine overhead.
- investigate binary search multiMapBS behaviour
- expect a constant time
- where is the tipping point between linear and binary search.
(expect around size = 8)
- extend unit tests
- multi type versions
#### Could
- Investigate class implementation
- basic call out = mm.map(value);
- basic call ```out = mm.map(value);```
- runtime adjusting input and output array **begin(in[], out[])**
- performance / footprint
- less parameter passing
@ -154,10 +205,6 @@ Please note the fail example as this shows that in the intern math overflow can
now it is constrained without user being informed.
- Investigate a 2D multiMap e.g. for complex numbers?
- is it possible / feasible?
- data type input array does not need to be equal to the output array.
- template<typename T1, typename T2>
```T2 multiMapBS(T1 value, T1* _in, T2* _out, uint16_t size)```
#### Wont
@ -165,3 +212,12 @@ Please note the fail example as this shows that in the intern math overflow can
- you cannot reuse e.g. the input array or the output array then.
this would not improve the memory footprint.
## 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

@ -4,6 +4,7 @@
// PURPOSE: demo
// DATE: 2023-06-23
#include "MultiMap.h"
long in[100];
@ -11,11 +12,15 @@ long out[100];
volatile int x;
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(100);
for (int i = 0; i < 100; i++)
{

View File

@ -28,7 +28,6 @@ float out[] = {
8.08, 16.34, 24.30, 32.64, 37.17, 42.13, 48.05, 54.19, 58.75, 66.03, 72.87, 83.85, 96.51, 111.46, 129.49, 182.82, 301.82
};
int sz = 33;
@ -36,8 +35,10 @@ void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(10); // make sure print has ended
delay(100);
start = micros();
x = val(z);

View File

@ -12,7 +12,7 @@
// this example is added to show how to reduce memory but also how it can FAIL due to math overflow
// E.g. see around 196-200; 340-400
// to prevent this one must have more values which increases the memory usage again.
//
#include "MultiMap.h"

View File

@ -14,7 +14,10 @@ void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(100);
for (int i = 80; i < 512; i++)
{

View File

@ -0,0 +1,125 @@
//
// FILE: multimap_distance_two_types.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
// DATE: 2023-11-12
//
// example simulates the lookup graph of a distance sensor
#include "MultiMap.h"
uint32_t start, stop;
volatile float dist;
void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(100);
for (int i = 80; i < 512; i++)
{
float distance1 = sharp2cm1(i);
float distance2 = sharp2cm2(i);
float distance3 = sharp2cm3(i);
Serial.print(i);
Serial.print('\t');
Serial.print(distance1, 2);
Serial.print('\t');
Serial.print(distance2, 2);
Serial.print('\t');
Serial.println(distance3, 2);
}
Serial.println();
delay(1000);
start = micros();
for (int i = 100; i < 500; i++)
{
dist = sharp2cm1(i);
}
stop = micros();
Serial.print("TIME1: ");
Serial.println((stop - start) / 400.0, 2);
delay(100);
start = micros();
for (int i = 100; i < 500; i++)
{
dist = sharp2cm2(i);
}
stop = micros();
Serial.print("TIME2: ");
Serial.println((stop - start) / 400.0, 2);
delay(100);
start = micros();
for (int i = 100; i < 500; i++)
{
dist = sharp2cm3(i);
}
stop = micros();
Serial.print("TIME3: ");
Serial.println((stop - start) / 400.0, 2);
delay(100);
Serial.println("\nDone...");
}
void loop()
{
}
// for a sharp distance range finder
float sharp2cm1(int val)
{
// out[] holds the distances in cm
float out[] = {150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20};
// in[] holds the measured analogRead() values for that distance
float in[] = { 90, 97, 105, 113, 124, 134, 147, 164, 185, 218, 255, 317, 408, 506};
float dist = multiMap<float>(val, in, out, 14);
return dist;
}
// for a sharp distance range finder
float sharp2cm2(int val)
{
// out[] holds the distances in cm
float out[] = {150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20};
// in[] holds the measured analogRead() values for that distance
int in[] = { 90, 97, 105, 113, 124, 134, 147, 164, 185, 218, 255, 317, 408, 506};
float dist = multiMap<int, float>(val, in, out, 14);
return dist;
}
// for a sharp distance range finder
float sharp2cm3(int val)
{
// out[] holds the distances in cm
float out[] = {150, 140, 130, 120, 110, 100, 90, 80, 70, 60, 50, 40, 30, 20};
// in[] holds the measured analogRead() values for that distance
int in[] = { 90, 97, 105, 113, 124, 134, 147, 164, 185, 218, 255, 317, 408, 506};
float dist = multiMapBS<int, float>(val, in, out, 14);
return dist;
}
// -- END OF FILE --

View File

@ -0,0 +1,46 @@
D:\Rob\WORK\Arduino\libraries\MultiMap\examples\21
Done...
0
509 20.00 20.00 20.00
510 20.00 20.00 20.00
511 20.00 20.00 20.00
TIME1: 195.55
TIME2: 116.33
TIME3: 116.21
Done...
D:\Rob\WORK\Arduino\libraries\MultiMap\examples\multimap_distance_two_types\multimap_distance_two_types.ino
MULTIMAP_LIB_VERSION: 0.2.0
80 150.00 150.00 150.00
81 150.00 150.00 150.00
82 150.00 150.00 150.00
83 150.00 150.00 150.00
84 150.00 150.00 150.00
85 150.00 150.00 150.00
86 150.00 150.00 150.00
87 150.00 150.00 150.00
88 150.00 150.00 150.00
89 150.00 150.00 150.00
90 150.00 150.00 150.00
... (reduced for readability)
500 20.61 20.61 20.61
501 20.51 20.51 20.51
502 20.41 20.41 20.41
503 20.31 20.31 20.31
504 20.20 20.20 20.20
505 20.10 20.10 20.10
506 20.00 20.00 20.00
507 20.00 20.00 20.00
508 20.00 20.00 20.00
509 20.00 20.00 20.00
510 20.00 20.00 20.00
511 20.00 20.00 20.00
TIME1: 195.55
TIME2: 116.33
TIME3: 116.21
Done...

View File

@ -8,7 +8,6 @@
// example show use of multiMap to approximate some well known functions.
#include "MultiMap.h"
@ -16,8 +15,10 @@ void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(10); // make sure print has ended
delay(100);
test_normal_distribution();
test_sinus();

View File

@ -26,11 +26,11 @@ void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(100);
// make sure print has ended
delay(10);
// determine gain
performance();
delay(5000);

View File

@ -24,8 +24,10 @@ void setup()
{
Serial.begin(115200);
Serial.println(__FILE__);
Serial.print("MULTIMAP_LIB_VERSION: ");
Serial.println(MULTIMAP_LIB_VERSION);
Serial.println();
delay(10); // make sure print has ended
delay(100);
start = micros();
float x = multiMap<int>(12, in, out, 3);

View File

@ -15,9 +15,9 @@
"type": "git",
"url": "https://github.com/RobTillaart/MultiMap.git"
},
"version": "0.1.7",
"version": "0.2.0",
"license": "MIT",
"frameworks": "arduino",
"frameworks": "*",
"platforms": "*",
"headers": "MultiMap.h"
}

View File

@ -1,5 +1,5 @@
name=MultiMap
version=0.1.7
version=0.2.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Library for fast non-linear interpolation by means of two arrays.