Math Extensions for Ignition

#1

EDIT 2017-07-13: Things have changed enough that this first post is getting revamped.
EDIT 2019-08-08: Re-uploaded due to broken link

Some useful math functions just aren’t available through scripting. This is an attempt to rectify some of it.

Functions:

  • sum
  • mean
  • rectangular-to-polar conversions
  • polar-to-rectangular conversions
  • simple linear regressions
  • polynomial regression
  • greatest-common-denominator
  • least common multiple
  • standard deviation (population and sample)
  • decimal to BCD
  • decimal to binary formatted string with bit count
  • IEEE 754 floating point to decmal
  • scaling with parameters (think RSLogix500 SCP function)
  • binary encoding (jython version of the expression function)
  • expression evaluation.

A quick document is added for usage and examples.

Enjoy!

2017-07-13: Repair some broken functions. New usage doc. :slight_smile:

Latest release here:
math_extensions_2017-07-13.proj (4.7 KB)

Usage:
Math Extensions for Igniton 2017-07-13.pdf (242.1 KB)

3 Likes

Best way to do heavy math operations in Ignition?
Matrix based regression calculations for polynomial coefficients
#2

Sweet!

0 Likes

#3

In response to this thread, here is the updated app.math script.[attachment=2]math.py[/attachment]

Usage in a bit. Off to a meeting… :mrgreen:

EDIT: Adding a window to play with. I’d like to ask for the more deviously hacker-minded users to try to break it. That way it can be improved upon. :smiley:

Also, degrees and radians functions are commented out within evaluate(), since they aren’t supported in Ignition 7.2.x. Feel free to add them back in!

[attachment=1]eval.vwin[/attachment]

Some quick examples:

x=app.math.evaluate('5+7') print x x=app.math.evaluate('sqrt(pow(3,2)+pow(4,2))') print x x=app.math.evaluate('__import__("sys").stdout.write("Yep. Still evaluates...")') print x

[attachment=0]12-2-2013 10-28-16 AM.png[/attachment]

0 Likes

#4

Doesn’t R play well with Python, and therefore could be extended through Python in some way?

0 Likes

#5

Possibly, but the purpose of this was to provide extensibility without adding another software package. Using R would require that you install-- and maintain-- it on every client and gateway that would use it. An import of the script is all that is necessary here.

Even then, it wasn’t a perfect solution. In Ignition 7.2, you still needed to import it for each project that required it. But now with 7.7, we now get globally shared scripting. One import per gateway. It’s even possible that some of the functions are now redundant, with the move to Jython 2.5.

Also, keep in mind that other libraries that would make it even easier to work with R, like Numpy, depend on being used with C-based components, and just won’t work with Jython.

0 Likes

#6

Latest version is at top post.

During my few days off for vacation, I’ve added added another function: polyfit().

This is a polynomial regression model returning up to 5th degree coefficients. If you’ve ever had to do this by hand, you may remember having to do a lot of matrix maths. Considering I hadn’t had to do anything like that in 25 years, I had to relearn it. Not sure what fell out of my brain trying to cram all this back in, but that’s another problem for another time!

Usage: polyfit(x, y, [stringReturn])
Like linreg(), polyfit() requires the x and y coordinates to be in separate lists.
I tossed in an optional parameter called stringReturn to give you a polynomial expression. Is it useful? I have no idea, but if you find it useful, then I’m glad I could help. :slight_smile:


0 Likes

#7

That’s awesome, Jordan!

I use excel to plot and figure out polynomial expressions but this seems much easier…

0 Likes

#8

Update to polyfit():

USAGE: polyfit(x,y,[order],[stringReturn])

x and y, as before are separate ordinate lists.

NEW option-- order: specify order of polynomial to return (default 2)

stringReturn-- optional parameter to return a string expression of the polynomial.

0 Likes

#9

New functionality:

dec2bin(integer,bits,signed) convert decimal to binary string representation.

Usage:
-integer: number to convert
-bits: number of binary bits to convert to. Default is 32.
-signed: returns a signed or unsigned binary representation. (Default is 1/True)

Returns: list [binary_string, bit_count]
Almost forgot the bit count! This is the number of ones in the binary representation.

New functionality:

fp2dec(number)

convert IEEE 754 floating point to decimal.
Usage:
-number: decimal representation of floating point number
Returns: decimal value.

Tested using examples from here.

I’ve started a function to go to IEEE 754, but I’m getting sleepy! :mrgreen:


0 Likes

#10

Hey guys, is there a way of plotting the linear regression line on an Easy Chart? i am able get the r² values etc.

0 Likes

#11

Hi guys!

Latest version re-uploaded. Some of them have broken over time, so I fixed those as well. I think. Updated usage doc also uploaded.

0 Likes

#12

I figured this is probably the more proper topic to post to with new functions…

Below is a function to calculate the median value of either a list, or an arbitrary number of arguments.

#returns median value
#accepts either a list of values, or a series of arguments
#if the first argument is a list, it only computes the median on that list
def median(*args):
	if type(args[0]) == list:
		data = args[0]
	else:
		data =[]
		for i in args:
			data.append(i)
	new_list = sorted(data)
	if len(new_list)%2 > 0:
		return new_list[len(new_list)/2]
	elif len(new_list)%2 == 0:
		return (new_list[(len(new_list)/2)] + new_list[(len(new_list)/2)-1]) /2.0

Some example results from the function:

>>> print shared.math.median(1,2,3,4,5,9)
3.5
>>> print shared.math.median([1,2,3,4,5,9])
3.5
>>> print shared.math.median([0.145,2.14,50,2.299,30.21,5])
3.6495
>>> print shared.math.median("apple","orange","grape",8,10)
apple

The script does actually return the median value of a list of strings (or mixed string and numeric values, with the numbers being sorted ahead of the strings) if the number of items is odd. Predictably, the function blows up if there’s an even number of items that leads to trying to average two strings. I didn’t add a test for numeric values.

2 Likes

#13

Hey Jordan,

I’m just trying to use the polyfit function, but I’m not getting the results i’m expecting :worried:
Any ideas before I try to step into the frying pan?

x = [1552920192, 1552938624, 1552961664] # datetimes in unix timestamp
y = [12.85043621, 12.4644804, 11.92448521]

a,b1,b2,b3 = shared.math.polyfit(x,y,order=3,stringReturn=False)
print r
def y(x, a,b1,b2,b3):
	return b3*x**3 + b2*x**2 + b1*x + a
print y(1552920192, a,b1,b2,b3)
>>>
[10608.0, -4.2691826820373535e-06, 6.775829897165408e-15, -5.4057545500047475e-24]
74.2827997627   ## This should return ~12.85043621

This should return approximately y[0]

FYI I have removed your line that was rounding the coefficients in lines 349-360ish:

def polyfit(x,y,order=2,stringReturn=False):
  assert(len(x)==len(y)), "Number of X ordinates do not match number of Y ordinates"
  assert(order > 0 and order < 6), "Order must be between 1 and 5"
  order += 1
   	 
  sumX=shared.math.sum(*x)
   	
  x2=[]
  for i in range(len(x)):
    x2.append(x[i] * x[i])
  sumX2=shared.math.sum(*x2)
   	
  x3=[]
  for i in range(len(x)):
    x3.append(x[i] * x2[i])
  sumX3=shared.math.sum(*x3)
   	
  x4=[]
  for i in range(len(x)):
    x4.append(x2[i] * x2[i])
  sumX4=shared.math.sum(*x4)
  
  x5=[]
  for i in range(len(x)):
    x5.append(x2[i] * x3[i])
  sumX5=shared.math.sum(*x5)
  
  x6=[]
  for i in range(len(x)):
    x6.append(x3[i] * x3[i])
  sumX6=shared.math.sum(*x6)
  
  x7=[]
  for i in range(len(x)):
    x7.append(x3[i] * x4[i])
  sumX7=shared.math.sum(*x7)
   	
  x8=[]
  for i in range(len(x)):
    x8.append(x4[i] * x4[i])
  sumX8=shared.math.sum(*x8)
   	
  x9=[]
  for i in range(len(x)):
    x9.append(x4[i] * x5[i])
  sumX9=shared.math.sum(*x9)
   	
  x10=[]
  for i in range(len(x)):
    x10.append(x5[i] * x5[i])
  sumX10=shared.math.sum(*x10)
   	
  sumY=shared.math.sum(*y)
  
  xy=[]
  for i in range(len(x)):
    xy.append(x[i] * y[i])
  sumXY=shared.math.sum(*xy)
  
  x2y=[]
  for i in range(len(x)):
    x2y.append(x2[i] * y[i])
  sumX2Y=shared.math.sum(*x2y)
  
  x3y=[]
  for i in range(len(x)):
    x3y.append(x3[i] * y[i])
  sumX3Y=shared.math.sum(*x3y)
  
  x4y=[]
  for i in range(len(x)):
    x4y.append(x4[i] * y[i])
  sumX4Y=shared.math.sum(*x4y)
  
  x5y=[]
  for i in range(len(x)):
    x5y.append(x5[i] * y[i])
  sumX5Y=shared.math.sum(*x5y)
  
  matrix=[]
  matrix.append([float(len(x)),  sumX, sumX2, sumX3, sumX4,  sumX5])
  matrix.append([ sumX, sumX2, sumX3, sumX4, sumX5,  sumX6])
  matrix.append([sumX2, sumX3, sumX4, sumX5, sumX6,  sumX7])
  matrix.append([sumX3, sumX4, sumX5, sumX6, sumX7,  sumX8])
  matrix.append([sumX4, sumX5, sumX6, sumX7, sumX8,  sumX9])
  matrix.append([sumX5, sumX6, sumX7, sumX8, sumX9, sumX10])
  
  vector=[sumY, sumXY, sumX2Y, sumX3Y, sumX4Y, sumX5Y]  
  
  inverse=[]
  inverse.append([1.0,0.0,0.0,0.0,0.0,0.0])
  inverse.append([0.0,1.0,0.0,0.0,0.0,0.0])
  inverse.append([0.0,0.0,1.0,0.0,0.0,0.0])
  inverse.append([0.0,0.0,0.0,1.0,0.0,0.0])
  inverse.append([0.0,0.0,0.0,0.0,1.0,0.0])
  inverse.append([0.0,0.0,0.0,0.0,0.0,1.0])
  
  del matrix[order:]
  del vector[order:]
  del inverse[order:]
  for i in range(order):
    del matrix[i][order:]
    del inverse[i][order:]
    assert(matrix[i][i]!=0), "Invalid Matrix"
  
  x=matrix[0][0]
  for i in range(order):
    matrix[0][i] = matrix[0][i] / x
    inverse[0][i] = inverse[0][i] / x
  
  for z in range(1,order):
    for i in range(z,order):
  	x=matrix[i][z-1]
  	for j in range(order):
  	  matrix[i][j]=matrix[i][j]-x*matrix[z-1][j]
  	  inverse[i][j]=inverse[i][j]-x*inverse[z-1][j]
    y=matrix[z][z]
    for j in range(order):
  	matrix[z][j]=matrix[z][j] / y
  	inverse[z][j]=inverse[z][j] / y
  
  for z in range(order-1,0,-1):
    for i in range(0,z):
  	x=matrix[i][z]
  	for j in range(order):
  	  matrix[i][j]=matrix[i][j]-x*matrix[z][j]
  	  inverse[i][j]=inverse[i][j]-x*inverse[z][j]
    y=matrix[z][z]
    for j in range(order):
  	matrix[z][j]=matrix[z][j] / y
  	inverse[z][j]=inverse[z][j] / y
  
  
  coefficient=[]
  for i in range(order):
    x = 0.0
    for j in range(order):
      x += inverse[i][j]*vector[j]
    # Note: removed rounding from this next line to make more accurate
    coefficient.append(x)
  if stringReturn==False:   
    return coefficient
  else:
    s=''
    for c in range(len(coefficient)-1,-1,-1):
      if coefficient[c] != 0:
        if c > 1:
          s+= "%+.2fx^" % coefficient[c]+str(c)
        elif c>0:
          s += "%+.2fx" % coefficient[c]
        else:
          s += "%+.2f" % coefficient[c]
    return s
0 Likes

#14

The issue here (mostly :wink: ) is that it is diffucult to get a good regression from three data points.

You can do three things:

  • Use many data points:
  • Change the order magnitude:
x = [1552920192, 1552938624, 1552961664] # datetimes in unix timestamp
y = [12.85043621, 12.4644804, 11.92448521]


a,b1,b2 = shared.math.polyfit(x,y,order=2,stringReturn=False)

def y2(x, a,b1,b2):
	return b2*x**2 + b1*x + a
print '2nd order:', y2(1552920192, a,b1,b2)

a,b1,b2,b3 = shared.math.polyfit(x,y,order=3,stringReturn=False)

def y3(x, a,b1,b2,b3):
	return b3*x**3 + b2*x**2 + b1*x + a
print '3rd order:',y3(1552920192, a,b1,b2,b3)

a,b1,b2,b3,b4 = shared.math.polyfit(x,y,order=4,stringReturn=False)

def y4(x, a,b1,b2,b3,b4):
	return b4*x**4 + b3*x**3 + b2*x**2 + b1*x + a
print '4th order:',y4(1552920192, a,b1,b2,b3,b4)


a,b1,b2,b3,b4,b5 = shared.math.polyfit(x,y,order=5,stringReturn=False)

def y5(x, a,b1,b2,b3,b4,b5):
	return b5*x**5 + b4*x**4 + b3*x**3 + b2*x**2 + b1*x + a
print '5th order:',y5(1552920192, a,b1,b2,b3,b4,b5)
2nd order: 4.40191416635
3rd order: 74.2827997627
4th order: 18.3351743291
5th order: 16.1118623086
  • Use polynomial interpolation. That’s attempts to find a polynomial that includes all the data points. It’s subtly different than regression, but it is different.
0 Likes