Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added python Script to calculate distance from RSSI #1151

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
79 changes: 79 additions & 0 deletions lib/scripts/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## INSTRUCTION TO USE THR POWER REGRESSION PYTHON SCRIPT

In the given scenario, we have two variables, namely `xData` and `yData`. The xData variable represents a list of ratios of RSSI (Received Signal Strength Indication) values between your phone and the iPhone at a distance of 1 meter. On the other hand, the yData variable represents the corresponding distances in meters.

To approximate the distance based on the RSSI values, a function called `fun()` is defined. This function follows the form of `Ax^B`, where A and B are coefficients. However, it is possible to modify the equation and find a better approach by using a different form, such as `Ax^B+C`.

By utilizing this equation and the coefficients obtained, it becomes possible to estimate the approximate or much closer distance value compared to the actual distance. This estimation is achieved by applying the modified equation to the RSSI ratios from xData. The resulting values will correspond to the distances in yData.


## About the script

- The given code snippet performs a curve fitting analysis on a set of economic data using the `scipy.optimize.curve_fit` function. The goal is to find the best-fit curve that approximates the relationship between two variables, xData and yData.

- The code begins by importing the necessary libraries, such as `numpy`, `scipy`, and `matplotlib`, and defining the required functions and variables. It defines three functions: `func, func2, and func3`, each representing a different mathematical equation form for the curve fitting.

- Next, the `sumOfSquaredError` function is defined, which calculates the sum of squared errors between the observed `yData` and the values predicted by the curve fitting function. This function will be used by the genetic algorithm implemented in the `differential_evolution `method to minimize the error and find optimal parameter values.

- The `generate_Initial_Parameters` function sets the search bounds for the parameters (a, b, c, d, and e) used in the curve fitting process. It then calls the differential_evolution function to find the initial parameter values that minimize the sum of squared errors.

- The code then proceeds to fit the curve using the curve_fit function, passing the `func`, `xData`, `yData`, and the initial parameter values obtained from the genetic algorithm. The resulting `fittedParameters` represent the best-fit parameters for the curve.

- After fitting the curve, the code calculates various evaluation metrics, such as the root mean squared error (RMSE) and the coefficient of determination (R-squared), to assess the quality of the fit. It also keeps track of the best-fit parameters and the corresponding R-squared value.

- Finally, the code generates a scatter plot of the raw data points (`xData` and `yData`) and overlays the fitted curve based on the `fittedParameters`. The plot is displayed using `matplotlib.pyplot` and then closed.

- Overall, this code performs a curve-fitting analysis on economic data, finds the best-fit curve using a genetic algorithm and curve_fit, and visualizes the results through a scatter plot.

## Excel Sheet

- The provided Excel sheet serves as a reference tool for comparing and analyzing distance values based on measured RSSI (Received Signal Strength Indication) values. It is designed to work in conjunction with the script mentioned earlier to obtain the coefficients required for the distance comparison.

- The Excel sheet allows users to input the necessary data, including the measured RSSI values and corresponding distance values. These values are typically obtained from experimental measurements or data collection processes.

- Once the data is entered into the Excel sheet, the script mentioned earlier comes into play. It uses the curve fitting algorithm to find the best-fit curve that approximates the relationship between RSSI values and distances. By fitting the data to a mathematical equation, the script determines the coefficients that provide the closest match to the observed data.

- These coefficients are then obtained from the script and can be manually entered into the Excel sheet. By doing so, the Excel sheet can utilize the coefficients to calculate and display the estimated distances corresponding to new or additional RSSI values.

## INSTRUCTION TO BUILD YOUR OWN SHEET

### Calculating Formula Constants

After taking distance measurements for a specific Android device, the next step is to run a power regression to get the A, B and C constants used in the y=A*x^B+C formula.

### Make a spreadsheet

Create a new spreadsheet using Excel, Google Docs or similar. You can see an example spreadsheet [here](https://docs.google.com/spreadsheets/d/1ymREowDj40tYuA5CXd4IfC4WYPXxlx5hq1x8tQcWWCI/edit?usp=sharing) that you can follow as a guide.

- Paste Your Data
- Paste your measured iPhone 1m RSSI average into table 1.
- Paste your measured RSSI values at various distances for the Android device into table 2.
- Step 1: Calculate Ratio
- Make a new column beside your measured RSSI values at various distances. Divide your measured RSSI at each distance by the iPhone 1m measured RSSI.

### Step 2: Format Data for Regression

Make two new columns and copy your ratio values into the first column and the distance values into the second column. This will align your dependent and independent varaibles for the power regression.

### Step 3: Run the Regression

While there are many tools that can be used to run a power regression, the online website [here](http://www.xuru.org/rt/powr.asp) may be the easiest. Just paste the two columns from the previous step into the form field, and it will output the A and B constants.

### Step 4: Test the Prediction

Make four new columns to form table 5: RSSI, Ratio, Actual Distance, Predicted Distance, and paste for existing values into the first three. For the fourth column, calculate the predicted distance using the formula y=A*x^B.

### Step 4. Calculate C

The power regression assumes a zero intercept. In order to optimize distance estimates at 1 meter, we add an intercept variable C. To do this, subtract the actual distance from the predicted distance in table 5 above, and put this in table 6.

### Step 5. Test the Prediction again

Make four new columns to form table 7: RSSI, Ratio, Actual Distance, Predicted Distance, and paste for existing values into the first three. For the fourth column, calculate the predicted distance using the formula y=A*x^B+C. This is the final formula.

### Step 6. Validate Results

Check the predicted distances against the actual distances to see that the formula provides a reasonable fit for the device. You may wish to compare if this customized formula predicts distance better than the default Nexus formula with the library before submitting the constants for inclusion in the library.

### Step 7. Submit a Pull Request
Submit a new Pull Request to the Android Beacon Library project, providing a link to your spreadsheet calculations.
Binary file not shown.
129 changes: 129 additions & 0 deletions lib/scripts/python_script_to_get_coefficients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Dec 26 15:23:54 2022

@author: gptshubham595
"""

# fit a line to the economic data
import numpy, scipy, matplotlib
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from scipy.optimize import differential_evolution
import warnings
import math
import numpy as np

vector = np.vectorize(np.int_)

yData = numpy.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 20, 27, 34, 41])

xData=[1.176470588, 1.215686275, 1.352941176, 1.176470588, 1.254901961, 1.431372549, 1.254901961, 1.31372549, 1.274509804, 1.784313725, 1.490196078, 1.411764706, 1.529411765, 1.588235294, 1.549019608, 1.568627451, 1.450980392, 1.68627451, 1.62745098, 1.62745098]

def func2(x, a, b, c): # from the zunzun.com "function finder"
return (a * (x**b) + c)

def func(x, a, b): # from the zunzun.com "function finder"
return (a * (x**b))

def func3(x, a, b, c, d, e): # from the zunzun.com "function finder"
y=[]
for i in range(len(x)):
y.append((a * math.exp(b*x[i]) + c * (x[i]**d) + e))
return y

# function for genetic algorithm to minimize (sum of squared error)
def sumOfSquaredError(parameterTuple):
warnings.filterwarnings("ignore") # do not print warnings by genetic algorithm
val = func(xData, *parameterTuple)
return numpy.sum((yData - val) ** 2.0)


def generate_Initial_Parameters(i):
# min and max used for bounds
maxX = max(xData)
minX = min(xData)
maxY = max(yData)
minY = min(yData)

minData = min(minX, minY)
maxData = max(maxX, maxY)

parameterBounds = []
parameterBounds.append([0, maxData]) # search bounds for a
parameterBounds.append([0, maxData]) # search bounds for b
if(i==1):
parameterBounds.append([0, maxData]) # search bounds for c
if(i==2):
parameterBounds.append([0, maxData]) # search bounds for d
parameterBounds.append([0, maxData]) # search bounds for e

# "seed" the numpy random number generator for repeatable results
result = differential_evolution(sumOfSquaredError, parameterBounds, seed=5)
return result.x


# by default, differential_evolution completes by calling curve_fit() using parameter bounds
geneticParameters = generate_Initial_Parameters(0)

# now call curve_fit without passing bounds from the genetic algorithm,
# just in case the best fit parameters are aoutside those bounds
#fittedParameters, pcov = curve_fit(func, xData, yData, geneticParameters, maxfev=3000)
bestfit = []
bestRSq = 1000


#for i in range(3):
fittedParameters, pcov = curve_fit(func, xData, yData, geneticParameters, maxfev=8000)

print('Fitted parameters:', fittedParameters)
print()

modelPredictions = func(xData, *fittedParameters)

absError = modelPredictions - yData
SE = numpy.square(absError) # squared errors
MSE = numpy.mean(SE) # mean squared errors
RMSE = numpy.sqrt(MSE) # Root Mean Squared Error, RMSE

Rsquared = 1.0 - (numpy.var(absError) / numpy.var(yData))
if(bestRSq>Rsquared):
bestRSq = Rsquared
bestfit = fittedParameters
print()
print('RMSE:', RMSE)
print('R-squared:', Rsquared)

print()

print(bestRSq)
print(bestfit)


##########################################################
# graphics output section
def ModelAndScatterPlot(graphWidth, graphHeight):
f = plt.figure(figsize=(graphWidth/100.0, graphHeight/100.0), dpi=100)
axes = f.add_subplot(111)

# first the raw data as a scatter plot
axes.plot(xData, yData, 'D')

# create data for the fitted equation plot
xModel = numpy.linspace(min(xData), max(xData))
yModel = func(xModel, *fittedParameters) #change it to fun or fun2

# now the model as a line plot
axes.plot(xModel, yModel)

axes.set_xlabel('X Data') # X axis data label
axes.set_ylabel('Y Data') # Y axis data label

plt.show()
plt.close('all') # clean up after using pyplot


graphWidth = 800
graphHeight = 600
ModelAndScatterPlot(graphWidth, graphHeight)