using System; using System.Collections.Generic; using System.Globalization; using System.Runtime.Serialization; using System.Linq; using Complex = System.Numerics.Complex; using System.Text; using IStation.Numerics.LinearAlgebra; using IStation.Numerics.LinearAlgebra.Double; using IStation.Numerics.LinearRegression; using IStation.Numerics.LinearAlgebra.Factorization; #if !NETSTANDARD1_3 using System.Runtime; #endif namespace IStation.Numerics { /// /// A single-variable polynomial with real-valued coefficients and non-negative exponents. /// [Serializable] [DataContract(Namespace = "urn:IStation/Numerics")] public class Polynomial : IFormattable, IEquatable #if !NETSTANDARD1_3 , ICloneable #endif { /// /// The coefficients of the polynomial in a /// [DataMember(Order = 1)] public double[] Coefficients { get; private set; } /// /// Only needed for the ToString method /// [DataMember(Order = 2)] public string VariableName = "x"; /// /// Degree of the polynomial, i.e. the largest monomial exponent. For example, the degree of y=x^2+x^5 is 5, for y=3 it is 0. /// The null-polynomial returns degree -1 because the correct degree, negative infinity, cannot be represented by integers. /// public int Degree => EvaluateDegree(Coefficients); /// /// Create a zero-polynomial with a coefficient array of the given length. /// An array of length N can support polynomials of a degree of at most N-1. /// /// Length of the coefficient array public Polynomial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException(nameof(n), "n must be non-negative"); } Coefficients = new double[n]; } /// /// Create a zero-polynomial /// public Polynomial() { #if NET40 Coefficients = new double[0]; #else Coefficients = Array.Empty(); #endif } /// /// Create a constant polynomial. /// Example: 3.0 -> "p : x -> 3.0" /// /// The coefficient of the "x^0" monomial. public Polynomial(double coefficient) { if (coefficient == 0.0) { #if NET40 Coefficients = new double[0]; #else Coefficients = Array.Empty(); #endif } else { Coefficients = new[] { coefficient }; } } /// /// Create a polynomial with the provided coefficients (in ascending order, where the index matches the exponent). /// Example: {5, 0, 2} -> "p : x -> 5 + 0 x^1 + 2 x^2". /// /// Polynomial coefficients as array public Polynomial(params double[] coefficients) { Coefficients = coefficients; } /// /// Create a polynomial with the provided coefficients (in ascending order, where the index matches the exponent). /// Example: {5, 0, 2} -> "p : x -> 5 + 0 x^1 + 2 x^2". /// /// Polynomial coefficients as enumerable public Polynomial(IEnumerable coefficients) : this(coefficients.ToArray()) { } public static Polynomial Zero => new Polynomial(); /// /// Least-Squares fitting the points (x,y) to a k-order polynomial y : x -> p0 + p1*x + p2*x^2 + ... + pk*x^k /// public static Polynomial Fit(double[] x, double[] y, int order, DirectRegressionMethod method = DirectRegressionMethod.QR) { var coefficients = Numerics.Fit.Polynomial(x, y, order, method); return new Polynomial(coefficients); } static int EvaluateDegree(double[] coefficients) { for (int i = coefficients.Length - 1; i >= 0; i--) { if (coefficients[i] != 0.0) { return i; } } return -1; } #region Evaluation /// /// Evaluate a polynomial at point x. /// Coefficients are ordered ascending by power with power k at index k. /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. /// /// The location where to evaluate the polynomial at. /// The coefficients of the polynomial, coefficient for power k at index k. /// /// is a null reference. /// public static double Evaluate(double z, params double[] coefficients) { // 2020-10-07 jbialogrodzki #730 Since this is public API we should probably // handle null arguments? It doesn't seem to have been done consistently in this class though. if (coefficients == null) { throw new ArgumentNullException(nameof(coefficients)); } // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // Without this check, we attempted to peek coefficients at negative indices! int n = coefficients.Length; if (n == 0) { return 0; } double sum = coefficients[n - 1]; for (int i = n - 2; i >= 0; --i) { sum *= z; sum += coefficients[i]; } return sum; } /// /// Evaluate a polynomial at point x. /// Coefficients are ordered ascending by power with power k at index k. /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. /// /// The location where to evaluate the polynomial at. /// The coefficients of the polynomial, coefficient for power k at index k. /// /// is a null reference. /// public static Complex Evaluate(Complex z, params double[] coefficients) { // 2020-10-07 jbialogrodzki #730 Since this is a public API we should probably // handle null arguments? It doesn't seem to have been done consistently in this class though. if (coefficients == null) { throw new ArgumentNullException(nameof(coefficients)); } // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // Without this check, we attempted to peek coefficients at negative indices! int n = coefficients.Length; if (n == 0) { return 0; } Complex sum = coefficients[n - 1]; for (int i = n - 2; i >= 0; --i) { sum *= z; sum += coefficients[i]; } return sum; } /// /// Evaluate a polynomial at point x. /// Coefficients are ordered ascending by power with power k at index k. /// Example: coefficients [3,-1,2] represent y=2x^2-x+3. /// /// The location where to evaluate the polynomial at. /// The coefficients of the polynomial, coefficient for power k at index k. /// /// is a null reference. /// public static Complex Evaluate(Complex z, params Complex[] coefficients) { // 2020-10-07 jbialogrodzki #730 Since this is a public API we should probably // handle null arguments? It doesn't seem to have been done consistently in this class though. if (coefficients == null) { throw new ArgumentNullException(nameof(coefficients)); } // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // Without this check, we attempted to peek coefficients at negative indices! int n = coefficients.Length; if (n == 0) { return 0; } Complex sum = coefficients[n - 1]; for (int i = n - 2; i >= 0; --i) { sum *= z; sum += coefficients[i]; } return sum; } /// /// Evaluate a polynomial at point x. /// /// The location where to evaluate the polynomial at. public double Evaluate(double z) { return Evaluate(z, Coefficients); } /// /// Evaluate a polynomial at point x. /// /// The location where to evaluate the polynomial at. public Complex Evaluate(Complex z) { return Evaluate(z, Coefficients); } /// /// Evaluate a polynomial at points z. /// /// The locations where to evaluate the polynomial at. public IEnumerable Evaluate(IEnumerable z) { return z.Select(Evaluate); } /// /// Evaluate a polynomial at points z. /// /// The locations where to evaluate the polynomial at. public IEnumerable Evaluate(IEnumerable z) { return z.Select(Evaluate); } #endregion #region Calculus public Polynomial Differentiate() { int n = Degree; if (n < 0) { return this; } if (n == 0) { // Zero return Zero; } var c = new double[n]; for (int i = 0; i < c.Length; i++) { c[i] = Coefficients[i + 1] * (i + 1); } return new Polynomial(c); } public Polynomial Integrate() { int n = Degree; if (n < 0) { return this; } var c = new double[n + 2]; for (int i = 1; i < c.Length; i++) { c[i] = Coefficients[i - 1] / i; } return new Polynomial(c); } #endregion #region Linear Algebra /// /// Calculates the complex roots of the Polynomial by eigenvalue decomposition /// /// a vector of complex numbers with the roots public Complex[] Roots() { switch (Degree) { case -1: // Zero-polynomial case 0: // Non-zero constant: y = a0 return new Complex[0]; case 1: // Linear: y = a0 + a1*x return new[] { new Complex(-Coefficients[0] / Coefficients[1], 0) }; } DenseMatrix A = EigenvalueMatrix(); Evd eigen = A.Evd(Symmetricity.Asymmetric); return eigen.EigenValues.AsArray(); } /// /// Get the eigenvalue matrix A of this polynomial such that eig(A) = roots of this polynomial. /// /// Eigenvalue matrix A /// This matrix is similar to the companion matrix of this polynomial, in such a way, that it's transpose is the columnflip of the companion matrix public DenseMatrix EigenvalueMatrix() { int n = Degree; if (n < 2) { return null; } // Negate, and normalize (scale such that the polynomial becomes monic) double aN = Coefficients[n]; double[] p = new double[n]; for (int i = n - 1; i >= 0; i--) { p[i] = -Coefficients[i] / aN; } DenseMatrix A0 = DenseMatrix.CreateDiagonal(n - 1, n - 1, 1.0); DenseMatrix A = new DenseMatrix(n); A.SetSubMatrix(1, 0, A0); A.SetRow(0, p.Reverse().ToArray()); return A; } #endregion #region Arithmetic Operations /// /// Addition of two Polynomials (point-wise). /// /// Left Polynomial /// Right Polynomial /// Resulting Polynomial public static Polynomial Add(Polynomial a, Polynomial b) { var ac = a.Coefficients; var bc = b.Coefficients; var degree = Math.Max(a.Degree, b.Degree); var result = new double[degree + 1]; var commonLength = Math.Min(Math.Min(ac.Length, bc.Length), result.Length); for (int i = 0; i < commonLength; i++) { result[i] = ac[i] + bc[i]; } int acLength = Math.Min(ac.Length, result.Length); for (int i = commonLength; i < acLength; i++) { // no need to add since only one of both applies result[i] = ac[i]; } int bcLength = Math.Min(bc.Length, result.Length); for (int i = commonLength; i < bcLength; i++) { // no need to add since only one of both applies result[i] = bc[i]; } return new Polynomial(result); } /// /// Addition of a polynomial and a scalar. /// public static Polynomial Add(Polynomial a, double b) { var ac = a.Coefficients; var degree = Math.Max(a.Degree, 0); var result = new double[degree + 1]; var commonLength = Math.Min(ac.Length, result.Length); for (int i = 0; i < commonLength; i++) { result[i] = ac[i]; } result[0] += b; return new Polynomial(result); } /// /// Subtraction of two Polynomials (point-wise). /// /// Left Polynomial /// Right Polynomial /// Resulting Polynomial public static Polynomial Subtract(Polynomial a, Polynomial b) { var ac = a.Coefficients; var bc = b.Coefficients; var degree = Math.Max(a.Degree, b.Degree); var result = new double[degree + 1]; var commonLength = Math.Min(Math.Min(ac.Length, bc.Length), result.Length); for (int i = 0; i < commonLength; i++) { result[i] = ac[i] - bc[i]; } int acLength = Math.Min(ac.Length, result.Length); for (int i = commonLength; i < acLength; i++) { // no need to add since only one of both applies result[i] = ac[i]; } int bcLength = Math.Min(bc.Length, result.Length); for (int i = commonLength; i < bcLength; i++) { // no need to add since only one of both applies result[i] = -bc[i]; } return new Polynomial(result); } /// /// Addition of a scalar from a polynomial. /// public static Polynomial Subtract(Polynomial a, double b) { return Add(a, -b); } /// /// Addition of a polynomial from a scalar. /// public static Polynomial Subtract(double b, Polynomial a) { var ac = a.Coefficients; var degree = Math.Max(a.Degree, 0); var result = new double[degree + 1]; var commonLength = Math.Min(ac.Length, result.Length); for (int i = 0; i < commonLength; i++) { result[i] = -ac[i]; } result[0] += b; return new Polynomial(result); } /// /// Negation of a polynomial. /// public static Polynomial Negate(Polynomial a) { var ac = a.Coefficients; var degree = a.Degree; var result = new double[degree + 1]; for (int i = 0; i < result.Length; i++) { result[i] = -ac[i]; } return new Polynomial(result); } /// /// Multiplies a polynomial by a polynomial (convolution) /// /// Left polynomial /// Right polynomial /// Resulting Polynomial /// /// or is a null reference. /// public static Polynomial Multiply(Polynomial a, Polynomial b) { // 2020-10-07 jbialogrodzki #730 Since this is a public API we should probably // handle null arguments? It doesn't seem to have been done consistently in this class though. if (a == null) { throw new ArgumentNullException(nameof(a)); } if (b == null) { throw new ArgumentNullException(nameof(b)); } var ad = a.Degree; var bd = b.Degree; // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // Without this check, we attempted to create arrays of negative lengths! if (ad < 0 || bd < 0) { return Polynomial.Zero; } double[] ac = a.Coefficients; double[] bc = b.Coefficients; var degree = ad + bd; double[] result = new double[degree + 1]; for (int i = 0; i <= ad; i++) { for (int j = 0; j <= bd; j++) { result[i + j] += ac[i] * bc[j]; } } return new Polynomial(result); } /// /// Scales a polynomial by a scalar /// /// Polynomial /// Scalar value /// Resulting Polynomial public static Polynomial Multiply(Polynomial a, double k) { var ac = a.Coefficients; var result = new double[a.Degree + 1]; for (int i = 0; i < result.Length; i++) { result[i] = ac[i] * k; } return new Polynomial(result); } /// /// Scales a polynomial by division by a scalar /// /// Polynomial /// Scalar value /// Resulting Polynomial public static Polynomial Divide(Polynomial a, double k) { var ac = a.Coefficients; var result = new double[a.Degree + 1]; for (int i = 0; i < result.Length; i++) { result[i] = ac[i] / k; } return new Polynomial(result); } /// /// Euclidean long division of two polynomials, returning the quotient q and remainder r of the two polynomials a and b such that a = q*b + r /// /// Left polynomial /// Right polynomial /// A tuple holding quotient in first and remainder in second public static Tuple DivideRemainder(Polynomial a, Polynomial b) { var bDegree = b.Degree; if (bDegree < 0) { throw new DivideByZeroException("b polynomial ends with zero"); } var aDegree = a.Degree; if (aDegree < 0) { // zero divided by non-zero is zero without remainder return Tuple.Create(a, a); } if (bDegree == 0) { // division by scalar return Tuple.Create(Divide(a, b.Coefficients[0]), Zero); } if (aDegree < bDegree) { // denominator degree higher than nominator degree // quotient always be 0 and return c1 as remainder return Tuple.Create(Zero, a); } var c1 = a.Coefficients.ToArray(); var c2 = b.Coefficients.ToArray(); var scl = c2[bDegree]; var c22 = new double[bDegree]; for (int ii = 0; ii < c22.Length; ii++) { c22[ii] = c2[ii] / scl; } int i = aDegree - bDegree; int j = aDegree; while (i >= 0) { var v = c1[j]; for (int k = i; k < j; k++) { c1[k] -= c22[k - i] * v; } i--; j--; } var j1 = j + 1; var l1 = aDegree - j; var quo = new double[l1]; for (int k = 0; k < l1; k++) { quo[k] = c1[k + j1] / scl; } var rem = new double[j1]; for (int k = 0; k < j1; k++) { rem[k] = c1[k]; } return Tuple.Create(new Polynomial(quo), new Polynomial(rem)); } #endregion #region Arithmetic Pointwise Operations /// /// Point-wise division of two Polynomials /// /// Left Polynomial /// Right Polynomial /// Resulting Polynomial public static Polynomial PointwiseDivide(Polynomial a, Polynomial b) { var ac = a.Coefficients; var bc = b.Coefficients; var degree = a.Degree; var result = new double[degree + 1]; var commonLength = Math.Min(Math.Min(ac.Length, bc.Length), result.Length); for (int i = 0; i < commonLength; i++) { result[i] = ac[i] / bc[i]; } for (int i = commonLength; i < result.Length; i++) { result[i] = ac[i] / 0.0; } return new Polynomial(result); } /// /// Point-wise multiplication of two Polynomials /// /// Left Polynomial /// Right Polynomial /// Resulting Polynomial public static Polynomial PointwiseMultiply(Polynomial a, Polynomial b) { var ac = a.Coefficients; var bc = b.Coefficients; var degree = Math.Min(a.Degree, b.Degree); var result = new double[degree + 1]; for (int i = 0; i < result.Length; i++) { result[i] = ac[i] * bc[i]; } return new Polynomial(result); } #endregion #region Arithmetic Instance Methods (forwarders) /// /// Division of two polynomials returning the quotient-with-remainder of the two polynomials given /// /// Right polynomial /// A tuple holding quotient in first and remainder in second public Tuple DivideRemainder(Polynomial b) { return DivideRemainder(this, b); } #endregion #region Arithmetic Operator Overloads (forwarders) /// /// Addition of two Polynomials (piecewise) /// /// Left polynomial /// Right polynomial /// Resulting Polynomial public static Polynomial operator +(Polynomial a, Polynomial b) { return Add(a, b); } /// /// adds a scalar to a polynomial. /// /// Polynomial /// Scalar value /// Resulting Polynomial public static Polynomial operator +(Polynomial a, double k) { return Add(a, k); } /// /// adds a scalar to a polynomial. /// /// Scalar value /// Polynomial /// Resulting Polynomial public static Polynomial operator +(double k, Polynomial a) { return Add(a, k); } /// /// Subtraction of two polynomial. /// /// Left polynomial /// Right polynomial /// Resulting Polynomial public static Polynomial operator -(Polynomial a, Polynomial b) { return Subtract(a, b); } /// /// Subtracts a scalar from a polynomial. /// /// Polynomial /// Scalar value /// Resulting Polynomial public static Polynomial operator -(Polynomial a, double k) { return Subtract(a, k); } /// /// Subtracts a polynomial from a scalar. /// /// Scalar value /// Polynomial /// Resulting Polynomial public static Polynomial operator -(double k, Polynomial a) { return Subtract(k, a); } /// /// Negates a polynomial. /// /// Polynomial /// Resulting Polynomial public static Polynomial operator -(Polynomial a) { return Negate(a); } /// /// Multiplies a polynomial by a polynomial (convolution). /// /// Left polynomial /// Right polynomial /// resulting Polynomial public static Polynomial operator *(Polynomial a, Polynomial b) { return Multiply(a, b); } /// /// Multiplies a polynomial by a scalar. /// /// Polynomial /// Scalar value /// Resulting Polynomial public static Polynomial operator *(Polynomial a, double k) { return Multiply(a, k); } /// /// Multiplies a polynomial by a scalar. /// /// Scalar value /// Polynomial /// Resulting Polynomial public static Polynomial operator *(double k, Polynomial a) { return Multiply(a, k); } /// /// Divides a polynomial by scalar value. /// /// Polynomial /// Scalar value /// Resulting Polynomial public static Polynomial operator /(Polynomial a, double k) { return Divide(a, k); } #endregion #region ToString /// /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". /// public override string ToString() { return ToString("G", CultureInfo.CurrentCulture); } /// /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". /// public string ToStringDescending() { return ToStringDescending("G", CultureInfo.CurrentCulture); } /// /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". /// public string ToString(string format) { return ToString(format, CultureInfo.CurrentCulture); } /// /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". /// public string ToStringDescending(string format) { return ToStringDescending(format, CultureInfo.CurrentCulture); } /// /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". /// public string ToString(IFormatProvider formatProvider) { return ToString("G", formatProvider); } /// /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". /// public string ToStringDescending(IFormatProvider formatProvider) { return ToStringDescending("G", formatProvider); } /// /// Format the polynomial in ascending order, e.g. "4.3 + 2.0x^2 - x^3". /// public string ToString(string format, IFormatProvider formatProvider) { if (Degree < 0) { return "0"; } var sb = new StringBuilder(); bool first = true; for (int i = 0; i < Coefficients.Length; i++) { double c = Coefficients[i]; if (c == 0.0) { continue; } if (first) { sb.Append(c.ToString(format, formatProvider)); if (i > 0) { sb.Append(VariableName); } if (i > 1) { sb.Append("^"); sb.Append(i); } first = false; } else { if (c < 0.0) { sb.Append(" - "); sb.Append((-c).ToString(format, formatProvider)); } else { sb.Append(" + "); sb.Append(c.ToString(format, formatProvider)); } if (i > 0) { sb.Append(VariableName); } if (i > 1) { sb.Append("^"); sb.Append(i); } } } return sb.ToString(); } /// /// Format the polynomial in descending order, e.g. "x^3 + 2.0x^2 - 4.3". /// public string ToStringDescending(string format, IFormatProvider formatProvider) { if (Degree < 0) { return "0"; } var sb = new StringBuilder(); bool first = true; for (int i = Coefficients.Length - 1; i >= 0; i--) { double c = Coefficients[i]; if (c == 0.0) { continue; } if (first) { sb.Append(c.ToString(format, formatProvider)); if (i > 0) { sb.Append(VariableName); } if (i > 1) { sb.Append("^"); sb.Append(i); } first = false; } else { if (c < 0.0) { sb.Append(" - "); sb.Append((-c).ToString(format, formatProvider)); } else { sb.Append(" + "); sb.Append(c.ToString(format, formatProvider)); } if (i > 0) { sb.Append(VariableName); } if (i > 1) { sb.Append("^"); sb.Append(i); } } } return sb.ToString(); } #endregion #region Equality public bool Equals(Polynomial other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; int n = Degree; if (n != other.Degree) { return false; } for (var i = 0; i <= n; i++) { if (!Coefficients[i].Equals(other.Coefficients[i])) { return false; } } return true; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != typeof(Polynomial)) return false; return Equals((Polynomial)obj); } public override int GetHashCode() { var hashNum = Math.Min(Degree + 1, 25); int hash = 17; unchecked { for (var i = 0; i < hashNum; i++) { hash = hash * 31 + Coefficients[i].GetHashCode(); } } return hash; } #endregion #region Clone public Polynomial Clone() { int degree = EvaluateDegree(Coefficients); var coefficients = new double[degree + 1]; Array.Copy(Coefficients, coefficients, coefficients.Length); return new Polynomial(coefficients); } #if !NETSTANDARD1_3 /// /// Creates a new object that is a copy of the current instance. /// /// /// A new object that is a copy of this instance. /// object ICloneable.Clone() { return Clone(); } #endif #endregion } }