//******************************************************************************************************
// ComplexNumber.cs - Gbtc
//
// Copyright © 2012, 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://www.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:
// ----------------------------------------------------------------------------------------------------
// 01/25/2008 - J. Ritchie Carroll
// Initial version of source generated.
// 08/3/2009 - Josh L. Patterson
// Updated comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
// 12/18/2012 - J. Ritchie Carroll
// Updated operation such that class will used cached angle and magnitude values
// when these are provided to improve accuracy and operational speed.
// 12/02/2015 - J. Ritchie Carroll
// Added implicit operators to interact with new .NET Complex structure.
//
//******************************************************************************************************
#region [ Contributor License Agreements ]
/**************************************************************************\
Copyright © 2009 - J. Ritchie Carroll
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\**************************************************************************/
#endregion
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using GSF.Units;
#if DNF46
using System.Numerics;
#endif
// ReSharper disable CompareOfFloatsByEqualityOperator
// ReSharper disable PossibleInvalidOperationException
namespace GSF
{
///
/// Represents a complex number.
///
public struct ComplexNumber : IEquatable
{
#region [ Members ]
// Complex number will cache assigned polar components as an optimization and for increased accuracy,
// however, preferential treatment is given to rectangular components when used in mixed mode.
// Fields
private double? m_real; // Real value of complex number
private double? m_imaginary; // Imaginary value of complex number
private double? m_angle; // Angle value of complex number
private double? m_magnitude; // Magnitude value of complex number
#endregion
#region [ Constructors ]
///
/// Creates a from the given rectangular values.
///
/// The real component of the .
/// The imaginary component of the .
public ComplexNumber(double real, double imaginary)
: this()
{
m_real = real;
m_imaginary = imaginary;
}
///
/// Creates a from the given polar values.
///
/// The component, in radians, of the .
/// The magnitude (or absolute value) component of the .
public ComplexNumber(Angle angle, double magnitude)
: this()
{
m_angle = angle;
m_magnitude = magnitude;
CalculateRectangularFromPolar();
}
///
/// Creates a from the given .
///
/// to be copied.
public ComplexNumber(ComplexNumber z)
: this()
{
// Make sure state of source complex number is replicated exactly
m_real = z.m_real;
m_imaginary = z.m_imaginary;
m_angle = z.m_angle;
m_magnitude = z.m_magnitude;
}
#endregion
#region [ Properties ]
///
/// Gets or sets the real component of this .
///
public double Real
{
get
{
return m_real ?? double.NaN;
}
set
{
m_real = value;
// If we are updating a rectangular component, clear polar components so they will be recalculated
m_angle = null;
m_magnitude = null;
}
}
///
/// Gets or sets the imaginary component of this .
///
public double Imaginary
{
get
{
return m_imaginary ?? double.NaN;
}
set
{
m_imaginary = value;
// If we are updating a rectangular component, clear polar components so they will be recalculated
m_angle = null;
m_magnitude = null;
}
}
///
/// Gets or sets the magnitude (a.k.a. the modulus or absolute value) of this .
///
public double Magnitude
{
get
{
// Return any assigned magnitude value
if (m_magnitude.HasValue)
return m_magnitude.Value;
if (!AllAssigned)
return double.NaN;
// If complex number is internally represented in rectangular coordinates and no magnitude has been assigned,
// return a calculated magnitude
double real = m_real.Value;
double imaginary = m_imaginary.Value;
return Math.Sqrt(real * real + imaginary * imaginary);
}
set
{
// Cache assigned magnitude value
m_magnitude = value;
if (!m_real.HasValue || !m_imaginary.HasValue)
{
// Rectangular coordinates have yet to be fully assigned, if all composite polar values have been received,
// we can calculate real and imaginary values
CalculateRectangularFromPolar();
}
else
{
// Rectangular values have already been assigned, user is simply requesting to change complex number
// by updating its absolute value so we calculate a new complex number based on the updated polar
// coordinates and then update the real and imaginary components
ComplexNumber updatedValue = new ComplexNumber(Angle, value);
m_real = updatedValue.m_real;
m_imaginary = updatedValue.m_imaginary;
}
}
}
///
/// Gets or sets the (a.k.a. the argument) in radians of this .
///
public Angle Angle
{
get
{
// Return any assigned angle value
if (m_angle.HasValue)
return m_angle.Value;
if (!AllAssigned)
return double.NaN;
// If complex number is internally represented in rectangular coordinates and no angle has been assigned,
// return a calculated angle
return Math.Atan2(m_imaginary.Value, m_real.Value);
}
set
{
// Cache assigned angle value
m_angle = value;
if (!m_real.HasValue || !m_imaginary.HasValue)
{
// Rectangular coordinates have yet to be fully assigned, if all composite polar values have been received,
// we can calculate real and imaginary values
CalculateRectangularFromPolar();
}
else
{
// Rectangular values have already been assigned, user is simply requesting to change complex number
// by updating its angle value so we calculate a new complex number based on the updated polar
// coordinates and then update the real and imaginary components
ComplexNumber updatedValue = new ComplexNumber(value, Magnitude);
m_real = updatedValue.m_real;
m_imaginary = updatedValue.m_imaginary;
}
}
}
///
/// Gets the complex conjugate of this .
///
public ComplexNumber Conjugate => new ComplexNumber(Real, -Imaginary);
///
/// Gets a boolean value indicating if each composite value of the (i.e., real and imaginary) has been assigned a value.
///
/// True, if all composite values have been assigned a value; otherwise, false.
public bool AllAssigned => m_real.HasValue && m_imaginary.HasValue;
///
/// Gets a boolean value indicating if each composite value of the (i.e., real and imaginary) has not been assigned a value.
///
/// True, if none of the composite values have been assigned a value; otherwise, false.
public bool NoneAssigned => !m_real.HasValue && !m_imaginary.HasValue;
#endregion
#region [ Methods ]
///
/// Returns a value indicating whether this instance is equal to a specified object.
///
/// An object to compare, or null.
///
/// True if is an instance of ComplexNumber and equals the value of this instance;
/// otherwise, False.
///
public override bool Equals(object obj)
{
if (obj is ComplexNumber)
return Equals((ComplexNumber)obj);
return false;
}
///
/// Returns a value indicating whether this instance is equal to a specified ComplexNumber value.
///
/// A to compare to this instance.
///
/// True if has the same value as this instance; otherwise, False.
///
public bool Equals(ComplexNumber obj) => this == obj;
///
/// Returns the hash code for this instance.
///
///
/// A 32-bit signed integer hash code.
///
public override int GetHashCode() => Real.GetHashCode() ^ Imaginary.GetHashCode();
///
/// Converts the numeric value of this instance to its equivalent string representation.
///
///
/// The string representation of the value of this instance.
///
public override string ToString()
{
StringBuilder image = new StringBuilder();
image.Append(Real);
if (Imaginary != 0.0D)
{
image.Append(Imaginary > 0.0D ? " + " : " - ");
image.Append(Math.Abs(Imaginary));
image.Append('i');
}
return image.ToString();
}
// Calculate real and imaginary components from angle and magnitude
private void CalculateRectangularFromPolar()
{
if (m_angle.HasValue && m_magnitude.HasValue)
{
// All values assigned, calculate a new rectangular based complex number from its polar composite values
double angle = m_angle.Value;
double magnitude = m_magnitude.Value;
m_real = magnitude * Math.Cos(angle);
m_imaginary = magnitude * Math.Sin(angle);
}
}
#endregion
#region [ Operators ]
///
/// Implicitly converts a to a .
///
/// Operand.
/// ComplexNumber representing the result of the operation.
public static implicit operator ComplexNumber(double value) => new ComplexNumber(value, 0.0D);
#if DNF46
///
/// Implicitly converts a to a .NET value.
///
/// Operand.
/// Complex representing the result of the operation.
public static implicit operator Complex(ComplexNumber value) => new Complex(value.Real, value.Imaginary);
///
/// Implicitly converts a .NET value to a .
///
/// Operand.
/// Complex representing the result of the operation.
public static implicit operator ComplexNumber(Complex value) => new ComplexNumber(value.Real, value.Imaginary);
#endif
///
/// Compares the two values for equality.
///
/// Left hand operand.
/// Right hand operand.
/// Boolean representing the result of the addition operation.
public static bool operator ==(ComplexNumber value1, ComplexNumber value2) => value1.Real == value2.Real && value1.Imaginary == value2.Imaginary;
///
/// Compares the two values for inequality.
///
/// Left hand operand.
/// Right hand operand.
/// Boolean representing the result of the inequality operation.
public static bool operator !=(ComplexNumber value1, ComplexNumber value2) => !(value1 == value2);
///
/// Returns the negated value.
///
/// Left hand operand.
/// ComplexNumber representing the result of the unary negation operation.
public static ComplexNumber operator -(ComplexNumber z) => new ComplexNumber(-z.Real, -z.Imaginary);
///
/// Returns computed sum of values.
///
/// Left hand operand.
/// Right hand operand.
/// ComplexNumber representing the result of the addition operation.
public static ComplexNumber operator +(ComplexNumber value1, ComplexNumber value2) => new ComplexNumber(value1.Real + value2.Real, value1.Imaginary + value2.Imaginary);
///
/// Returns computed difference of values.
///
/// Left hand operand.
/// Right hand operand.
/// ComplexNumber representing the result of the subtraction operation.
public static ComplexNumber operator -(ComplexNumber value1, ComplexNumber value2) => new ComplexNumber(value1.Real - value2.Real, value1.Imaginary - value2.Imaginary);
///
/// Returns computed product of values.
///
/// Left hand operand.
/// Right hand operand.
/// ComplexNumber representing the result of the multiplication operation.
public static ComplexNumber operator *(ComplexNumber value1, ComplexNumber value2)
{
double real = value1.Real * value2.Real - value1.Imaginary * value2.Imaginary;
double imaginary = value1.Imaginary * value2.Real + value1.Real * value2.Imaginary;
return new ComplexNumber(real, imaginary);
}
///
/// Returns computed division of values.
///
/// Left hand operand.
/// Right hand operand.
/// ComplexNumber representing the result of the division operation.
public static ComplexNumber operator /(ComplexNumber value1, ComplexNumber value2)
{
double divisor = Math.Pow(value2.Real, 2) + Math.Pow(value2.Imaginary, 2);
double real = (value1.Real * value2.Real + value1.Imaginary * value2.Imaginary) / divisor;
double imaginary = (value1.Imaginary * value2.Real - value1.Real * value2.Imaginary) / divisor;
return new ComplexNumber(real, imaginary);
}
///
/// Returns specified raised to the specified power.
///
///Complex number to be raised to power .
///Power to raise .
/// ComplexNumber representing the result of the operation.
public static ComplexNumber Pow(ComplexNumber z, double y) => new ComplexNumber(z.Angle * y, Math.Pow(z.Magnitude, y));
// C# doesn't expose an exponent operator but some other .NET languages do,
// so we expose the operator via its native special IL function name
///
/// Returns result of first value raised to power of second value.
///
///Complex number to be raised to power .
///Power to raise .
/// ComplexNumber representing the result of the operation.
[EditorBrowsable(EditorBrowsableState.Advanced), SpecialName]
public static ComplexNumber op_Exponent(ComplexNumber z, double y) => Pow(z, y);
#endregion
#region [ Static ]
// Static Methods
///
/// Converts the string representation of a complex number to its complex number equivalent.
///
/// A string that contains a number to convert.
/// A complex number that is equivalent to the numeric value or symbols specified in .
public static ComplexNumber Parse(string 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]");
match = Regex.Match(str, 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
return new ComplexNumber(real, imaginary);
}
#endregion
}
}