Represents a data set containing data about an event and the fault conditions that were detected when analyzing the data set.
        public static double[] ModifiedTakagi(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber[] zeros;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            zeros    = faultDataSet.Cycles.Select(cycle => 3 * CycleData.CalculateSequenceComponents(cycle.AN.I, cycle.BN.I, cycle.CN.I)[0]).ToArray();

            return(voltages.Zip(currents, (v, i) => new
            {
                V = v,
                I = i
            })
                   .Zip(zeros, (vi, zero) =>
            {
                ComplexNumber v = vi.V;
                ComplexNumber i = vi.I;
                Angle t = (i / zero).Angle;

                ComplexNumber ejt = new ComplexNumber(t, 1.0D);

                return (v * zero.Conjugate * ejt).Imaginary / (z * i * zero.Conjugate * ejt).Imaginary;
            })
                   .Select(m => m * faultDataSet.LineDistance)
                   .ToArray());
        }
        private static FaultType Simple(FaultLocationDataSet faultDataSet, string parameters)
        {
            Dictionary<string, string> parameterLookup;
            string parameterValue;

            int largestCurrentIndex;
            CycleData largestCurrentCycle;
            FaultType faultType;

            double anCurrentRMS;
            double bnCurrentRMS;
            double cnCurrentRMS;

            double tolerance;
            double largestCurrentRMS;
            double faultCurrentRMS;

            bool anFault;
            bool bnFault;
            bool cnFault;

            parameterLookup = parameters.ParseKeyValuePairs();
            largestCurrentIndex = faultDataSet.Cycles.GetLargestCurrentIndex();
            largestCurrentCycle = faultDataSet.Cycles[largestCurrentIndex];

            if ((object)largestCurrentCycle == null)
                throw new InvalidOperationException("No cycles found in fault data set. Cannot calculate fault type.");

            if (!parameterLookup.TryGetValue("tolerance", out parameterValue) || !double.TryParse(parameterValue, out tolerance))
                tolerance = 10.0D;

            anCurrentRMS = largestCurrentCycle.AN.I.RMS;
            bnCurrentRMS = largestCurrentCycle.BN.I.RMS;
            cnCurrentRMS = largestCurrentCycle.CN.I.RMS;

            largestCurrentRMS = Math.Max(Math.Max(anCurrentRMS, bnCurrentRMS), cnCurrentRMS);
            faultCurrentRMS = largestCurrentRMS * tolerance * 0.01D;

            anFault = (anCurrentRMS >= faultCurrentRMS);
            bnFault = (bnCurrentRMS >= faultCurrentRMS);
            cnFault = (cnCurrentRMS >= faultCurrentRMS);

            if (anFault && bnFault && cnFault)
                faultType = FaultType.ABC;
            else if (anFault && bnFault)
                faultType = FaultType.AB;
            else if (bnFault && cnFault)
                faultType = FaultType.BC;
            else if (cnFault && anFault)
                faultType = FaultType.CA;
            else if (anFault)
                faultType = FaultType.AN;
            else if (bnFault)
                faultType = FaultType.BN;
            else
                faultType = FaultType.CN;

            return faultType;
        }
        private static bool RatedCurrentTrigger(FaultLocationDataSet faultDataSet, string parameters)
        {
            Dictionary <string, string> parameterLookup;
            string parameterValue;
            double ratingMultiplier;

            double anFaultLimit;
            double bnFaultLimit;
            double cnFaultLimit;

            List <int> faultedCycles;
            bool       anFaultCycle;
            bool       bnFaultCycle;
            bool       cnFaultCycle;

            // If no cycles exist in the data set, there is no fault
            if (faultDataSet.Cycles.Count <= 0)
            {
                return(false);
            }

            // Get parameters required for determining the existence of the fault
            parameterLookup = parameters.ParseKeyValuePairs();

            if (!parameterLookup.TryGetValue("ratingMultiplier", out parameterValue) || !double.TryParse(parameterValue, out ratingMultiplier))
            {
                ratingMultiplier = 2.0D;
            }

            // Determine the upper limit of the current during normal conditions
            anFaultLimit = faultDataSet.RatedCurrent * ratingMultiplier;
            bnFaultLimit = faultDataSet.RatedCurrent * ratingMultiplier;
            cnFaultLimit = faultDataSet.RatedCurrent * ratingMultiplier;

            // Build a list of faulted cycles
            faultedCycles = new List <int>();

            for (int i = 0; i < faultDataSet.Cycles.Count; i++)
            {
                CycleData cycle = faultDataSet.Cycles[i];

                anFaultCycle = (cycle.AN.I.RMS >= anFaultLimit);
                bnFaultCycle = (cycle.BN.I.RMS >= bnFaultLimit);
                cnFaultCycle = (cycle.CN.I.RMS >= cnFaultLimit);

                if (anFaultCycle || bnFaultCycle || cnFaultCycle)
                {
                    faultedCycles.Add(i);
                }
            }

            // Provide additional information about which
            // cycles were found to contain fault conditions
            faultDataSet.FaultedCycles = faultedCycles;

            return(faultedCycles.Count > 0);
        }
        private static bool PrefaultMultiplierAndRatedCurrentTrigger(FaultLocationDataSet faultDataSet, string parameters)
        {
            Dictionary<string, string> parameterLookup;
            string parameterValue;
            double prefaultMultiplier;
            double ratingMultiplier;

            double anFaultLimit;
            double bnFaultLimit;
            double cnFaultLimit;

            List<int> faultedCycles;
            bool anFaultCycle;
            bool bnFaultCycle;
            bool cnFaultCycle;

            // If no cycles exist in the data set, there is no fault
            if (faultDataSet.Cycles.Count <= 0)
                return false;

            // Get parameters required for determining the existence of the fault
            parameterLookup = parameters.ParseKeyValuePairs();

            if (!parameterLookup.TryGetValue("prefaultMultiplier", out parameterValue) || !double.TryParse(parameterValue, out prefaultMultiplier))
                prefaultMultiplier = 5.0D;

            if (!parameterLookup.TryGetValue("ratingMultiplier", out parameterValue) || !double.TryParse(parameterValue, out ratingMultiplier))
                ratingMultiplier = 2.0D;

            // Determine the upper limit of the current during normal conditions
            anFaultLimit = Math.Max(faultDataSet.Cycles[0].AN.I.RMS * prefaultMultiplier, faultDataSet.RatedCurrent * ratingMultiplier);
            bnFaultLimit = Math.Max(faultDataSet.Cycles[0].BN.I.RMS * prefaultMultiplier, faultDataSet.RatedCurrent * ratingMultiplier);
            cnFaultLimit = Math.Max(faultDataSet.Cycles[0].CN.I.RMS * prefaultMultiplier, faultDataSet.RatedCurrent * ratingMultiplier);

            // Build a list of faulted cycles
            faultedCycles = new List<int>();

            for (int i = 0; i < faultDataSet.Cycles.Count; i++)
            {
                CycleData cycle = faultDataSet.Cycles[i];

                anFaultCycle = (cycle.AN.I.RMS >= anFaultLimit);
                bnFaultCycle = (cycle.BN.I.RMS >= bnFaultLimit);
                cnFaultCycle = (cycle.CN.I.RMS >= cnFaultLimit);

                if (anFaultCycle || bnFaultCycle || cnFaultCycle)
                    faultedCycles.Add(i);
            }

            // Provide additional information about which
            // cycles were found to contain fault conditions
            faultDataSet.FaultedCycles = faultedCycles;

            return faultedCycles.Count > 0;
        }
        public static double[] Novosel(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber   vPre;
            ComplexNumber   iPre;

            ComplexNumber loadImpedance;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            vPre     = GetFaultVoltage(faultDataSet.PrefaultCycle, faultDataSet.FaultType);
            iPre     = GetFaultCurrent(faultDataSet.PrefaultCycle, faultDataSet.FaultType);

            loadImpedance = (vPre / iPre) - z;

            return(voltages.Zip(currents, (v, i) =>
            {
                ComplexNumber sourceImpedance = faultDataSet.ZSrc;

                // TODO: Test to determine the effect of using -(v - vPre) / (i - iPre) instead
                if (IsNaN(sourceImpedance))
                {
                    sourceImpedance = (v - vPre) / (i - iPre);
                }

                ComplexNumber ab = (v / (z * i)) + (loadImpedance / z) + 1;
                ComplexNumber cd = (v / (z * i)) * (1 + (loadImpedance / z));
                ComplexNumber ef = ((i - iPre) / (z * i)) * (1 + ((loadImpedance + sourceImpedance) / z));

                double a = ab.Real, b = ab.Imaginary;
                double c = cd.Real, d = cd.Imaginary;
                double e = ef.Real, f = ef.Imaginary;

                double left = (a - ((e * b) / f));
                double right = Math.Sqrt(left * left - 4.0D * (c - ((e * d) / f)));
                double m1 = (left + right) / 2.0D;
                double m2 = (left - right) / 2.0D;

                if (MinDistance(m1, 0.0D, 1.0D) < MinDistance(m2, 0.0D, 1.0D))
                {
                    return m1;
                }

                return m2;
            })
                   .Select(m => m * faultDataSet.LineDistance)
                   .ToArray());
        }
        // True if current spikes upward by 300amps within a cycle while in a range between the
        //rated current and a user defined maximum current.
        private static bool RangeSpikeTrigger(FaultLocationDataSet faultDataSet, string parameters)
        {
            Dictionary <string, string> parameterLookup;
            string parameterValue;
            double ratingMultiplier;

            double anFaultLimit;
            double bnFaultLimit;
            double cnFaultLimit;

            List <int> faultedCycles;
            bool       anFaultCycle;
            bool       bnFaultCycle;
            bool       cnFaultCycle;

            // If no cycles exist in the data set, there is no fault
            if (faultDataSet.Cycles.Count <= 0)
            {
                return(false);
            }

            // Get parameters required for determining the existence of the fault
            parameterLookup = parameters.ParseKeyValuePairs();

            if (!parameterLookup.TryGetValue("ratingMultiplier", out parameterValue) || !double.TryParse(parameterValue, out ratingMultiplier))
            {
                ratingMultiplier = 0.50D;
            }

            // Determine the upper limit of the current range during normal conditions
            anFaultLimit = faultDataSet.RatedCurrent * ratingMultiplier;
            bnFaultLimit = faultDataSet.RatedCurrent * ratingMultiplier;
            cnFaultLimit = faultDataSet.RatedCurrent * ratingMultiplier;

            for (int i = 0; i < faultDataSet.Cycles.Count; i++)
            {
                CycleData cycle = faultDataSet.Cycles[i];

                if ((cycle.AN.I.RMS >= faultDataSet.RatedCurrent && cycle.AN.I.RMS <= anFaultLimit ||
                     cycle.BN.I.RMS >= faultDataSet.RatedCurrent && cycle.BN.I.RMS <= bnFaultLimit ||
                     cycle.CN.I.RMS >= faultDataSet.RatedCurrent && cycle.CN.I.RMS <= cnFaultLimit) &&

                    faultDataSet.Cycles[i + 1].AN.I.RMS > faultDataSet.Cycles[i].AN.I.RMS + 300D ||
                    faultDataSet.Cycles[i + 1].BN.I.RMS > faultDataSet.Cycles[i].BN.I.RMS + 300D ||
                    faultDataSet.Cycles[i + 1].CN.I.RMS > faultDataSet.Cycles[i].CN.I.RMS + 300D)
                {
                    return(true);
                }
            }
            return(false);
        }
        public static double[] Eriksson(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber   iPre;

            ComplexNumber sourceImpedance;
            ComplexNumber remoteImpedance;

            sourceImpedance = faultDataSet.ZSrc;
            remoteImpedance = faultDataSet.ZRem;

            if (IsNaN(sourceImpedance) || IsNaN(remoteImpedance))
            {
                return(null);
            }

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            iPre     = GetFaultCurrent(faultDataSet.PrefaultCycle, faultDataSet.FaultType);

            return(voltages.Zip(currents, (v, i) =>
            {
                ComplexNumber ab = (v / (i * z)) + 1 + (remoteImpedance / z);
                ComplexNumber cd = (v / (i * z)) * ((remoteImpedance / z) + 1);
                ComplexNumber ef = ((i - iPre) / (i * z)) * (((sourceImpedance + remoteImpedance) / z) + 1);

                double a = ab.Real, b = ab.Imaginary;
                double c = cd.Real, d = cd.Imaginary;
                double e = ef.Real, f = ef.Imaginary;

                double left = (a - ((e * b) / f));
                double right = Math.Sqrt(left * left - 4.0D * (c - ((e * d) / f)));
                double m1 = (left + right) / 2.0D;
                double m2 = (left - right) / 2.0D;

                if (MinDistance(m1, 0.0D, 1.0D) < MinDistance(m2, 0.0D, 1.0D))
                {
                    return m1;
                }

                return m2;
            })
                   .Select(m => m * faultDataSet.LineDistance)
                   .ToArray());
        }
        public static double[] Reactance(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber nominalImpedance;

            nominalImpedance = GetNominalImpedance(faultDataSet);

            return(faultDataSet.Cycles
                   .Select(cycleData => new
            {
                V = GetFaultVoltage(cycleData, faultDataSet.FaultType),
                I = GetFaultCurrent(cycleData, faultDataSet.FaultType),
                Z = nominalImpedance
            })
                   .Select(cycle => (cycle.V / cycle.I).Imaginary / cycle.Z.Imaginary)
                   .Select(m => m * faultDataSet.LineDistance)
                   .ToArray());
        }
        /// <summary>
        /// Double-ended algorithm for calculating the distance to a fault that was found in the <see cref="FaultLocationDataSet"/>.
        /// </summary>
        /// <param name="localFaultDataSet">The data set used to find the distance to the fault.</param>
        /// <param name="remoteFaultCycle">The cycle of data from the remote station used in the double-ended distance algorithm.</param>
        /// <param name="parameters">Extra parameters to the algorithm.</param>
        /// <returns>A set of distance calculations, one for each cycle of data in <paramref name="localFaultDataSet"/>.</returns>
        public static ComplexNumber[] DoubleEnded(FaultLocationDataSet localFaultDataSet, CycleData remoteFaultCycle, string parameters)
        {
            FaultType     faultType;
            ComplexNumber vfs;
            ComplexNumber ifs;
            ComplexNumber z;

            faultType = localFaultDataSet.FaultType;
            vfs       = GetDoubleEndedFaultVoltage(remoteFaultCycle, faultType);
            ifs       = GetDoubleEndedFaultCurrent(remoteFaultCycle, faultType);
            z         = localFaultDataSet.Z1;

            return(localFaultDataSet.Cycles
                   .Select(cycleData => new
            {
                Vns = GetDoubleEndedFaultVoltage(cycleData, faultType),
                Ins = GetDoubleEndedFaultCurrent(cycleData, faultType)
            })
                   .Select(cycle => (cycle.Vns - vfs + z * ifs) / (z * (cycle.Ins + ifs)))
                   .Select(m => m * localFaultDataSet.LineDistance)
                   .ToArray());
        }
        public static double[] Takagi(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber   iPre;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            iPre     = GetFaultCurrent(faultDataSet.PrefaultCycle, faultDataSet.FaultType);

            return(voltages.Zip(currents, (v, i) =>
            {
                ComplexNumber iSupConjugate = (i - iPre).Conjugate;
                return (v * iSupConjugate).Imaginary / (z * i * iSupConjugate).Imaginary;
            })
                   .Select(m => m * faultDataSet.LineDistance)
                   .ToArray());
        }
Beispiel #11
0
        // Get the nominal impedance value to use in fault calculations, based on the fault type.
        private static ComplexNumber GetNominalImpedance(FaultLocationDataSet faultDataSet)
        {
            switch (faultDataSet.FaultType)
            {
            case FaultType.AN:
            case FaultType.BN:
            case FaultType.CN:
                return(faultDataSet.Zs);

            case FaultType.AB:
            case FaultType.BC:
            case FaultType.CA:
            case FaultType.ABG:
            case FaultType.BCG:
            case FaultType.CAG:
            case FaultType.ABC:
                return(faultDataSet.Z1);

            default:
                throw new ArgumentOutOfRangeException("faultDataSet", string.Format("Unknown fault type: {0}", faultDataSet.FaultType));
            }
        }
        /// <summary>
        /// Double-ended algorithm for calculating the distance to a fault that was found in the <see cref="FaultLocationDataSet"/>.
        /// </summary>
        /// <param name="localFaultDataSet">The data set used to find the distance to the fault.</param>
        /// <param name="remoteFaultCycle">The cycle of data from the remote station used in the double-ended distance algorithm.</param>
        /// <param name="parameters">Extra parameters to the algorithm.</param>
        /// <returns>A set of distance calculations, one for each cycle of data in <paramref name="localFaultDataSet"/>.</returns>
        public static ComplexNumber[] DoubleEnded(FaultLocationDataSet localFaultDataSet, CycleData remoteFaultCycle, string parameters)
        {
            FaultType faultType;
            ComplexNumber vfs;
            ComplexNumber ifs;
            ComplexNumber z;

            faultType = localFaultDataSet.FaultType;
            vfs = GetDoubleEndedFaultVoltage(remoteFaultCycle, faultType);
            ifs = GetDoubleEndedFaultCurrent(remoteFaultCycle, faultType);
            z = localFaultDataSet.Z1;

            return localFaultDataSet.Cycles
                .Select(cycleData => new
                {
                    Vns = GetDoubleEndedFaultVoltage(cycleData, faultType),
                    Ins = GetDoubleEndedFaultCurrent(cycleData, faultType)
                })
                .Select(cycle => (cycle.Vns - vfs + z * ifs) / (z * (cycle.Ins + ifs)))
                .Select(m => m * localFaultDataSet.LineDistance)
                .ToArray();
        }
        public static double[] Takagi(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber iPre;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            iPre = GetFaultCurrent(faultDataSet.PrefaultCycle, faultDataSet.FaultType);

            return voltages.Zip(currents, (v, i) =>
            {
                ComplexNumber iSupConjugate = (i - iPre).Conjugate;
                return (v * iSupConjugate).Imaginary / (z * i * iSupConjugate).Imaginary;
            })
            .Select(m => m * faultDataSet.LineDistance)
            .ToArray();
        }
        public static double[] Novosel(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber vPre;
            ComplexNumber iPre;

            ComplexNumber loadImpedance;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            vPre = GetFaultVoltage(faultDataSet.PrefaultCycle, faultDataSet.FaultType);
            iPre = GetFaultCurrent(faultDataSet.PrefaultCycle, faultDataSet.FaultType);

            loadImpedance = (vPre / iPre) - z;

            return voltages.Zip(currents, (v, i) =>
            {
                ComplexNumber sourceImpedance = faultDataSet.ZSrc;

                // TODO: Test to determine the effect of using -(v - vPre) / (i - iPre) instead
                if (IsNaN(sourceImpedance))
                    sourceImpedance = (v - vPre) / (i - iPre);

                ComplexNumber ab = (v / (z * i)) + (loadImpedance / z) + 1;
                ComplexNumber cd = (v / (z * i)) * (1 + (loadImpedance / z));
                ComplexNumber ef = ((i - iPre) / (z * i)) * (1 + ((loadImpedance + sourceImpedance) / z));

                double a = ab.Real, b = ab.Imaginary;
                double c = cd.Real, d = cd.Imaginary;
                double e = ef.Real, f = ef.Imaginary;

                double left = (a - ((e * b) / f));
                double right = Math.Sqrt(left * left - 4.0D * (c - ((e * d) / f)));
                double m1 = (left + right) / 2.0D;
                double m2 = (left - right) / 2.0D;

                if (MinDistance(m1, 0.0D, 1.0D) < MinDistance(m2, 0.0D, 1.0D))
                    return m1;

                return m2;
            })
            .Select(m => m * faultDataSet.LineDistance)
            .ToArray();
        }
        public static double[] Simple(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber nominalImpedance;

            nominalImpedance = GetNominalImpedance(faultDataSet);

            return faultDataSet.Cycles
                .Select(cycleData => new
                {
                    V = GetFaultVoltage(cycleData, faultDataSet.FaultType),
                    I = GetFaultCurrent(cycleData, faultDataSet.FaultType),
                    Z = nominalImpedance
                })
                .Select(cycle => (cycle.V.Magnitude / cycle.I.Magnitude) / cycle.Z.Magnitude)
                .Select(m => m * faultDataSet.LineDistance)
                .ToArray();
        }
        public static double[] Eriksson(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber iPre;

            ComplexNumber sourceImpedance;
            ComplexNumber remoteImpedance;

            sourceImpedance = faultDataSet.ZSrc;
            remoteImpedance = faultDataSet.ZRem;

            if (IsNaN(sourceImpedance) || IsNaN(remoteImpedance))
                return null;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            iPre = GetFaultCurrent(faultDataSet.PrefaultCycle, faultDataSet.FaultType);

            return voltages.Zip(currents, (v, i) =>
            {
                ComplexNumber ab = (v / (i * z)) + 1 + (remoteImpedance / z);
                ComplexNumber cd = (v / (i * z)) * ((remoteImpedance / z) + 1);
                ComplexNumber ef = ((i - iPre) / (i * z)) * (((sourceImpedance + remoteImpedance) / z) + 1);

                double a = ab.Real, b = ab.Imaginary;
                double c = cd.Real, d = cd.Imaginary;
                double e = ef.Real, f = ef.Imaginary;

                double left = (a - ((e * b) / f));
                double right = Math.Sqrt(left * left - 4.0D * (c - ((e * d) / f)));
                double m1 = (left + right) / 2.0D;
                double m2 = (left - right) / 2.0D;

                if (MinDistance(m1, 0.0D, 1.0D) < MinDistance(m2, 0.0D, 1.0D))
                    return m1;

                return m2;
            })
            .Select(m => m * faultDataSet.LineDistance)
            .ToArray();
        }
        public static double[] ModifiedTakagi(FaultLocationDataSet faultDataSet, string parameters)
        {
            ComplexNumber z;

            ComplexNumber[] voltages;
            ComplexNumber[] currents;
            ComplexNumber[] zeros;

            z = GetNominalImpedance(faultDataSet);

            voltages = faultDataSet.Cycles.Select(cycle => GetFaultVoltage(cycle, faultDataSet.FaultType)).ToArray();
            currents = faultDataSet.Cycles.Select(cycle => GetFaultCurrent(cycle, faultDataSet.FaultType)).ToArray();
            zeros = faultDataSet.Cycles.Select(cycle => 3 * CycleData.CalculateSequenceComponents(cycle.AN.I, cycle.BN.I, cycle.CN.I)[0]).ToArray();

            return voltages.Zip(currents, (v, i) => new
            {
                V = v,
                I = i
            })
            .Zip(zeros, (vi, zero) =>
            {
                ComplexNumber v = vi.V;
                ComplexNumber i = vi.I;
                Angle t = (i / zero).Angle;

                ComplexNumber ejt = new ComplexNumber(t, 1.0D);

                return (v * zero.Conjugate * ejt).Imaginary / (z * i * zero.Conjugate * ejt).Imaginary;
            })
            .Select(m => m * faultDataSet.LineDistance)
            .ToArray();
        }
        /// <summary>
        /// Populate known voltage and current data from PQDIF file.
        /// </summary>
        /// <param name="faultDataSet">Fault data set to be populated.</param>
        /// <param name="settings">Source parameters.</param>
        /// <param name="line">Associated XML event file definition.</param>
        public static void PopulateDataSet(FaultLocationDataSet faultDataSet, Dictionary<string, string> settings, Line line)
        {
            string fileName;

            if ((object)line == null)
                throw new ArgumentNullException("line");

            if (!settings.TryGetValue("fileName", out fileName) || !File.Exists(fileName))
                throw new ArgumentException("Parameters must define a valid \"fileName\" setting.");

            // Comtrade parsing will require a CFG file, make sure this exists...
            string directory = Path.GetDirectoryName(fileName) ?? string.Empty;
            string rootFileName = FilePath.GetFileNameWithoutExtension(fileName);
            string configurationFileName = Path.Combine(directory, rootFileName + ".cfg");

            if (!File.Exists(configurationFileName))
                throw new FileNotFoundException(string.Format("Associated CFG file \"{0}\" for COMTRADE data file does not exist - cannot parse COMTRADE file.", configurationFileName));

            // Parse configuration file
            Schema schema = new Schema(configurationFileName);

            // Find <Channels> element in XML line definition
            XElement channels = line.ChannelsElement;

            if ((object)channels == null)
                throw new NullReferenceException("No \"<channels>\" element was found in event file definition - cannot load COMTRADE data file.");

            // Extract COMTRADE channel ID's for desired voltage and current elements
            IEnumerable<Tuple<int, int>> vaIndexes = GetValueIndex(schema, channels, "VA").ToList();
            IEnumerable<Tuple<int, int>> vbIndexes = GetValueIndex(schema, channels, "VB").ToList();
            IEnumerable<Tuple<int, int>> vcIndexes = GetValueIndex(schema, channels, "VC").ToList();
            IEnumerable<Tuple<int, int>> iaIndexes = GetValueIndex(schema, channels, "IA").ToList();
            IEnumerable<Tuple<int, int>> ibIndexes = GetValueIndex(schema, channels, "IB").ToList();
            IEnumerable<Tuple<int, int>> icIndexes = GetValueIndex(schema, channels, "IC").ToList();

            List<long> times = new List<long>();
            List<double> vaValues = new List<double>();
            List<double> vbValues = new List<double>();
            List<double> vcValues = new List<double>();
            List<double> iaValues = new List<double>();
            List<double> ibValues = new List<double>();
            List<double> icValues = new List<double>();

            SampleRate sampleRate;

            ValidateIndexes("VA", vaIndexes);
            ValidateIndexes("VB", vbIndexes);
            ValidateIndexes("VC", vcIndexes);
            ValidateIndexes("IA", iaIndexes);
            ValidateIndexes("IB", ibIndexes);
            ValidateIndexes("IC", icIndexes);

            // Create a new COMTRADE file parser
            using (Parser parser = new Parser()
            {
                Schema = schema,
                FileName = fileName,
                InferTimeFromSampleRates = true
            })
            {
                // Open COMTRADE data files
                parser.OpenFiles();

                faultDataSet.Frequency = schema.NominalFrequency;
                sampleRate = schema.SampleRates.First();

                if (sampleRate.Rate != 0)
                    faultDataSet.SetSampleRates((int)(sampleRate.Rate / faultDataSet.Frequency));

                // Read all COMTRADE records
                while (parser.ReadNext())
                {
                    times.Add(parser.Timestamp.Ticks);
                    vaValues.Add(GetValue(parser, vaIndexes));
                    vbValues.Add(GetValue(parser, vbIndexes));
                    vcValues.Add(GetValue(parser, vcIndexes));
                    iaValues.Add(GetValue(parser, iaIndexes));
                    ibValues.Add(GetValue(parser, ibIndexes));
                    icValues.Add(GetValue(parser, icIndexes));
                }
            }

            // Populate voltage data set
            faultDataSet.Voltages.AN.Times = times.ToArray();
            faultDataSet.Voltages.AN.Measurements = vaValues.ToArray();
            faultDataSet.Voltages.BN.Times = times.ToArray();
            faultDataSet.Voltages.BN.Measurements = vbValues.ToArray();
            faultDataSet.Voltages.CN.Times = times.ToArray();
            faultDataSet.Voltages.CN.Measurements = vcValues.ToArray();

            // Populate current data set
            faultDataSet.Currents.AN.Times = times.ToArray();
            faultDataSet.Currents.AN.Measurements = iaValues.ToArray();
            faultDataSet.Currents.BN.Times = times.ToArray();
            faultDataSet.Currents.BN.Measurements = ibValues.ToArray();
            faultDataSet.Currents.CN.Times = times.ToArray();
            faultDataSet.Currents.CN.Measurements = icValues.ToArray();
        }
        private static FaultType Simple(FaultLocationDataSet faultDataSet, string parameters)
        {
            Dictionary <string, string> parameterLookup;
            string parameterValue;

            int       largestCurrentIndex;
            CycleData largestCurrentCycle;
            FaultType faultType;

            double anCurrentRMS;
            double bnCurrentRMS;
            double cnCurrentRMS;

            double tolerance;
            double largestCurrentRMS;
            double faultCurrentRMS;

            bool anFault;
            bool bnFault;
            bool cnFault;

            parameterLookup     = parameters.ParseKeyValuePairs();
            largestCurrentIndex = faultDataSet.Cycles.GetLargestCurrentIndex();
            largestCurrentCycle = faultDataSet.Cycles[largestCurrentIndex];

            if ((object)largestCurrentCycle == null)
            {
                throw new InvalidOperationException("No cycles found in fault data set. Cannot calculate fault type.");
            }

            if (!parameterLookup.TryGetValue("tolerance", out parameterValue) || !double.TryParse(parameterValue, out tolerance))
            {
                tolerance = 10.0D;
            }

            anCurrentRMS = largestCurrentCycle.AN.I.RMS;
            bnCurrentRMS = largestCurrentCycle.BN.I.RMS;
            cnCurrentRMS = largestCurrentCycle.CN.I.RMS;

            largestCurrentRMS = Math.Max(Math.Max(anCurrentRMS, bnCurrentRMS), cnCurrentRMS);
            faultCurrentRMS   = largestCurrentRMS * tolerance * 0.01D;

            anFault = (anCurrentRMS >= faultCurrentRMS);
            bnFault = (bnCurrentRMS >= faultCurrentRMS);
            cnFault = (cnCurrentRMS >= faultCurrentRMS);

            if (anFault && bnFault && cnFault)
            {
                faultType = FaultType.ABC;
            }
            else if (anFault && bnFault)
            {
                faultType = FaultType.AB;
            }
            else if (bnFault && cnFault)
            {
                faultType = FaultType.BC;
            }
            else if (cnFault && anFault)
            {
                faultType = FaultType.CA;
            }
            else if (anFault)
            {
                faultType = FaultType.AN;
            }
            else if (bnFault)
            {
                faultType = FaultType.BN;
            }
            else
            {
                faultType = FaultType.CN;
            }

            return(faultType);
        }
        private void InsertFaultDistances(FaultLocationDataSet faultDataSet, SqlCommand command, int cycleIndex, int cycleID)
        {
            IDbDataParameter algorithmParameter = command.CreateParameter();
            IDbDataParameter distanceParameter = command.CreateParameter();
            double distance;

            command.CommandText = "INSERT INTO FaultDistance (CycleID, Algorithm, Distance) VALUES (@cycleID, @algorithm, @distance)";
            command.Parameters.AddWithValue("@cycleID", cycleID);
            command.Parameters.Add(algorithmParameter);
            command.Parameters.Add(distanceParameter);

            algorithmParameter.ParameterName = "@algorithm";
            distanceParameter.ParameterName = "@distance";

            foreach (KeyValuePair<string, double[]> faultDistances in faultDataSet.FaultDistances)
            {
                distance = faultDistances.Value[cycleIndex];

                if (!double.IsNaN(distance))
                {
                    algorithmParameter.Value = faultDistances.Key;
                    distanceParameter.Value = distance;
                    command.ExecuteNonQuery();
                }
            }

            command.Parameters.Clear();
        }
        private void InsertFaultData(Line line, FaultLocationDataSet faultDataSet, SqlCommand command, int fileGroupID)
        {
            int lineID;
            int lineDisturbanceID;

            int largestCurrentIndex;
            double faultDistance;

            // Get largest current index and median fault distance for that cycle
            largestCurrentIndex = faultDataSet.Cycles.GetLargestCurrentIndex();

            faultDistance = faultDataSet.FaultDistances.Values
                .Select(distances => distances[largestCurrentIndex])
                .OrderBy(distance => distance)
                .ToArray()[faultDataSet.FaultDistances.Count / 2];

            // Get ID of the line associated with this fault data
            command.CommandText = "SELECT ID FROM Line WHERE FLEID = @fleID";
            command.Parameters.AddWithValue("@fleID", line.ID);
            lineID = Convert.ToInt32(command.ExecuteScalar());
            command.Parameters.Clear();

            // Insert a disturbance record for this line
            command.CommandText = "INSERT INTO LineDisturbance (LineID, FileGroupID, FaultType, LargestCurrentIndex, " +
                                  "FaultDistance, IAMax, IBMax, ICMax, VAMin, VBMin, VCMin) VALUES (@lineID, @fileGroupID, " +
                                  "@faultType, @largestCurrentIndex, @faultDistance, @iaMax, @ibMax, @icMax, @vaMin, @vbMin, @vcMin)";

            command.Parameters.AddWithValue("@lineID", lineID);
            command.Parameters.AddWithValue("@fileGroupID", fileGroupID);
            command.Parameters.AddWithValue("@faultType", faultDataSet.FaultType.ToString());
            command.Parameters.AddWithValue("@largestCurrentIndex", largestCurrentIndex);
            command.Parameters.AddWithValue("@faultDistance", faultDistance);
            command.Parameters.AddWithValue("@iaMax", faultDataSet.Cycles.Max(cycle => cycle.AN.I.RMS));
            command.Parameters.AddWithValue("@ibMax", faultDataSet.Cycles.Max(cycle => cycle.BN.I.RMS));
            command.Parameters.AddWithValue("@icMax", faultDataSet.Cycles.Max(cycle => cycle.CN.I.RMS));
            command.Parameters.AddWithValue("@vaMin", faultDataSet.Cycles.Min(cycle => cycle.AN.V.RMS));
            command.Parameters.AddWithValue("@vbMin", faultDataSet.Cycles.Min(cycle => cycle.BN.V.RMS));
            command.Parameters.AddWithValue("@vcMin", faultDataSet.Cycles.Min(cycle => cycle.CN.V.RMS));
            command.ExecuteNonQuery();
            command.Parameters.Clear();

            // Get the ID of the disturbance record
            command.CommandText = "SELECT @@IDENTITY";
            lineDisturbanceID = Convert.ToInt32(command.ExecuteScalar());

            // Insert data for each cycle into the database
            for (int i = 0; i < faultDataSet.Cycles.Count; i++)
                InsertCycleData(faultDataSet, command, lineDisturbanceID, i);
        }
        // Get the nominal impedance value to use in fault calculations, based on the fault type.
        private static ComplexNumber GetNominalImpedance(FaultLocationDataSet faultDataSet)
        {
            switch (faultDataSet.FaultType)
            {
                case FaultType.AN:
                case FaultType.BN:
                case FaultType.CN:
                    return faultDataSet.Zs;

                case FaultType.AB:
                case FaultType.BC:
                case FaultType.CA:
                case FaultType.ABG:
                case FaultType.BCG:
                case FaultType.CAG:
                case FaultType.ABC:
                    return faultDataSet.Z1;

                default:
                    throw new ArgumentOutOfRangeException("faultDataSet", string.Format("Unknown fault type: {0}", faultDataSet.FaultType));
            }
        }
        private void InsertCycleData(FaultLocationDataSet faultDataSet, SqlCommand command, int lineDisturbanceID, int cycleIndex)
        {
            int cycleID;
            CycleData cycle;
            ComplexNumber[] sequenceVoltages;
            ComplexNumber[] sequenceCurrents;

            // Get the cycle data and sequence
            // components from the fault data set
            cycle = faultDataSet.Cycles[cycleIndex];
            sequenceVoltages = CycleData.CalculateSequenceComponents(cycle.AN.V, cycle.BN.V, cycle.CN.V);
            sequenceCurrents = CycleData.CalculateSequenceComponents(cycle.AN.I, cycle.BN.I, cycle.CN.I);

            // Insert the cycle data into the database
            command.CommandText = "INSERT INTO Cycle (LineDisturbanceID, TimeStart, CycleIndex," +
                                  "ANVoltagePeak, ANVoltageRMS, ANVoltagePhase, ANCurrentPeak, ANCurrentRMS, ANCurrentPhase, " +
                                  "BNVoltagePeak, BNVoltageRMS, BNVoltagePhase, BNCurrentPeak, BNCurrentRMS, BNCurrentPhase, " +
                                  "CNVoltagePeak, CNVoltageRMS, CNVoltagePhase, CNCurrentPeak, CNCurrentRMS, CNCurrentPhase, " +
                                  "PositiveVoltageMagnitude, PositiveVoltageAngle, PositiveCurrentMagnitude, PositiveCurrentAngle, " +
                                  "NegativeVoltageMagnitude, NegativeVoltageAngle, NegativeCurrentMagnitude, NegativeCurrentAngle, " +
                                  "ZeroVoltageMagnitude, ZeroVoltageAngle, ZeroCurrentMagnitude, ZeroCurrentAngle) " +
                                  "VALUES (@lineDisturbanceID, @timeStart, @cycleIndex, " +
                                  "@anVoltagePeak, @anVoltageRMS, @anVoltagePhase, @anCurrentPeak, @anCurrentRMS, @anCurrentPhase, " +
                                  "@bnVoltagePeak, @bnVoltageRMS, @bnVoltagePhase, @bnCurrentPeak, @bnCurrentRMS, @bnCurrentPhase, " +
                                  "@cnVoltagePeak, @cnVoltageRMS, @cnVoltagePhase, @cnCurrentPeak, @cnCurrentRMS, @cnCurrentPhase, " +
                                  "@positiveVoltageMagnitude, @positiveVoltageAngle, @positiveCurrentMagnitude, @positiveCurrentAngle, " +
                                  "@negativeVoltageMagnitude, @negativeVoltageAngle, @negativeCurrentMagnitude, @negativeCurrentAngle, " +
                                  "@zeroVoltageMagnitude, @zeroVoltageAngle, @zeroCurrentMagnitude, @zeroCurrentAngle)";

            command.Parameters.AddWithValue("@lineDisturbanceID", lineDisturbanceID);
            command.Parameters.AddWithValue("@timeStart", cycle.StartTime);
            command.Parameters.AddWithValue("@cycleIndex", cycleIndex);
            command.Parameters.AddWithValue("@anVoltagePeak", cycle.AN.V.Peak);
            command.Parameters.AddWithValue("@anVoltageRMS", cycle.AN.V.RMS);
            command.Parameters.AddWithValue("@anVoltagePhase", cycle.AN.V.Phase.ToDegrees());
            command.Parameters.AddWithValue("@anCurrentPeak", cycle.AN.I.Peak);
            command.Parameters.AddWithValue("@anCurrentRMS", cycle.AN.I.RMS);
            command.Parameters.AddWithValue("@anCurrentPhase", cycle.AN.I.Phase.ToDegrees());
            command.Parameters.AddWithValue("@bnVoltagePeak", cycle.BN.V.Peak);
            command.Parameters.AddWithValue("@bnVoltageRMS", cycle.BN.V.RMS);
            command.Parameters.AddWithValue("@bnVoltagePhase", cycle.BN.V.Phase.ToDegrees());
            command.Parameters.AddWithValue("@bnCurrentPeak", cycle.BN.I.Peak);
            command.Parameters.AddWithValue("@bnCurrentRMS", cycle.BN.I.RMS);
            command.Parameters.AddWithValue("@bnCurrentPhase", cycle.BN.I.Phase.ToDegrees());
            command.Parameters.AddWithValue("@cnVoltagePeak", cycle.CN.V.Peak);
            command.Parameters.AddWithValue("@cnVoltageRMS", cycle.CN.V.RMS);
            command.Parameters.AddWithValue("@cnVoltagePhase", cycle.CN.V.Phase.ToDegrees());
            command.Parameters.AddWithValue("@cnCurrentPeak", cycle.CN.I.Peak);
            command.Parameters.AddWithValue("@cnCurrentRMS", cycle.CN.I.RMS);
            command.Parameters.AddWithValue("@cnCurrentPhase", cycle.CN.I.Phase.ToDegrees());
            command.Parameters.AddWithValue("@positiveVoltageMagnitude", sequenceVoltages[1].Magnitude);
            command.Parameters.AddWithValue("@positiveVoltageAngle", sequenceVoltages[1].Angle.ToDegrees());
            command.Parameters.AddWithValue("@negativeVoltageMagnitude", sequenceVoltages[2].Magnitude);
            command.Parameters.AddWithValue("@negativeVoltageAngle", sequenceVoltages[2].Angle.ToDegrees());
            command.Parameters.AddWithValue("@zeroVoltageMagnitude", sequenceVoltages[0].Magnitude);
            command.Parameters.AddWithValue("@zeroVoltageAngle", sequenceVoltages[0].Angle.ToDegrees());
            command.Parameters.AddWithValue("@positiveCurrentMagnitude", sequenceCurrents[1].Magnitude);
            command.Parameters.AddWithValue("@positiveCurrentAngle", sequenceCurrents[1].Angle.ToDegrees());
            command.Parameters.AddWithValue("@negativeCurrentMagnitude", sequenceCurrents[2].Magnitude);
            command.Parameters.AddWithValue("@negativeCurrentAngle", sequenceCurrents[2].Angle.ToDegrees());
            command.Parameters.AddWithValue("@zeroCurrentMagnitude", sequenceCurrents[0].Magnitude);
            command.Parameters.AddWithValue("@zeroCurrentAngle", sequenceCurrents[0].Angle.ToDegrees());

            command.ExecuteNonQuery();
            command.Parameters.Clear();

            // Get the ID of the cycle data that was entered
            command.CommandText = "SELECT @@IDENTITY";
            cycleID = Convert.ToInt32(command.ExecuteScalar());

            // Insert fault distances calculated for this cycle
            InsertFaultDistances(faultDataSet, command, cycleIndex, cycleID);
        }
        // True if current spikes upward by 300amps within a cycle while in a range between the
        //rated current and a user defined maximum current.
        private static bool RangeSpikeTrigger(FaultLocationDataSet faultDataSet, string parameters)
        {
            Dictionary<string, string> parameterLookup;
            string parameterValue;
            double ratingMultiplier;

            double anFaultLimit;
            double bnFaultLimit;
            double cnFaultLimit;

            List<int> faultedCycles;
            bool anFaultCycle;
            bool bnFaultCycle;
            bool cnFaultCycle;

            // If no cycles exist in the data set, there is no fault
            if (faultDataSet.Cycles.Count <= 0)
                return false;

            // Get parameters required for determining the existence of the fault
            parameterLookup = parameters.ParseKeyValuePairs();

            if (!parameterLookup.TryGetValue("ratingMultiplier", out parameterValue) || !double.TryParse(parameterValue, out ratingMultiplier))
                ratingMultiplier = 0.50D;

            // Determine the upper limit of the current range during normal conditions
            anFaultLimit =  faultDataSet.RatedCurrent * ratingMultiplier;
            bnFaultLimit =  faultDataSet.RatedCurrent * ratingMultiplier;
            cnFaultLimit =  faultDataSet.RatedCurrent * ratingMultiplier;

            for (int i = 0; i < faultDataSet.Cycles.Count; i++)
            {
                CycleData cycle = faultDataSet.Cycles[i];

                if ((cycle.AN.I.RMS >= faultDataSet.RatedCurrent && cycle.AN.I.RMS <= anFaultLimit ||
                     cycle.BN.I.RMS >= faultDataSet.RatedCurrent && cycle.BN.I.RMS <= bnFaultLimit ||
                     cycle.CN.I.RMS >= faultDataSet.RatedCurrent && cycle.CN.I.RMS <= cnFaultLimit) &&

                     faultDataSet.Cycles[i + 1].AN.I.RMS > faultDataSet.Cycles[i].AN.I.RMS + 300D ||
                     faultDataSet.Cycles[i + 1].BN.I.RMS > faultDataSet.Cycles[i].BN.I.RMS + 300D ||
                     faultDataSet.Cycles[i + 1].CN.I.RMS > faultDataSet.Cycles[i].CN.I.RMS + 300D)

                    return true;
            }
            return false;
        }