# encoding: ascii-8bit # Copyright 2022 Ball Aerospace & Technologies Corp. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it # under the terms of the GNU Affero General Public License # as published by the Free Software Foundation; version 3 with # attribution addendums as found in the LICENSE.txt # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # Modified by OpenC3, Inc. # All changes Copyright 2024, OpenC3, Inc. # All Rights Reserved # # This file may also be used under the terms of a commercial license # if purchased from OpenC3, Inc. require 'openc3/conversions/conversion' module OpenC3 # Segmented polynomial conversions consist of polynomial conversions that are # applied for a range of values. class SegmentedPolynomialConversion < Conversion # @return [Array] Segments which make up this conversion attr_reader :segments # A polynomial conversion segment which applies the conversion from the # lower bound (inclusive) until another segment's lower bound is # encountered. class Segment # @return [Integer] The value at which point this polynomial conversion # should apply. All values >= to this value will be converted using the # given coefficients. attr_reader :lower_bound # @return [Array] The polynomial coefficients attr_reader :coeffs # Creates a polynomial conversion segment. Multiple Segments are used to # implement a {SegmentedPolynomialConversion}. # # @param lower_bound [Integer] The value at which point this polynomial conversion # should apply. All values >= to this value will be converted using the # given coefficients. # @param coeffs [Array] The polynomial coefficients def initialize(lower_bound, coeffs) @lower_bound = lower_bound @coeffs = coeffs end # Implement the comparison operator to compared based on the lower_bound # but sort in reverse order so the segment with the largest lower_bound # comes first. This makes the calculation code in call easier. # # @param other_segment [Segment] The segment to compare # @return [Integer] 1 if self.lower_bound > other_segment.lower_bound, 0 # if they are equal, -1 if self.lower_bound < other_segment.lower_bound def <=>(other) return other.lower_bound <=> @lower_bound end # Implement equality operator primarily for ease of testing # # @param segment [Segment] Other segment def ==(other) @lower_bound == other.lower_bound && @coeffs == other.coeffs end # Perform the polynomial conversion # # @param value [Numeric] The value to convert # @return [Float] The converted value def calculate(value) converted = 0.0 @coeffs.length.times do |index| converted += @coeffs[index].to_f * (value**index) end return converted end end # Initialize the converted_type to :FLOAT and converted_bit_size to 64. # # @param segments [Array] Array of segments typically generated by as_json # Format similar to the following: [[15, [3, 2]], [10, [1, 2]]] # Where each entry is an array with the first value as the lower_bound # and the other entry is an array of the coefficients for that segment. def initialize(segments = []) super() @params = [] @segments = [] segments.each { |lower_bound, coeffs| add_segment(lower_bound, *coeffs) } @converted_type = :FLOAT @converted_bit_size = 64 end # Add a segment to the segmented polynomial. The lower bound is inclusive, but # is ignored for the segment with the lowest lower_bound. # # @param lower_bound [Integer] The value at which point this polynomial conversion # should apply. All values >= to this value will be converted using the # given coefficients. # @param coeffs [Array] The polynomial coefficients def add_segment(lower_bound, *coeffs) @params << [lower_bound, coeffs] @segments << Segment.new(lower_bound, coeffs) @segments.sort! end # @param (see Conversion#call) # @return [Float] The value with the polynomial applied def call(value, packet, buffer) # Try to find correct segment @segments.each do |segment| return segment.calculate(value) if value >= segment.lower_bound end # Default to using segment with smallest lower_bound segment = @segments[-1] if segment return @segments[-1].calculate(value) else return nil end end # @return [String] The name of the class followed by a description of all # the polynomial segments. def to_s result = '' count = 0 @segments.each do |segment| result << "\n" if count > 0 result << "Lower Bound: #{segment.lower_bound} Polynomial: " segment.coeffs.length.times do |index| if index == 0 result << "#{segment.coeffs[index]}" elsif index == 1 result << " + #{segment.coeffs[index]}x" else result << " + #{segment.coeffs[index]}x^#{index}" end end count += 1 end result end # @param (see Conversion#to_config) # @return [String] Config fragment for this conversion def to_config(read_or_write) config = '' @segments.each do |segment| config << " SEG_POLY_#{read_or_write}_CONVERSION #{segment.lower_bound} #{segment.coeffs.join(' ')}\n" end config end end end