//****************************************************************************************************** // ComplexNumber.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 04/14/2016 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Data.SqlTypes; using System.Text; using System.Text.RegularExpressions; using Microsoft.SqlServer.Server; namespace GSF.Core.SqlClr { /// /// Defines a complex number type that can be used in a SQL Server database. /// [Serializable] [SqlUserDefinedType(Format.Native, IsByteOrdered = true)] public struct ComplexNumber : INullable { #region [ Members ] // Fields private bool m_isNull; private double m_real; private double m_imaginary; #endregion #region [ Properties ] /// /// Gets a value that indicates whether the complex number is null. /// public bool IsNull { get { return m_isNull; } } /// /// Gets or sets the real component of a complex number. /// /// /// Usage example: /// /// /// DECLARE @tbl TABLE(Num COMPLEX) /// INSERT INTO @tbl VALUES('1+5i') /// UPDATE @tbl SET Num.Real = 5 /// SELECT Num.ToString(), Num.Real FROM @tbl /// -- Returns [Num: 5+5i, Real: 5] /// /// public double Real { get { return m_real; } set { m_real = value; } } /// /// Gets or sets the imaginary component of a complex number. /// /// /// Usage example: /// /// /// DECLARE @tbl TABLE(Num COMPLEX) /// INSERT INTO @tbl VALUES('1+5i') /// UPDATE @tbl SET Num.Imaginary = 1 /// SELECT Num.ToString(), Num.Imaginary FROM @tbl /// -- Returns [Num: 1+1i, Imaginary: 1] /// /// public double Imaginary { get { return m_imaginary; } set { m_imaginary = value; } } /// /// Gets or sets the magnitude of a complex number. /// /// /// Usage example: /// /// /// DECLARE @tbl TABLE(Num COMPLEX) /// INSERT INTO @tbl VALUES('1+5i') /// UPDATE @tbl SET Num.Magnitude = 5 /// SELECT Num.ToString(), Num.Magnitude FROM @tbl /// -- Returns [Num: 0.98058+4.9029i, Magnitude: 5] /// /// public double Magnitude { get { return Math.Sqrt(m_real * m_real + m_imaginary * m_imaginary); } set { double angle = Angle; m_real = value * Math.Cos(angle); m_imaginary = value * Math.Sin(angle); } } /// /// Gets or sets the angle of a complex number. /// /// /// Usage example: /// /// /// DECLARE @tbl TABLE(Num COMPLEX) /// INSERT INTO @tbl VALUES('1+5i') /// UPDATE @tbl SET Num.Angle = 1 /// SELECT Num.ToString(), Num.Angle FROM @tbl /// -- Returns [Num: 2.75501+4.29068i, Angle: 1] /// /// public double Angle { get { return Math.Atan2(m_imaginary, m_real); } set { double magnitude = Magnitude; m_real = magnitude * Math.Cos(value); m_imaginary = magnitude * Math.Sin(value); } } #endregion #region [ Methods ] /// /// Returns the complex conjugate of a complex number. /// /// The complex conjugate. /// /// Usage example: /// /// /// DECLARE @complex COMPLEX = '1+5i' /// SELECT @complex.Conjugate().ToString() /// -- Returns 1-5i /// /// [SqlMethod(OnNullCall = false, IsDeterministic = true)] public ComplexNumber Conjugate() { ComplexNumber num = new ComplexNumber(); num.m_real = m_real; num.m_imaginary = -m_imaginary; return num; } /// /// Returns the negative value of the complex number. /// /// The negative value. /// /// Usage example: /// /// /// DECLARE @complex COMPLEX = '1+5i' /// SELECT @complex.Negate().ToString() /// -- Returns -1-5i /// /// [SqlMethod(OnNullCall = false, IsDeterministic = true)] public ComplexNumber Negate() { ComplexNumber num = new ComplexNumber(); num.m_real = -m_real; num.m_imaginary = -m_imaginary; return num; } /// /// Returns the sum of two complex numbers. /// /// The number to be added to this one. /// The sum. /// /// Usage example: /// /// /// DECLARE @complex COMPLEX = '1+5i' /// SELECT @complex.[Add]('-5+1i').ToString() /// -- Returns -4+6i /// /// [SqlMethod(OnNullCall = false, IsDeterministic = true)] public ComplexNumber Add(ComplexNumber num) { ComplexNumber result = new ComplexNumber(); result.m_real = m_real + num.m_real; result.m_imaginary = m_imaginary + num.m_imaginary; return result; } /// /// Returns the difference of two complex numbers. /// /// The number to be subtracted from this one. /// The difference. /// /// Usage example: /// /// /// DECLARE @complex COMPLEX = '1+5i' /// SELECT @complex.Subtract('-5+1i').ToString() /// -- Returns 6+4i /// /// [SqlMethod(OnNullCall = false, IsDeterministic = true)] public ComplexNumber Subtract(ComplexNumber num) { ComplexNumber result = new ComplexNumber(); result.m_real = m_real - num.m_real; result.m_imaginary = m_imaginary - num.m_imaginary; return result; } /// /// Returns the product of two complex numbers. /// /// The number to be multiplied with this one. /// The product. /// /// Usage example: /// /// /// DECLARE @complex COMPLEX = '1+5i' /// SELECT @complex.Multiply('-5+1i').ToString() /// -- Returns -10-24i /// /// [SqlMethod(OnNullCall = false, IsDeterministic = true)] public ComplexNumber Multiply(ComplexNumber num) { ComplexNumber result = new ComplexNumber(); result.SetPhasor(Magnitude * num.Magnitude, Angle + num.Angle); return result; } /// /// Returns the quotient of two complex numbers. /// /// The number to divide by this one. /// The quotient. /// /// Usage example: /// /// /// DECLARE @complex COMPLEX = '1+5i' /// SELECT @complex.Divide('-5+1i').ToString() /// -- Returns -1i /// /// [SqlMethod(OnNullCall = false, IsDeterministic = true)] public ComplexNumber Divide(ComplexNumber num) { ComplexNumber result = new ComplexNumber(); result.SetPhasor(Magnitude / num.Magnitude, Angle - num.Angle); return result; } /// /// Sets the real and imaginary components of the complex number. /// /// The real component. /// The imaginary component. /// /// Usage example: /// /// /// DECLARE @tbl TABLE(Num COMPLEX) /// INSERT INTO @tbl VALUES('1+5i') /// UPDATE @tbl SET Num.SetRect(-5, 1) /// SELECT Num.ToString() FROM @tbl /// -- Returns -5+1i /// /// [SqlMethod(IsMutator = true, OnNullCall = false)] public void SetRect(double real, double imaginary) { m_real = real; m_imaginary = imaginary; } /// /// Sets the magnitude and angle of the vector described by a complex number. /// /// The magnitude. /// The angle. /// /// Usage example: /// /// /// DECLARE @tbl TABLE(Num COMPLEX) /// INSERT INTO @tbl VALUES('1+5i') /// UPDATE @tbl SET Num.SetPhasor(-5, 1) /// SELECT Num.ToString() FROM @tbl /// -- Returns -2.70151-4.20735i /// /// [SqlMethod(IsMutator = true, OnNullCall = false)] public void SetPhasor(double magnitude, double angle) { m_real = magnitude * Math.Cos(angle); m_imaginary = magnitude * Math.Sin(angle); } /// /// Returns the string representation of a complex number. /// /// The string representation of the complex number. public override string ToString() { StringBuilder image = new StringBuilder(); double real = Math.Round(m_real, 5); double imaginary = Math.Round(m_imaginary, 5); if (real == 0.0D && imaginary == 0.0D) return "0"; if (real != 0.0D) image.Append(real); if (imaginary != 0.0D) { image.Append(imaginary > 0.0D ? "+" : "-"); image.Append(Math.Abs(imaginary)); image.Append('i'); } return image.ToString(); } #endregion #region [ Static ] // Static Properties /// /// Gets a value that represents a null complex number. /// public static ComplexNumber Null { get { ComplexNumber num = new ComplexNumber(); num.m_isNull = true; return num; } } // Static Methods /// /// Converts the string representation of a complex number to a complex number. /// /// The string representation of a complex number. /// The complex number. [SqlMethod(OnNullCall = false)] public static ComplexNumber Parse(SqlString str) { // Regex pattern to match a single number const string NumberPattern = @"(?[+-]?(?:[ij][0-9eE\.]+|[0-9eE\.]+[ij]?))"; // Regex pattern to match the operator const string OperatorPattern = @"(?[+-])"; // Regex pattern to match the whole complex number // const string Pattern = @"^" + // Start of string @"\s*" + // Whitespace NumberPattern + // Number @"\s*" + // Whitespace "(?:" + // Start of optional of noncapturing group OperatorPattern + // Operator @"\s*" + // Whitespace NumberPattern + // Number @"\s*" + // Whitespace ")?" + // End of optional noncapturing group "$"; // End of string Match match; CaptureCollection numberCaptures; CaptureCollection operatorCaptures; double op = 1.0; double real = 0.0D; double imaginary = 0.0D; // Parses the string as a double by first removing the i or j Func parse = s => double.Parse(Regex.Replace(s, "[ij]", "")); Func isImaginary = s => Regex.IsMatch(s, "[ij]"); ComplexNumber num; if (str.IsNull) return Null; match = Regex.Match(str.Value, Pattern); // String format is invalid if regex does not match if (!match.Success) throw new FormatException("Input string was not in a correct format."); // Get the captures for the Number and Operator groups numberCaptures = match.Groups["Number"].Captures; operatorCaptures = match.Groups["Operator"].Captures; // If the string defines two numbers, ensure that exactly one of them is imaginary if (numberCaptures.Count == 2 && !(isImaginary(numberCaptures[0].Value) ^ isImaginary(numberCaptures[1].Value))) throw new FormatException("Input string was not in a correct format."); // Parse the first capture group to the // appropriate part of the complex number if (isImaginary(numberCaptures[0].Value)) imaginary = parse(numberCaptures[0].Value); else real = parse(numberCaptures[0].Value); if (numberCaptures.Count == 2) { // Determine if the sign needs to be // inverted based on the operator if (operatorCaptures[0].Value == "-") op = -1.0; // Parse the second capture group to the // appropriate part of the complex number if (isImaginary(numberCaptures[1].Value)) imaginary = op * parse(numberCaptures[1].Value); else real = op * parse(numberCaptures[1].Value); } // Return the complex number num = new ComplexNumber(); num.m_real = real; num.m_imaginary = imaginary; return num; } #endregion } }