// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace OpenTap { /// /// A that has an RF cable loss parameter. /// [Display("RF Connection", "Directionless RF connection modeled as a set of cable loss points with frequency and loss values.", "Basic Connections")] public class RfConnection : Connection { /// /// Represents a point in a frequency/loss table. /// public class CableLossPoint : ValidatingObject { internal class FrequencyComparer : IComparer { public static readonly FrequencyComparer Instance = new FrequencyComparer(); public int Compare(CableLossPoint x, CableLossPoint y) { if (ReferenceEquals(x, y)) return 0; if (ReferenceEquals(null, y)) return 1; if (ReferenceEquals(null, x)) return -1; var frequencyComparison = x.Frequency.CompareTo(y.Frequency); return frequencyComparison; } } /// Returns an error if the frequency is less 0. protected override string GetError(string propertyName = null) { if (propertyName == nameof(Frequency) || propertyName == null) { if (Frequency < 0) return "Frequency must not be negative"; } return null; } /// /// The frequency at which the loss is applied. /// [Unit("Hz", true)] public double Frequency { get; set; } /// /// The cable loss in dB. /// [Unit("dB")] public double Loss { get; set; } static readonly UnitAttribute frequencyUnit = typeof(CableLossPoint).GetProperty(nameof(Frequency)).GetAttribute(); static readonly UnitAttribute lossUnit = typeof(CableLossPoint).GetProperty(nameof(Loss)).GetAttribute(); /// /// Prints the loss point, e.g "10dB @ 100kHz". /// /// public override string ToString() { var freqParser = new NumberFormatter(CultureInfo.CurrentCulture, frequencyUnit) { IsCompact = true }; var lossParser = new NumberFormatter(CultureInfo.CurrentCulture, lossUnit) { IsCompact = true }; return $"{lossParser.FormatNumber(Loss)} @{freqParser.FormatNumber(Frequency)}"; } } /// /// The cable loss in dB between the two ports. /// [Display("Cable Loss")] public List CableLoss { get; set; } /// /// Initializes a new instance of the class. /// public RfConnection() { Name = "RF"; CableLoss = new List(); Rules.Add(() => CableLoss.Any(c => c.Frequency < 0) == false, "Frequency must not be negative.", "CableLoss"); Rules.Add(() => CableLoss.Count == CableLoss.Select(c => c.Frequency).Distinct().Count(), "A frequency may only be specified once.", "CableLoss"); Rules.Add(() => PortDirectionsAreGood(), () => "An output port cannot be connected to another output port.", "Port1"); Rules.Add(() => PortDirectionsAreGood(), () => "An output port cannot be connected to another output port.", "Port2"); } private bool PortDirectionsAreGood() { return (Port1 == null) || (Port2 == null) || ((Port1 is OutputPort) ? !(Port2 is OutputPort) : true); } /// /// Given a particular frequency, an interpolated CableLoss value is returned, based on CableLoss values at the two closest frequencies. /// If exact frequency is defined, that CableLoss value will be returned. /// public double GetInterpolatedCableLoss(double frequency) { // If there are no cable loss points configured assume no loss. if (CableLoss.Count == 0) return 0.0; // If there is only one point there is nothing to interpolate if (CableLoss.Count == 1) return CableLoss[0].Loss; // Sort the loss table by frequency. // only do so if it actually needs to be sorted. if(!CableLoss.IsSortedBy(loss => loss.Frequency)) CableLoss = CableLoss.OrderBy(loss => loss.Frequency).ToList(); // for searching. var loss = new CableLossPoint { Frequency = frequency }; // Find the index by binary search. // if the result >= 0 it means an exact match was found. // if the result <0 it means that the match lies somewhere between two points or at the bounds. int index = CableLoss.BinarySearch(loss, CableLossPoint.FrequencyComparer.Instance); // Calculate loss using linear interpolation or nearest neighbour when outside the bounds. if (index < 0) { // the match is at the bounds. // that means the index found is the first element greater than the searched frequency. // hence 0 -> the result is less than the minimum (nearest interpolation) // and count -> the result is greater than the maximum. (nearest interpolation). // otherwise interpolate between index -1 and index. index = ~index; } else { // exact match. return CableLoss[index].Loss; } if (index == 0) return CableLoss[0].Loss; if (index == CableLoss.Count) return CableLoss[index - 1].Loss; CableLossPoint below = CableLoss[index - 1]; //Check for below, or if value exists. CableLossPoint above = CableLoss[index]; // linear interpolation. return below.Loss + (frequency - below.Frequency) * (above.Loss - below.Loss) / (above.Frequency - below.Frequency); } } }