using DTS.Common.Enums.Sensors; using System; using System.Collections.Generic; using System.Text; namespace DTS.Common.DAS.Concepts { public class LinearizationFormula { private bool _bIsValid; public bool IsValid() { return _bIsValid; } public void MarkValid(bool bValid) { _bIsValid = bValid; } // Translation public NonLinearSLICEWareStyles NonLinearSliceWareStyle { get => (NonLinearSLICEWareStyles)NonLinearStyle; set => NonLinearStyle = (NonLinearStyles)value; } public NonLinearStyles NonLinearStyle { get; set; } = NonLinearStyles.Polynomial; // Dont make the default style one that locks a specific zero-method FB 10323 public double PolynomialSensitivity { get; set; } = 1D; public double LinearizationExponent { get; set; } = 1D; /// /// THIS IS MM/V, (UI has already been updated, we need to update the variable name) /// private double _mmPerMV; public double MMPerV { get => _mmPerMV; set => _mmPerMV = value; } public double MVAt0MM { get; set; } public double Slope { get; set; } public double Intercept { get; set; } public double CalibrationFactor { get; set; } public double ZeroPositionIntercept { get; set; } public LinearizationFormula() { ZeroPositionIntercept = 0D; CalibrationFactor = 0D; Intercept = 0D; _coefficients = new List(new double[] { 0, 0, 0, 0 }); _exponents = new List(new double[] { 0, 1, 2, 3 }); } public LinearizationFormula(LinearizationFormula copy) { UsemVOverVForPolys = copy.UsemVOverVForPolys; _bIsValid = copy._bIsValid; _coefficients = new List(copy._coefficients.ToArray()); _exponents = new List(copy._exponents.ToArray()); Intercept = copy.Intercept; LinearizationExponent = copy.LinearizationExponent; _mmPerMV = copy._mmPerMV; MVAt0MM = copy.MVAt0MM; Slope = copy.Slope; NonLinearStyle = copy.NonLinearStyle; _coefficients = new List(copy._coefficients); _exponents = new List(copy._exponents); PolynomialSensitivity = copy.PolynomialSensitivity; ZeroPositionIntercept = copy.ZeroPositionIntercept; CalibrationFactor = copy.CalibrationFactor; } public double GetCoefficient(double exponent) { for (var i = 0; i < _exponents.Count && i < _coefficients.Count; i++) { if (_exponents[i] == exponent) { return _coefficients[i]; } } return 0; } public void SetCoefficient(double exponent, double coefficient) { for (var i = 0; i < _exponents.Count && i < _coefficients.Count; i++) { if (_exponents[i] == exponent) { _coefficients[i] = coefficient; return; } } } public double GetLinearizedValue(double input, double excitation) { if (NonLinearStyle != NonLinearStyles.Polynomial && input <= 0) { //ir-tracc should never be < 0, however we may get readings less than zero due to //noise and other factors, treat these as positive near to zero input = .001; } //first linearize input /= 1000D;//assume input is in mV and we want it in Volts input = Math.Pow(input, LinearizationExponent); switch (NonLinearStyle) { case NonLinearStyles.IRTraccDiagnosticsZero: return GetEUDiagnosticsZero(input); case NonLinearStyles.IRTraccManual: return GetEUIRTraccManual(input); case NonLinearStyles.IRTraccZeroMMmV: return GetEUZeroMMmV(input); case NonLinearStyles.IRTraccAverageOverTime: return GetEUAverageOverTime(input); case NonLinearStyles.Polynomial: return GetEUPolynomial(input, excitation); case NonLinearStyles.IRTraccCalFactor: return GetEUIRTraccCalFactor(input); default: throw new NotSupportedException("unknown format: " + NonLinearStyle); } } private double GetEUIRTraccCalFactor(double volts) { return volts * CalibrationFactor + ZeroPositionIntercept; } private double GetEUIRTraccManual(double volts) { return (volts - Intercept) / Slope; } public bool UsemVOverVForPolys { get; set; } private double GetEUZeroMMmV(double volts) { var input = MVAt0MM / 1000D; input = Math.Pow(input, LinearizationExponent); if (double.IsNaN(input) || double.IsNegativeInfinity(input) || double.IsPositiveInfinity(input)) { return volts * MMPerV; } return (volts * MMPerV - MMPerV * input); } private List _coefficients = new List(); private List _exponents = new List(); private double GetEUPolynomial(double volts, double excitation) { //per J2517 //3.4 Use of the Calibration Coefficients //The potentiometer assembly should be re-installed in the dummy without any mechanical adjustment of the //potentiometer. Prior to a crash test, the original zero offset level must be preserved by either not zeroing the //potentiometer (by signal conditioning or post-processing) or the amount that was zeroed must be added during postprocessing. //During the test the absolute voltage output time history should be recorded. This voltage signal is then //converted to engineering units by: //1. Convert voltage signal to mV/V at the sensor. This is the sensor reading S. //2. Convert the sensor reading S to displacement D by using the equation: //D = A*S^3 + B*S^2 + C*S + M (Eq. 2) //where: //D is the displacement relative to the thorax design position in mm //S is the sensor output reading in mV/V //A, B, C, and M are the calibration coefficients //NOTE: Make sure to use sufficient significant digits on all coefficients to assure accuracy of the conversion to //engineering units. It is recommended to use 5 significant digits (example 0.000012345). //double mV = volts * 1000D; //double gain = 1D; //mV = mV / (gain * excitation); //if (0 != PolynomialSensitivity && 1!= PolynomialSensitivity) { mV /= PolynomialSensitivity; } //double eu = 0D; //for (int i = 0; i < _coefficients.Count && i < _exponents.Count; i++) //{ // eu += _coefficients[i] * Math.Pow(mV, _exponents[i]); //} //return eu; //CHANGED FOR GM TESTING if (0 != PolynomialSensitivity && 1 != PolynomialSensitivity) { volts /= PolynomialSensitivity; } var voltsOverV = 0D; if (UsemVOverVForPolys) { //convert to mV first voltsOverV = (volts * 1000D) / excitation; } else { //used by GM voltsOverV = volts / excitation; } double eu = 0; for (var i = 0; i < _coefficients.Count && i < _exponents.Count; i++) { if (_exponents[i] != 0) { eu += _coefficients[i] * Math.Pow(voltsOverV, _exponents[i]); } else { eu += _coefficients[i]; } } return eu; } /// /// MvAt0MM set at diagnostics /// /// /// private double GetEUDiagnosticsZero(double volts) { //double input = MVAt0MM/1000D; //input = System.Math.Pow(input,LinearizationExponent); var input = double.NaN; if (double.IsNaN(input) || double.IsPositiveInfinity(input) || double.IsNegativeInfinity(input)) { return volts * MMPerV; } return volts * MMPerV - MMPerV * input; } /// /// MVAt0MM set by diagnostics and then later on at /// Average Over Time /// /// /// private double GetEUAverageOverTime(double volts) { //double input = MVAt0MM / 1000D; //input = System.Math.Pow(input, LinearizationExponent); var input = double.NaN; if (double.IsNaN(input) || double.IsNegativeInfinity(input) || double.IsPositiveInfinity(input)) { return volts * MMPerV; } return volts * MMPerV - MMPerV * input; } public string ToSLICEWareSerializeString() { if (!_bIsValid) { return ""; } var sb = new StringBuilder(); sb.AppendFormat("{0}_", NonLinearStyle); switch (NonLinearStyle) { case NonLinearStyles.IRTraccDiagnosticsZero: sb.Append(ToIRTraccDiagnosticZeroString()); break; case NonLinearStyles.IRTraccManual: sb.Append(ToIRTraccManualString()); break; case NonLinearStyles.IRTraccZeroMMmV: sb.Append(ToIRTraccZeroMMmVString()); break; case NonLinearStyles.IRTraccAverageOverTime: sb.Append(ToIRTraccAverageOverTimeString()); break; case NonLinearStyles.Polynomial: sb.Append(ToSLICEWarePolynomialString()); break; case NonLinearStyles.IRTraccCalFactor: throw new NotSupportedException("CalFactor not supported in SLICEWare"); default: throw new NotSupportedException("unknown type: " + NonLinearStyle); } return sb.ToString(); } /// /// serializes to a string of the format "c0xe0 c1xe1...cnxen" /// this will allow us arbitrary length polynomials and fractional exponents. /// /// public string ToSerializeString() { if (!_bIsValid) { return ""; } var sb = new StringBuilder(); sb.AppendFormat("{0}_", NonLinearStyle); switch (NonLinearStyle) { case NonLinearStyles.IRTraccDiagnosticsZero: sb.Append(ToIRTraccDiagnosticZeroString()); break; case NonLinearStyles.IRTraccManual: sb.Append(ToIRTraccManualString()); break; case NonLinearStyles.IRTraccZeroMMmV: sb.Append(ToIRTraccZeroMMmVString()); break; case NonLinearStyles.IRTraccAverageOverTime: sb.Append(ToIRTraccAverageOverTimeString()); break; case NonLinearStyles.Polynomial: sb.Append(ToPolynomialString()); break; case NonLinearStyles.IRTraccCalFactor: sb.Append(ToIRTraccCalFactorString()); break; default: throw new NotSupportedException("unknown type: " + NonLinearStyle); } return sb.ToString(); } public string ToIRTraccDiagnosticZeroString() { return $"{MMPerV.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{LinearizationExponent.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; } public string ToIRTraccCalFactorString() { return $"{CalibrationFactor.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{LinearizationExponent.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{ZeroPositionIntercept.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; } public void FromIRTraccCalFactorString(string s, System.Globalization.CultureInfo culture) { var tokens = s.Split('x'); if (tokens.Length < 3) { throw new NotSupportedException("Invalid CalFactor format: " + s); } CalibrationFactor = double.Parse(tokens[0], culture); LinearizationExponent = double.Parse(tokens[1], culture); ZeroPositionIntercept = double.Parse(tokens[2], culture); } public void FromIRTraccDiagnosticZeroString(string s, System.Globalization.CultureInfo culture) { var tokens = s.Split('x'); if (tokens.Length < 2) { throw new NotSupportedException("Invalid DiagnosticsZero format: " + s); } MMPerV = double.Parse(tokens[0], culture); LinearizationExponent = double.Parse(tokens[1], culture); } public string ToIRTraccManualString() { return $"{Slope.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{Intercept.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{LinearizationExponent.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; } public void FromIRTraccManualString(string s, System.Globalization.CultureInfo culture) { var tokens = s.Split('x'); if (tokens.Length < 3) { throw new NotSupportedException("Invalid IRTraccManual format: " + s); } Slope = double.Parse(tokens[0], culture); Intercept = double.Parse(tokens[1], culture); LinearizationExponent = double.Parse(tokens[2], culture); } public string ToIRTraccZeroMMmVString() { return $"{MMPerV.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{MVAt0MM.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{LinearizationExponent.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; } public string ToSLICEWarePolynomialString() { //SLICEWare is the reverse order of our DataPRO Database var sb = new StringBuilder(); for (var i = _exponents.Count - 1; i >= 0; i--) { if (i != _exponents.Count - 1) { sb.Append(","); } sb.AppendFormat("{0}x{1}", _coefficients[i].ToString(System.Globalization.CultureInfo.InvariantCulture), _exponents[i].ToString(System.Globalization.CultureInfo.InvariantCulture)); } sb.AppendFormat(",S={0}", PolynomialSensitivity.ToString(System.Globalization.CultureInfo.InvariantCulture)); return sb.ToString(); } public string ToPolynomialString() { var sb = new StringBuilder(); for (var i = 0; i < _coefficients.Count && i < _exponents.Count; i++) { if (i > 0) { sb.Append(","); } sb.AppendFormat("{0}x{1}", _coefficients[i].ToString(System.Globalization.CultureInfo.InvariantCulture), _exponents[i].ToString(System.Globalization.CultureInfo.InvariantCulture)); } sb.AppendFormat(",S={0},mV={1}", PolynomialSensitivity.ToString(System.Globalization.CultureInfo.InvariantCulture), UsemVOverVForPolys.ToString(System.Globalization.CultureInfo.InvariantCulture)); return sb.ToString(); } public double[] PolynomialCoefficients { get => _coefficients.ToArray(); set => _coefficients = new List(value); } public double[] PolynomialExponents { get => _exponents.ToArray(); set => _exponents = new List(value); } public string ToIRTraccAverageOverTimeString() { return $"{MMPerV.ToString(System.Globalization.CultureInfo.InvariantCulture)}x{LinearizationExponent.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; } public void FromIRTraccAverageOverTimeString(string s, System.Globalization.CultureInfo culture) { var tokens = s.Split('x'); if (tokens.Length < 2) { throw new NotSupportedException("Invalid IRTRaccAverageOverTime format: " + s); } MMPerV = double.Parse(tokens[0], culture); LinearizationExponent = double.Parse(tokens[1], culture); } public void FromIRTraccZeroMMmVString(string s, System.Globalization.CultureInfo culture) { var tokens = s.Split('x'); if (tokens.Length < 3) { throw new NotSupportedException("Invalid IRTraccZeroMMmV format: " + s); } MMPerV = double.Parse(tokens[0], culture); MVAt0MM = double.Parse(tokens[1], culture); LinearizationExponent = double.Parse(tokens[2], culture); } public void FromPolynomialString(string s, System.Globalization.CultureInfo culture) { _coefficients.Clear(); _exponents.Clear(); var tokens = s.Split(','); foreach (var t in tokens) { var subtokens = t.Split('x'); if (2 == subtokens.Length) { if (double.TryParse(subtokens[0], System.Globalization.NumberStyles.Float, culture, out var d)) { _coefficients.Add(d); _exponents.Add(double.Parse(subtokens[1], culture)); } else { PolynomialSensitivity = double.Parse(subtokens[1], culture); } } else { subtokens = t.Split('='); if (subtokens.Length == 2) { switch (subtokens[0]) { case "S": PolynomialSensitivity = double.Parse(subtokens[1], culture); break; case "mV": UsemVOverVForPolys = Convert.ToBoolean(subtokens[1], culture); break; } } } } } public void FromSerializeString(string s, System.Globalization.CultureInfo culture) { if (string.IsNullOrEmpty(s)) { _bIsValid = false; return; } if (s.Equals("1") || s.Equals("0") || s.Equals("1 ")) { _bIsValid = false; return; } var tokens = s.Split('_'); if (tokens.Length < 2) { throw new NotSupportedException("unsupported Linearization Formula Format"); } var style = (NonLinearStyles)Enum.Parse(typeof(NonLinearStyles), tokens[0], true); NonLinearStyle = style; switch (NonLinearStyle) { case NonLinearStyles.IRTraccDiagnosticsZero: FromIRTraccDiagnosticZeroString(tokens[1], culture); _bIsValid = true; break; case NonLinearStyles.IRTraccManual: FromIRTraccManualString(tokens[1], culture); _bIsValid = true; break; case NonLinearStyles.IRTraccZeroMMmV: FromIRTraccZeroMMmVString(tokens[1], culture); _bIsValid = true; break; case NonLinearStyles.Polynomial: FromPolynomialString(tokens[1], culture); _bIsValid = true; break; case NonLinearStyles.IRTraccAverageOverTime: FromIRTraccAverageOverTimeString(tokens[1], culture); _bIsValid = true; break; case NonLinearStyles.IRTraccCalFactor: FromIRTraccCalFactorString(tokens[1], culture); _bIsValid = true; break; default: throw new NotSupportedException("Unknown format: " + NonLinearStyle); } } public void FromSerializeString(string s) { FromSerializeString(s, System.Globalization.CultureInfo.InvariantCulture); } public void FromTDCSerializeString() { _bIsValid = true; } /// /// Will return a display string for a nonlinear calibration based on N4 formating /// public string ToDisplayString() { return ToDisplayString("N4"); } /// /// Will return a display string for a nonlinear calibration based on 1st paramater formating string /// /// /// public string ToDisplayString(string nonlinearFormat) { if (string.IsNullOrEmpty(nonlinearFormat)) { nonlinearFormat = "N4"; } switch (NonLinearStyle) { case NonLinearStyles.Polynomial: { return ToPolynomial(nonlinearFormat); } case NonLinearStyles.IRTraccZeroMMmV: { return $"mV = {MVAt0MM:n4}, {MMPerV:n4}*(V^{ToSuperScript(LinearizationExponent.ToString(nonlinearFormat))})"; } case NonLinearStyles.IRTraccManual: { return $"((V^{ToSuperScript(LinearizationExponent.ToString(nonlinearFormat))})-{Intercept:n4})/{Slope:n4}"; } case NonLinearStyles.IRTraccDiagnosticsZero: { return $"{MMPerV:n4}*(V^{ToSuperScript(LinearizationExponent.ToString(nonlinearFormat))})"; } case NonLinearStyles.IRTraccAverageOverTime: { return $"{MMPerV:n4}*(V^{ToSuperScript(LinearizationExponent.ToString(nonlinearFormat))})"; } case NonLinearStyles.IRTraccCalFactor: { return $"{ZeroPositionIntercept:n4}+{CalibrationFactor:n4}*(V^{ToSuperScript(LinearizationExponent.ToString(nonlinearFormat))})"; } default: return string.Empty; } } private string ToPolynomial(string nonlinearFormat) { if (string.IsNullOrEmpty(nonlinearFormat)) { nonlinearFormat = "N4"; } var sb = new StringBuilder(); var termNumber = PolynomialCoefficients.Length - 1; foreach (var x in PolynomialCoefficients) { if (PolynomialCoefficients[termNumber] != 0) { var coeff = PolynomialCoefficients[termNumber]; // Let the appended math symbol handle sign unless we're the first term. if (termNumber != PolynomialCoefficients.Length - 1) { coeff = Math.Abs(coeff); } sb.Append(coeff.ToString(nonlinearFormat)); if (PolynomialExponents[termNumber] != 0) { sb.Append("x"); if (PolynomialExponents[termNumber] != 1) { sb.Append(ToSuperScript(PolynomialExponents[termNumber].ToString("N0"))); } } if (termNumber > 0) { // Coerricients are Displayed in absolute value. We need to combine the sign with the addition symbol sb.Append(PolynomialCoefficients[termNumber - 1] > 0 ? " + " : " - "); } } termNumber--; } return sb.ToString(); } private string ToSuperScript(string source) { var superScript = new StringBuilder(); foreach (var c in source) { switch (c) { case '-': superScript.Append('\u207B'); break; case '.': superScript.Append('\u00B7'); break; case '1': superScript.Append('\u00B9'); break; case '2': superScript.Append('\u00B2'); break; case '3': superScript.Append('\u00B3'); break; case '4': superScript.Append('\u2074'); break; case '5': superScript.Append('\u2075'); break; case '6': superScript.Append('\u2076'); break; case '7': superScript.Append('\u2077'); break; case '8': superScript.Append('\u2078'); break; case '9': superScript.Append('\u2079'); break; case '0': superScript.Append('\u2070'); break; case '\'': superScript.Append('\u02C8'); break; case ',': superScript.Append('\u22C5'); // there is no unicode superscript comma. this comes close break; case '\u00A0': superScript.Append('\u2009'); // unicode 'thin' space break; default: superScript.Append('\u207F'); break; } } return superScript.ToString(); } /* * we are given an equation in the form of y = ax^1 + b, except x and y are backwards for us (y=V where we'd prefer X was voltage, so we switch it) * y/a - b/a = x, and then switch y and x, (1/a)x^1 -(b/a)x^0 = y * now we want to get the coefficient of the first equation, which is "a", we get this by taking the inverse * we get b on the other hand by taking -1 * (b/a)*a. if we have the "coefficient", we have a */ /* public double GetIRTraccCoefficient() { foreach (Factor f in Factors) { if (f.Exponent == 1D) { return System.Math.Pow(f.Coefficient, -1); } } return 1D; //0 doesn't make sense for ir } public double GetIRTraccConstant() { foreach (Factor f in Factors) { if (f.Exponent == 0D) { return -1D * GetIRTraccCoefficient() * f.Coefficient; } } return 0D; } public void SetIRTraccFactor(double coefficient, double constant) { if (0 == coefficient) { //well this doesn't make any sense ... coefficient = 1; } Factors = new Factor[] { new Factor(1/coefficient,1), new Factor(-constant/coefficient,0), }; }*/ } }