New Interpolate Linear Function

Help creating logic scripts for Air Manager Instruments

Moderators: russ, Ralph

Message
Author
Tetrachromat
Posts: 236
Joined: Sun Feb 14, 2021 6:55 pm

New Interpolate Linear Function

#1 Post by Tetrachromat »

I was using the 'interpolate_linear' helper function for some gauges wit nonlinear scales. Worked pretty well until feeded with input values out of range. The functions returns a nil in such cases (this is documented on the wiki page).

As I prefer receiving the boundary values for such cases I did my own interpolate function (see code below). I expected it to be a slower than the original because it does not remember previous values. So I did some measurements. To my surprise it was twice as fast, with random values almost tree times as fast (Performance measured once with 2^26 sequential values and once with 2^26 random values)

So i decidedd to give it away for free. I also added compatibility with the original function.

Usage:
value = interpolate_linear(settings, value, cap)

With the optional third (boolean) argument "cap" set to 'true', it provides the boundary values, without the third argument it provides nil for out of range values.

Code: Select all

function interpolate_linear(settings, value, cap)
  local r = 1 -- start with first row
  local max = #settings -- last row
  local input = value
  
  if cap then -- limit value to settings range
    input = var_cap(value, settings[r][1], settings[max][1])
  else -- return nil if outside of range
    if input < settings[r][1] or input > settings[max][1] then 
      return nil 
    end
  end
    
  while r < max do -- check for applicable row
    if input > settings[r+1][1] then -- check next row
      r = r+1
    else -- applicable row found
      break
    end
  end

  x0, y0 = table.unpack(settings[r])
  x1, y1 = table.unpack(settings[r+1])  

  return y0 + (input - x0) * (y1 - y0)/(x1 - x0)
end
I did extended testing to assert it is returning the same values as the original.

Paul

User avatar
Sling
Posts: 5237
Joined: Mon Sep 11, 2017 2:37 pm
Contact:

Re: New Interpolate Linear Function

#2 Post by Sling »

Nice work.

@Corjan it would be good to add cap as an extra optional argument to the main API function.

User avatar
Corjan
Posts: 2936
Joined: Thu Nov 19, 2015 9:04 am

Re: New Interpolate Linear Function

#3 Post by Corjan »

Hi,


Yeah, that might be a nice improvement.

@Tetrachromat
If you are okay with this, I will replace my implementation with yours. Always nice to get it a bit more efficient :)


Corjan

Tetrachromat
Posts: 236
Joined: Sun Feb 14, 2021 6:55 pm

Re: New Interpolate Linear Function

#4 Post by Tetrachromat »

Yes sure. Just a small thing I can give back to this community.

User avatar
Corjan
Posts: 2936
Joined: Thu Nov 19, 2015 9:04 am

Re: New Interpolate Linear Function

#5 Post by Corjan »

Hi,


Thanks! Looking at the code, yours is certainly a bit more elegant then mine.

Will be in next AM BETA.


Corjan

Tetrachromat
Posts: 236
Joined: Sun Feb 14, 2021 6:55 pm

Re: New Interpolate Linear Function

#6 Post by Tetrachromat »

@Corjan
The code I provided and was included in the last BETA has a minor flaw. The variables x0, y0, x1 and y1 are global and should be declared local. When left as global variables they could interfere with code using the same global variable names.

I have seen that many users (also some download instruments) use a lot of global variables when they should use local variables.

So to avoid interference I suggest to declare the variables x0, y0, x1 and y1 in the new interpolate_linear function as local for the next release/beta.

Code: Select all

  ...
  local x0, y0 = table.unpack(settings[r])
  local x1, y1 = table.unpack(settings[r+1]) 
  ...
BR
Paul

User avatar
Corjan
Posts: 2936
Joined: Thu Nov 19, 2015 9:04 am

Re: New Interpolate Linear Function

#7 Post by Corjan »

Hi,


Good point, will be added to next BETA.


Corjan

Toddimus
Posts: 33
Joined: Wed Feb 17, 2021 6:01 pm

Re: New Interpolate Linear Function

#8 Post by Toddimus »

First off, very cool function @Tetrachromat !
I thought I'd try it in my code but I kept getting a nil error with it. Tracked it down to the way the array is evaluated. It assumes input values increase with each subsequent row index. It just so happened that my settings array had it the other way round with the input value decreasing with subsequent rows. That broke the code.

I did a brute force hack and tried to take the order into account by adding an if statement that looks at the "slope" of the input values of the array and inverts the "<" and ">" logic if necessary. I know there's probably a much more elegant and efficient way to do it but here's what I kludged together quickly...

Code: Select all

function interpolate_linear_cap(settings, value, cap)
  local r = 1 -- start with first row
  local max = #settings -- last row
  local input = value

  if settings[r][1] > settings[max][1] then -- invert greater than / less than logic of array evaluation to avoid nil condition

      if cap then -- limit value to settings range
        input = var_cap(value, settings[max][1], settings[1][1])
      else -- return nil if outside of range
        if input > settings[r][1] or input < settings[max][1] then 
          return nil 
        end
      end
        
      while r < max do -- check for applicable row
        if input < settings[r+1][1] then -- check next row
          r = r+1
        else -- applicable row found
          break
        end
      end    
    else -- smaller values are first in array.  continue as original algorithm worked.
      if cap then -- limit value to settings range
        input = var_cap(value, settings[r][1], settings[max][1])
      else -- return nil if outside of range
        if input < settings[r][1] or input > settings[max][1] then 
          return nil 
        end
      end
        
      while r < max do -- check for applicable row
        if input > settings[r+1][1] then -- check next row
          r = r+1
        else -- applicable row found
          break
        end
      end
  end    

  local  x0, y0 = table.unpack(settings[r])
  local  x1, y1 = table.unpack(settings[r+1])  

  return y0 + (input - x0) * (y1 - y0)/(x1 - x0)
end
Simstrumentation Instrument developer
Check us out http://www.simstrumentation.com or https://github.com/Simstrumentation/Air-Manager

User avatar
jph
Posts: 2846
Joined: Fri Apr 10, 2020 12:50 pm
Location: Somewhere over the rainbow..

Re: New Interpolate Linear Function

#9 Post by jph »

Could you not perform a check at the beginning ? and if the values are high to low then use

r,max = max,r

as in something like --

Code: Select all

function interpolate_linear(settings, value, cap)
  local r = 1 -- start with first row
  local max = #settings -- last row
  local input = value
  if r > max then r,max = max,r   -- exchange r and max values if interpolation slope input values are high to low;


then continue as normal with no change to the rest of the code?
Rusty, CBA factor is high and it's early so please make allowances ;)

Or, it could be shown in the wiki as mandatory to place the values in the low to high prior to calling the interpolate function ?. It would probably be best to cover it within the function though.

Just pondering.
Joe
Joe. CISSP, MSc.

Toddimus
Posts: 33
Joined: Wed Feb 17, 2021 6:01 pm

Re: New Interpolate Linear Function

#10 Post by Toddimus »

Yeah, I thought about something like that but I don't think it works with the r = r+1 parts of the code. You'd need it to be r = r-1, wouldn't you?

Ideally, it seems that a reversal of the order of the settings table would do the trick but I didn't think doing that manipulation each time the function is run would be very efficient. Almost seems like you need a helper function that could do it once upon first running the script and then save the reversed table to a "temporary" variable. But that means another function to be added, which can probably be done. I just couldn't figure out a way to do it without adding stuff outside the function itself. A local variable flag value (to indicate it's already been reversed), inside the function, wouldn't persist between calls, would it? If so, you could do something like this:

Code: Select all

function interpolate_linear(...)
	local reverse_flag
	if not reverse_flag then
		<do table reversing routine>
	end
	...
end
But having said all of that, the code I posed yesterday only really adds a value comparison and subsequent if/then/else statement, which shouldn't be too much more "work" at execution time. It's not pretty code wise but it is only slightly less efficient than the original.

I'm still a bit of a noob at the lua programming but I'm starting to pick up steam. :)
Simstrumentation Instrument developer
Check us out http://www.simstrumentation.com or https://github.com/Simstrumentation/Air-Manager

Post Reply