/// <summary> /// Returns the nominal (Item1), min (Item2), and max (Item3) allowed HBM Peak Current to be within JS-001 specification /// </summary> /// <param name="hbmVoltage">The voltage of the HBM pulse waveform (polarity is ignored, absolute value will be used)</param> /// <returns>The nominal (Item1), min (Item2), and max (Item3) allowed HBM Peak Current to be within JS-001 specification</returns> internal static Tuple <double, double, double> HBMPeakCurrentNominalMinMax(double hbmVoltage) { double absHBMVoltage = System.Math.Abs(hbmVoltage); HBM500OhmJS001WaveformCharacteristicsSet set = HBM500OhmJS001WaveformCharacteristics.GenerateSetForHBMVoltage(absHBMVoltage); return(new Tuple <double, double, double>( DoubleRangeExtensions.CenterOfRange(set.PeakCurrent.Item1, set.PeakCurrent.Item2), set.PeakCurrent.Item1, set.PeakCurrent.Item2)); }
/// <summary> /// Returns the nominal (Item1), min (Item2), and max (Item3) allowed CDM Full Width at Half Maximum to be within the JS-002 specification /// </summary> /// <param name="signedCDMVoltage">The sign value of the CDM pulse waveform</param> /// <param name="isLargeTarget">A value indicating whether the CDM target is large or not (if not, then it is small)</param> /// <param name="oscilloscopeIsHighBandwidth">A value indicating whether the oscilloscope is high bandwidth (6GHz+) or not</param> /// <returns>The nominal (Item1), min (Item2), and max (Item3) allowed CDM Full Width at Half Maximum to be within the JS-002 specification</returns> public static Tuple <double, double, double> FullWidthHalfMaxNominalMinMax(double signedCDMVoltage, bool isLargeTarget, bool oscilloscopeIsHighBandwidth) { CDMJS002WaveformCharacteristicsSet set = CDMJS002WaveformCharacteristics.GenerateSetForCDMVoltageAndCharacteristics( signedCDMVoltage, isLargeTarget, oscilloscopeIsHighBandwidth); return(new Tuple <double, double, double>( DoubleRangeExtensions.CenterOfRange(set.FullWidthAtHalfMaximum.Item1, set.FullWidthAtHalfMaximum.Item2), set.FullWidthAtHalfMaximum.Item1, set.FullWidthAtHalfMaximum.Item2)); }
/// <summary> /// Calculates the Full Width at Half Max related values /// </summary> /// <param name="fullWidthHalfMaxPercent">(Optional) The threshold of where to measure the FWHM as a percent of the peak current (Default: 50%)</param> private void CalculateFullWidthAtHalfMax(double fullWidthHalfMaxPercent = 0.5) { if (fullWidthHalfMaxPercent <= 0.0 || fullWidthHalfMaxPercent >= 1.0) { throw new ArgumentOutOfRangeException("The Full Width Half Max Percentage cannot be less than or equal to 0% or greater than or equal to 100%"); } // Calculate what the half-max current value is (default is 50% of max amplitude) double fullWidthHalfMaxCurrentSigned = this.PeakCurrentValue * fullWidthHalfMaxPercent; double fullWidthHalfMaxCurrentAbsolute = fullWidthHalfMaxCurrentSigned.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Find the first (interpolated) data point that is on the rising edge of the initial spike (of the absolute value waveform) DataPoint?fullWidthHalfMaxRisingAbsoluteDataPoint = this.AbsoluteWaveform.DataPointAtYThreshold(fullWidthHalfMaxCurrentAbsolute, true); if (fullWidthHalfMaxRisingAbsoluteDataPoint.HasValue) { // Convert the rising data point to the signed value this.FullWidthHalfMaxStartDataPoint = fullWidthHalfMaxRisingAbsoluteDataPoint.Value.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Trim the waveform, removing everything before the max peak Waveform absoluteWaveformAfterPeakCurrentTime = this.AbsoluteWaveform.TrimStart(this.PeakCurrentDataPoint.X); // Find the first (interpolated) data point that is on the falling edge of the initial spike (of the absolute value waveform) DataPoint?fullWidthHalfMaxFallingDataPointAbsolute = absoluteWaveformAfterPeakCurrentTime.DataPointAtYThreshold(fullWidthHalfMaxCurrentAbsolute, true); if (fullWidthHalfMaxFallingDataPointAbsolute.HasValue) { // Convert the falling data point to the signed value this.FullWidthHalfMaxEndDataPoint = fullWidthHalfMaxFallingDataPointAbsolute.Value.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Find the full width half max time this.FullWidthHalfMaxValue = this.FullWidthHalfMaxEndDataPoint.X - this.FullWidthHalfMaxStartDataPoint.X; // Determine the min and max values for the Full Width Half Max to be passing Tuple <double, double, double> nomMinMaxFullWidthHalfMax = CDMJS002WaveformCharacteristics.FullWidthHalfMaxNominalMinMax(this.SignedVoltage, this.IsLargeTarget, this.OscilloscopeIsHighBandwidth); this.FullWidthHalfMaxAllowedMinimum = nomMinMaxFullWidthHalfMax.Item2; this.FullWidthHalfMaxAllowedMaximum = nomMinMaxFullWidthHalfMax.Item3; // Determine if the Full Width Half Max is passing this.FullWidthHalfMaxIsPassing = DoubleRangeExtensions.BetweenInclusive(this.FullWidthHalfMaxAllowedMinimum, this.FullWidthHalfMaxAllowedMaximum, this.FullWidthHalfMaxValue); } else { // Full Width Half Max falling data point could not be found, no calculations could be made. } } else { // Full Width Half Max rising data point could not be found, no calculations could be made. } }
/// <summary> /// Calculates the Peak Current (Ips) related values /// </summary> private void CalculatePeakCurrent() { // Calculate the (absolute) Peak Current DataPoint DataPoint absolutePeakCurrentDataPoint = this.AbsoluteWaveform.Maximum(); // Convert to the signed Peak Current DataPoint this.PeakCurrentDataPoint = absolutePeakCurrentDataPoint.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Extract the Peak Current value this.PeakCurrentValue = this.PeakCurrentDataPoint.Y; // Determine the min and max allowed values for the Peak Current to be passing Tuple <double, double, double> nomMinMaxPeakCurrent = HBM500OhmJS001WaveformCharacteristics.HBMPeakCurrentNominalMinMax(this.SignedVoltage); this.PeakCurrentAllowedMinimum = nomMinMaxPeakCurrent.Item2.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); this.PeakCurrentAllowedMaximum = nomMinMaxPeakCurrent.Item3.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Determine if the Peak Current is passing this.PeakCurrentIsPassing = DoubleRangeExtensions.BetweenInclusive(this.PeakCurrentAllowedMinimum, this.PeakCurrentAllowedMaximum, this.PeakCurrentValue); }
/// <summary> /// Calculates the Peak Current (Ips) related values /// </summary> private void CalculatePeakCurrent() { // Calculate the (absolute) Max Current DataPoint DataPoint ipsMaxAbsoluteDataPoint = this.IpsMaxDataPoint.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Determine Ips by evaluating the ips Polynomial at the IpsMax time double ipsAbsoluteValue = this.AbsoluteIpsPolynomial.Evaluate(ipsMaxAbsoluteDataPoint.X); // Determine the Peak Current (Ips) value this.PeakCurrentValue = ipsAbsoluteValue.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Create a DataPoint at the time of Ips with the Ips value this.PeakCurrentDataPoint = new DataPoint(ipsMaxAbsoluteDataPoint.X, this.PeakCurrentValue); // Determine the min and max allowed values for the Peak Current to be passing Tuple <double, double, double> nomMinMaxPeakCurrent = HBM0OhmJS001WaveformCharacteristics.HBMPeakCurrentNominalMinMax(this.SignedVoltage); this.PeakCurrentAllowedMinimum = nomMinMaxPeakCurrent.Item2.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); this.PeakCurrentAllowedMaximum = nomMinMaxPeakCurrent.Item3.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Determine if the Peak Current is passing this.PeakCurrentIsPassing = DoubleRangeExtensions.BetweenInclusive(this.PeakCurrentAllowedMinimum, this.PeakCurrentAllowedMaximum, this.PeakCurrentValue); }
/// <summary> /// Calculates the Peak Current related values /// </summary> /// <param name="pointsAroundPeakToAverage">Number of points around the absolute peak to average together (Default: 0)</param> private void CalculatePeakCurrent(int pointsAroundPeakToAverage) { // Calculate the (absolute) Peak Current DataPoint DataPoint absolutePeakCurrentDataPoint = this.AbsoluteWaveform.Maximum(); List <DataPoint> absWfmDataPoints = this.AbsoluteWaveform.DataPoints.ToList(); int peakIndex = absWfmDataPoints.IndexOf(absolutePeakCurrentDataPoint); int peakStartIndex = Math.Max(0, peakIndex - pointsAroundPeakToAverage); int peakStopIndex = Math.Min(absWfmDataPoints.Count - 1, peakIndex + pointsAroundPeakToAverage); List <DataPoint> peakDataPoints = new List <DataPoint>(); for (int i = peakStartIndex; i <= peakStopIndex; i++) { peakDataPoints.Add(absWfmDataPoints[i]); } Waveform peakWaveform = new Waveform(peakDataPoints); double peakCurrent = peakWaveform.Average(); if (!this.WaveformIsPositivePolarity) { peakCurrent *= -1.0; } // Extract the Peak Current value this.PeakCurrentValue = peakCurrent; this.PeakCurrentDataPoint = new DataPoint(absolutePeakCurrentDataPoint.X, peakCurrent); // Determine the min and max allowed values for the Peak Current to be passing Tuple <double, double, double> nomMinMaxPeakCurrent = CDMJS002WaveformCharacteristics.PeakCurrentNominalMinMax(this.SignedVoltage, this.IsLargeTarget, this.OscilloscopeIsHighBandwidth); this.PeakCurrentAllowedMinimum = nomMinMaxPeakCurrent.Item2.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); this.PeakCurrentAllowedMaximum = nomMinMaxPeakCurrent.Item3.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Determine if the Peak Current is passing this.PeakCurrentIsPassing = DoubleRangeExtensions.BetweenInclusive(this.PeakCurrentAllowedMinimum, this.PeakCurrentAllowedMaximum, this.PeakCurrentValue); }
/// <summary> /// Calculates the Decay Time related values /// </summary> private void CalculateDecayTime() { // The percentage of the Peak Current to be the end of the decay time (1/e) double decayTimeEndPercentOfIps = 1.0 / System.Math.E; // Trim the waveform, removing everything before the max peak Waveform absoluteWaveformAfterPeakCurrentTime = this.AbsoluteWaveform.TrimStart(this.PeakCurrentDataPoint.X); // Find the start/stop times for the noise-reducing exponential fit waveform DataPoint?expFitStartDataPoint = absoluteWaveformAfterPeakCurrentTime.DataPointAtYThreshold(0.5 * System.Math.Abs(this.PeakCurrentValue), true); if (expFitStartDataPoint.HasValue) { double expFitStartTimeThreshold = expFitStartDataPoint.Value.X; DataPoint?expFitEndDataPoint = absoluteWaveformAfterPeakCurrentTime.DataPointAtYThreshold(0.3 * System.Math.Abs(this.PeakCurrentValue), true); // The waveform was truncated before the lower end of the decaying region, // however there might be enough data to calculate by using the last data point instead. if (!expFitEndDataPoint.HasValue) { expFitEndDataPoint = absoluteWaveformAfterPeakCurrentTime.DataPoints.Last(); } double expFitEndTimeThreshold = expFitEndDataPoint.Value.X; // Gate the waveform to just the area for the noise-reducing exponential fit waveform Waveform absoluteWaveformDecayingRegion = this.AbsoluteWaveform.Gates(expFitStartTimeThreshold, expFitEndTimeThreshold); if (absoluteWaveformDecayingRegion.DataPoints.Any()) { // Find the exponential fit constants of the waveform from 0.5 * Ips to 0.3 * Ips to eliminate noise Tuple <double, double> expFitAbsoluteWaveformDecayingRegionConstants = absoluteWaveformDecayingRegion.ExponentialFit(); // Create time values that go out far enough to ensure the decay time end threshold can be found List <double> timeValues = (from dp in absoluteWaveformDecayingRegion.DataPoints select dp.X).ToList(); double samplingTime = absoluteWaveformDecayingRegion.SamplingTime(); while (timeValues.Last() < 0.000000800 && samplingTime > 0) { timeValues.Add(timeValues.Last() + samplingTime); } // Create the exponential fit equivalent waveform which has noise eliminated Waveform expFitAbsoluteWaveformDecayingRegion = WaveformExtensions.CreateExponentialFitWaveform( expFitAbsoluteWaveformDecayingRegionConstants.Item1, expFitAbsoluteWaveformDecayingRegionConstants.Item2, timeValues); // Calculate what the decay time end threshold is (1/e % of Ips) double decayTimeEndThreshold = System.Math.Abs(this.PeakCurrentValue) * decayTimeEndPercentOfIps; // Find the first (interpolated) data point that is at 1/e % of Ips value (of the absolute value waveform) DataPoint?decayTimeEndDataPointAbsolute = expFitAbsoluteWaveformDecayingRegion.DataPointAtYThreshold(decayTimeEndThreshold, true); if (decayTimeEndDataPointAbsolute.HasValue) { // Convert the Decay Time end DataPoint to the signed value this.DecayTimeEndDataPoint = decayTimeEndDataPointAbsolute.Value.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Calculate the Decay Time this.DecayTimeValue = this.DecayTimeEndDataPoint.X - this.PeakCurrentDataPoint.X; // Determine if the Decay Time is passing this.DecayTimeIsPassing = DoubleRangeExtensions.BetweenInclusive(this.DecayTimeAllowedMinimum, this.DecayTimeAllowedMaximum, this.DecayTimeValue); } else { // The derived exponential fit waveform didn't go out to a far enough time to capture the 1/e time. } } else { // Could not find any data points in the decay time window. Likely due to a malformed waveform. } } else { // Since the waveform was truncated before even the start of the window to calculate the decaying region, the decay time cannot be calculated. } }
/// <summary> /// Calculates the Rise Time related values /// </summary> /// <param name="riseTimeStartPercent">Rise Time starting percentage (Default: 90%)</param> /// <param name="riseTimeEndPercent">Rise Time ending percentage (Default: 10%)</param> private void CalculateRiseTime(double riseTimeStartPercent, double riseTimeEndPercent) { if (riseTimeStartPercent < 0.0 || riseTimeStartPercent >= 1.0) { throw new ArgumentOutOfRangeException("The Rise Time Start % cannot be less than 0% or greater than or equal to 100%"); } if (riseTimeEndPercent <= 0.0 || riseTimeEndPercent > 1.0) { throw new ArgumentOutOfRangeException("The Rise Time End % cannot be less than or equal to 0% or greater than 100%"); } if (riseTimeStartPercent >= riseTimeEndPercent) { throw new ArgumentOutOfRangeException("The Rise Time Start % cannot be equal to or greater than the Rise Time End %"); } // Generate an absolute-value version of the peak current value double peakCurrentAbsoluteValue = this.PeakCurrentValue.InvertValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Calculate what the rise time end threshold is (a percentage of peak current) double riseTimeEndThreshold = peakCurrentAbsoluteValue * riseTimeEndPercent; // Find the first (interpolated) data point that is at the rise time end threshold (of the absolute value waveform) DataPoint?riseTimeEndAbsoluteDataPoint = this.AbsoluteWaveform.DataPointAtYThreshold(riseTimeEndThreshold, true); if (riseTimeEndAbsoluteDataPoint.HasValue) { // Convert the first Rise Time Data Point to the signed value this.RiseTimeEndDataPoint = riseTimeEndAbsoluteDataPoint.Value.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Trim the waveform, removing everything after the Rise Time End Time Waveform absoluteWaveformUntilRiseTimeEnd = this.AbsoluteWaveform.TrimEnd(this.RiseTimeEndDataPoint.X); // Calculate what the Rise Time Start Threshold is (a percentage of peak current) double riseTimeStartThreshold = peakCurrentAbsoluteValue * riseTimeStartPercent; // Find the last Data Point (interpolated) Data Point that is below the Rise Time Start Threshold (of the absolute trimmed waveform) DataPoint?riseTimeStartAbsoluteDataPoint = absoluteWaveformUntilRiseTimeEnd.DataPointAtYThreshold(riseTimeStartThreshold, false); if (riseTimeStartAbsoluteDataPoint.HasValue) { // Convert the last Rise Time Data Point to the signed value this.RiseTimeStartDataPoint = riseTimeStartAbsoluteDataPoint.Value.InvertYValueIfNegativePolarity(this.WaveformIsPositivePolarity); // Find the Rise Time this.RiseTimeValue = this.RiseTimeEndDataPoint.X - this.RiseTimeStartDataPoint.X; // Determine if the Rise Time is passing this.RiseTimeIsPassing = DoubleRangeExtensions.BetweenInclusive(this.RiseTimeAllowedMinimum, this.RiseTimeAllowedMaximum, this.RiseTimeValue); } else { // Risetime start data point could not be found, no calculations could be made. } } else { // Risetime end data point could not be found, no calculations could be made. } }
/// <summary> /// Generates a new set for the CDM voltage, interpolated from the JS-002 specification. /// </summary> /// <param name="cdmVoltage">The voltage of the CDM pulse waveform (polarity is ignored, absolute value will be used)</param> /// <param name="isLargeTarget">A value indicating whether the CDM target is large or not (if not, then it is small)</param> /// <param name="oscilloscopeIsHighBandwidth">A value indicating whether the oscilloscope is high bandwidth (6GHz+) or not</param> /// <returns>A new set for the CDM voltage, interpolated from the JS-002 specification</returns> private static CDMJS002WaveformCharacteristicsSet GenerateSetForCDMVoltageAndCharacteristics(double cdmVoltage, bool isLargeTarget, bool oscilloscopeIsHighBandwidth) { double absCDMVoltage = System.Math.Abs(cdmVoltage); List <CDMJS002WaveformCharacteristicsSet> possibleSets = (from set in CDMJS002WaveformCharacteristics.characteristicSets where set.IsLargeTarget == isLargeTarget && set.IsHighBandwidth == oscilloscopeIsHighBandwidth select set).ToList(); if (possibleSets.Any(set => set.TestCondition == absCDMVoltage)) { // If the voltage is an exact match to a set, use it return(possibleSets.First(set => set.TestCondition == absCDMVoltage).Clone()); } else { // The voltage isn't an exact match to the table, so interpolate it CDMJS002WaveformCharacteristicsSet below = null; CDMJS002WaveformCharacteristicsSet above = null; // Try to find the closest sets that are below and above the Test Condition voltage foreach (CDMJS002WaveformCharacteristicsSet set in possibleSets) { if (set.TestCondition < absCDMVoltage) { if (below == null || below.TestCondition < set.TestCondition) { below = set; } } else { if (above == null || above.TestCondition > set.TestCondition) { above = set; } } } if (below != null && above != null) { // Interpolate between the below and above table entries double percentWithinRange = DoubleRangeExtensions.PercentWithinRange(absCDMVoltage, above.TestCondition, below.TestCondition); // Only the Peak Current varies between different Test Conditions. // All other properties are constant for a given target size and bandwidth. Tuple <double, double> interpolatedPeakCurrent = new Tuple <double, double>( DoubleRangeExtensions.EquivalentValueInNewRange(percentWithinRange, 1, 0, above.PeakCurrent.Item1, below.PeakCurrent.Item1), DoubleRangeExtensions.EquivalentValueInNewRange(percentWithinRange, 1, 0, above.PeakCurrent.Item2, below.PeakCurrent.Item2)); return(new CDMJS002WaveformCharacteristicsSet() { IsHighBandwidth = below.IsHighBandwidth, IsLargeTarget = below.IsLargeTarget, TestCondition = absCDMVoltage, PeakCurrent = interpolatedPeakCurrent, RiseTimeMax = below.RiseTimeMax, FullWidthAtHalfMaximum = new Tuple <double, double>(below.FullWidthAtHalfMaximum.Item1, below.FullWidthAtHalfMaximum.Item2), UndershootMaxPercent = below.UndershootMaxPercent, }); } else if (below != null) { // The CDM voltage is higher than the highest table entry, so scale the largest one double multiplier = absCDMVoltage / below.TestCondition; // Only the Peak Current varies between different Test Conditions. // All other properties are constant for a given target size and bandwidth. Tuple <double, double> scaledPeakCurrent = new Tuple <double, double>( below.PeakCurrent.Item1 * multiplier, below.PeakCurrent.Item2 * multiplier); return(new CDMJS002WaveformCharacteristicsSet() { IsHighBandwidth = below.IsHighBandwidth, IsLargeTarget = below.IsLargeTarget, TestCondition = absCDMVoltage, PeakCurrent = scaledPeakCurrent, RiseTimeMax = below.RiseTimeMax, FullWidthAtHalfMaximum = new Tuple <double, double>(below.FullWidthAtHalfMaximum.Item1, below.FullWidthAtHalfMaximum.Item2), UndershootMaxPercent = below.UndershootMaxPercent, }); } else if (above != null) { // The CDM voltage is lower than the lowest table entry, so scale the smallest one double multiplier = absCDMVoltage / above.TestCondition; // Only the Peak Current varies between different Test Conditions. // All other properties are constant for a given target size and bandwidth. Tuple <double, double> scaledPeakCurrent = new Tuple <double, double>( above.PeakCurrent.Item1 * multiplier, above.PeakCurrent.Item2 * multiplier); return(new CDMJS002WaveformCharacteristicsSet() { IsHighBandwidth = above.IsHighBandwidth, IsLargeTarget = above.IsLargeTarget, TestCondition = absCDMVoltage, PeakCurrent = scaledPeakCurrent, RiseTimeMax = above.RiseTimeMax, FullWidthAtHalfMaximum = new Tuple <double, double>(above.FullWidthAtHalfMaximum.Item1, above.FullWidthAtHalfMaximum.Item2), UndershootMaxPercent = above.UndershootMaxPercent, }); } else { throw new InvalidOperationException("Finding table entries for the CDM voltage " + absCDMVoltage + " failed."); } } }
/// <summary> /// Generates a new set for the HBM voltage from the JS-001 standard /// </summary> /// <param name="hbmVoltage">The voltage of the HBM pulse waveform (polarity is ignored, absolute value will be used)</param> /// <returns>A new set for the HBM voltage from the JS-001 standard</returns> private static HBM500OhmJS001WaveformCharacteristicsSet GenerateSetForHBMVoltage(double hbmVoltage) { double absHBMVoltage = System.Math.Abs(hbmVoltage); if (HBM500OhmJS001WaveformCharacteristics.characteristicSets.Any(set => set.TestCondition == absHBMVoltage)) { // If the voltage is an exact match to a set, use it return(HBM500OhmJS001WaveformCharacteristics.characteristicSets.First(set => set.TestCondition == absHBMVoltage).Clone()); } else { // The voltage isn't an exact match to the table, so interpolate it HBM500OhmJS001WaveformCharacteristicsSet below = null; HBM500OhmJS001WaveformCharacteristicsSet above = null; // Try to find the closest sets that are below and above the Test Condition voltage foreach (HBM500OhmJS001WaveformCharacteristicsSet set in HBM500OhmJS001WaveformCharacteristics.characteristicSets) { if (set.TestCondition < absHBMVoltage) { if (below == null || below.TestCondition < set.TestCondition) { below = set; } } else { if (above == null || above.TestCondition > set.TestCondition) { above = set; } } } if (below != null && above != null) { // Interpolate between the below and above table entries double percentWithinRange = DoubleRangeExtensions.PercentWithinRange(absHBMVoltage, above.TestCondition, below.TestCondition); // Only the Peak Current varies between different Test Conditions. // All other properties are constant for a given target size and bandwidth. Tuple <double, double> interpolatedPeakCurrent = new Tuple <double, double>( DoubleRangeExtensions.EquivalentValueInNewRange(percentWithinRange, 1, 0, above.PeakCurrent.Item1, below.PeakCurrent.Item1), DoubleRangeExtensions.EquivalentValueInNewRange(percentWithinRange, 1, 0, above.PeakCurrent.Item2, below.PeakCurrent.Item2)); return(new HBM500OhmJS001WaveformCharacteristicsSet() { TestCondition = absHBMVoltage, PeakCurrent = interpolatedPeakCurrent, }); } else if (below != null) { // The HBM voltage is higher than the highest table entry, so scale the largest one double multiplier = absHBMVoltage / below.TestCondition; // Only the Peak Current varies between different Test Conditions. // All other properties are constant for a given target size and bandwidth. Tuple <double, double> scaledPeakCurrent = new Tuple <double, double>( below.PeakCurrent.Item1 * multiplier, below.PeakCurrent.Item2 * multiplier); return(new HBM500OhmJS001WaveformCharacteristicsSet() { TestCondition = absHBMVoltage, PeakCurrent = scaledPeakCurrent, }); } else if (above != null) { // The HBM voltage is lower than the lowest table entry, so scale the smallest one double multiplier = absHBMVoltage / above.TestCondition; // Only the Peak Current varies between different Test Conditions. // All other properties are constant for a given target size and bandwidth. Tuple <double, double> scaledPeakCurrent = new Tuple <double, double>( above.PeakCurrent.Item1 * multiplier, above.PeakCurrent.Item2 * multiplier); return(new HBM500OhmJS001WaveformCharacteristicsSet() { TestCondition = absHBMVoltage, PeakCurrent = scaledPeakCurrent, }); } else { throw new InvalidOperationException("Finding table entries for the HBM voltage " + absHBMVoltage + " failed."); } } }