gonum.org/v1/gonum@v0.15.1-0.20240517103525-f853624cb1bb/cmplxs/cmplxs.go (about)

     1  // Copyright ©2013 The Gonum Authors. All rights reserved.
     2  // Use of this code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cmplxs
     6  
     7  import (
     8  	"errors"
     9  	"math"
    10  	"math/cmplx"
    11  	"slices"
    12  
    13  	"gonum.org/v1/gonum/cmplxs/cscalar"
    14  	"gonum.org/v1/gonum/internal/asm/c128"
    15  )
    16  
    17  const (
    18  	zeroLength   = "cmplxs: zero length slice"
    19  	shortSpan    = "cmplxs: slice length less than 2"
    20  	badLength    = "cmplxs: slice lengths do not match"
    21  	badDstLength = "cmplxs: destination slice length does not match input"
    22  )
    23  
    24  // Abs calculates the absolute values of the elements of s, and stores them in dst.
    25  // It panics if the argument lengths do not match.
    26  func Abs(dst []float64, s []complex128) {
    27  	if len(dst) != len(s) {
    28  		panic(badDstLength)
    29  	}
    30  	for i, v := range s {
    31  		dst[i] = cmplx.Abs(v)
    32  	}
    33  }
    34  
    35  // Add adds, element-wise, the elements of s and dst, and stores the result in dst.
    36  // It panics if the argument lengths do not match.
    37  func Add(dst, s []complex128) {
    38  	if len(dst) != len(s) {
    39  		panic(badLength)
    40  	}
    41  	c128.AxpyUnitaryTo(dst, 1, s, dst)
    42  }
    43  
    44  // AddTo adds, element-wise, the elements of s and t and
    45  // stores the result in dst.
    46  // It panics if the argument lengths do not match.
    47  func AddTo(dst, s, t []complex128) []complex128 {
    48  	if len(s) != len(t) {
    49  		panic(badLength)
    50  	}
    51  	if len(dst) != len(s) {
    52  		panic(badDstLength)
    53  	}
    54  	c128.AxpyUnitaryTo(dst, 1, s, t)
    55  	return dst
    56  }
    57  
    58  // AddConst adds the scalar c to all of the values in dst.
    59  func AddConst(c complex128, dst []complex128) {
    60  	c128.AddConst(c, dst)
    61  }
    62  
    63  // AddScaled performs dst = dst + alpha * s.
    64  // It panics if the slice argument lengths do not match.
    65  func AddScaled(dst []complex128, alpha complex128, s []complex128) {
    66  	if len(dst) != len(s) {
    67  		panic(badLength)
    68  	}
    69  	c128.AxpyUnitaryTo(dst, alpha, s, dst)
    70  }
    71  
    72  // AddScaledTo performs dst = y + alpha * s, where alpha is a scalar,
    73  // and dst, y and s are all slices.
    74  // It panics if the slice argument lengths do not match.
    75  //
    76  // At the return of the function, dst[i] = y[i] + alpha * s[i]
    77  func AddScaledTo(dst, y []complex128, alpha complex128, s []complex128) []complex128 {
    78  	if len(s) != len(y) {
    79  		panic(badLength)
    80  	}
    81  	if len(dst) != len(y) {
    82  		panic(badDstLength)
    83  	}
    84  	c128.AxpyUnitaryTo(dst, alpha, s, y)
    85  	return dst
    86  }
    87  
    88  // Count applies the function f to every element of s and returns the number
    89  // of times the function returned true.
    90  func Count(f func(complex128) bool, s []complex128) int {
    91  	var n int
    92  	for _, val := range s {
    93  		if f(val) {
    94  			n++
    95  		}
    96  	}
    97  	return n
    98  }
    99  
   100  // Complex fills each of the elements of dst with the complex number
   101  // constructed from the corresponding elements of real and imag.
   102  // It panics if the argument lengths do not match.
   103  func Complex(dst []complex128, real, imag []float64) []complex128 {
   104  	if len(real) != len(imag) {
   105  		panic(badLength)
   106  	}
   107  	if len(dst) != len(real) {
   108  		panic(badDstLength)
   109  	}
   110  	if len(dst) == 0 {
   111  		return dst
   112  	}
   113  	for i, r := range real {
   114  		dst[i] = complex(r, imag[i])
   115  	}
   116  	return dst
   117  }
   118  
   119  // CumProd finds the cumulative product of elements of s and store it in
   120  // place into dst so that
   121  //
   122  //	dst[i] = s[i] * s[i-1] * s[i-2] * ... * s[0]
   123  //
   124  // It panics if the argument lengths do not match.
   125  func CumProd(dst, s []complex128) []complex128 {
   126  	if len(dst) != len(s) {
   127  		panic(badDstLength)
   128  	}
   129  	if len(dst) == 0 {
   130  		return dst
   131  	}
   132  	return c128.CumProd(dst, s)
   133  }
   134  
   135  // CumSum finds the cumulative sum of elements of s and stores it in place
   136  // into dst so that
   137  //
   138  //	dst[i] = s[i] + s[i-1] + s[i-2] + ... + s[0]
   139  //
   140  // It panics if the argument lengths do not match.
   141  func CumSum(dst, s []complex128) []complex128 {
   142  	if len(dst) != len(s) {
   143  		panic(badDstLength)
   144  	}
   145  	if len(dst) == 0 {
   146  		return dst
   147  	}
   148  	return c128.CumSum(dst, s)
   149  }
   150  
   151  // Distance computes the L-norm of s - t. See Norm for special cases.
   152  // It panics if the slice argument lengths do not match.
   153  func Distance(s, t []complex128, L float64) float64 {
   154  	if len(s) != len(t) {
   155  		panic(badLength)
   156  	}
   157  	if len(s) == 0 {
   158  		return 0
   159  	}
   160  
   161  	var norm float64
   162  	switch {
   163  	case L == 2:
   164  		return c128.L2DistanceUnitary(s, t)
   165  	case L == 1:
   166  		for i, v := range s {
   167  			norm += cmplx.Abs(t[i] - v)
   168  		}
   169  		return norm
   170  	case math.IsInf(L, 1):
   171  		for i, v := range s {
   172  			absDiff := cmplx.Abs(t[i] - v)
   173  			if absDiff > norm {
   174  				norm = absDiff
   175  			}
   176  		}
   177  		return norm
   178  	default:
   179  		for i, v := range s {
   180  			norm += math.Pow(cmplx.Abs(t[i]-v), L)
   181  		}
   182  		return math.Pow(norm, 1/L)
   183  	}
   184  }
   185  
   186  // Div performs element-wise division dst / s
   187  // and stores the result in dst.
   188  // It panics if the argument lengths do not match.
   189  func Div(dst, s []complex128) {
   190  	if len(dst) != len(s) {
   191  		panic(badLength)
   192  	}
   193  	c128.Div(dst, s)
   194  }
   195  
   196  // DivTo performs element-wise division s / t
   197  // and stores the result in dst.
   198  // It panics if the argument lengths do not match.
   199  func DivTo(dst, s, t []complex128) []complex128 {
   200  	if len(s) != len(t) {
   201  		panic(badLength)
   202  	}
   203  	if len(dst) != len(s) {
   204  		panic(badDstLength)
   205  	}
   206  	return c128.DivTo(dst, s, t)
   207  }
   208  
   209  // Dot computes the dot product of s1 and s2, i.e.
   210  // sum_{i = 1}^N conj(s1[i])*s2[i].
   211  // It panics if the argument lengths do not match.
   212  func Dot(s1, s2 []complex128) complex128 {
   213  	if len(s1) != len(s2) {
   214  		panic(badLength)
   215  	}
   216  	return c128.DotUnitary(s1, s2)
   217  }
   218  
   219  // Equal returns true when the slices have equal lengths and
   220  // all elements are numerically identical.
   221  func Equal(s1, s2 []complex128) bool {
   222  	if len(s1) != len(s2) {
   223  		return false
   224  	}
   225  	for i, val := range s1 {
   226  		if s2[i] != val {
   227  			return false
   228  		}
   229  	}
   230  	return true
   231  }
   232  
   233  // EqualApprox returns true when the slices have equal lengths and
   234  // all element pairs have an absolute tolerance less than tol or a
   235  // relative tolerance less than tol.
   236  func EqualApprox(s1, s2 []complex128, tol float64) bool {
   237  	if len(s1) != len(s2) {
   238  		return false
   239  	}
   240  	for i, a := range s1 {
   241  		if !cscalar.EqualWithinAbsOrRel(a, s2[i], tol, tol) {
   242  			return false
   243  		}
   244  	}
   245  	return true
   246  }
   247  
   248  // EqualFunc returns true when the slices have the same lengths
   249  // and the function returns true for all element pairs.
   250  func EqualFunc(s1, s2 []complex128, f func(complex128, complex128) bool) bool {
   251  	if len(s1) != len(s2) {
   252  		return false
   253  	}
   254  	for i, val := range s1 {
   255  		if !f(val, s2[i]) {
   256  			return false
   257  		}
   258  	}
   259  	return true
   260  }
   261  
   262  // EqualLengths returns true when all of the slices have equal length,
   263  // and false otherwise. It also returns true when there are no input slices.
   264  func EqualLengths(slices ...[]complex128) bool {
   265  	// This length check is needed: http://play.golang.org/p/sdty6YiLhM
   266  	if len(slices) == 0 {
   267  		return true
   268  	}
   269  	l := len(slices[0])
   270  	for i := 1; i < len(slices); i++ {
   271  		if len(slices[i]) != l {
   272  			return false
   273  		}
   274  	}
   275  	return true
   276  }
   277  
   278  // Find applies f to every element of s and returns the indices of the first
   279  // k elements for which the f returns true, or all such elements
   280  // if k < 0.
   281  // Find will reslice inds to have 0 length, and will append
   282  // found indices to inds.
   283  // If k > 0 and there are fewer than k elements in s satisfying f,
   284  // all of the found elements will be returned along with an error.
   285  // At the return of the function, the input inds will be in an undetermined state.
   286  func Find(inds []int, f func(complex128) bool, s []complex128, k int) ([]int, error) {
   287  	// inds is also returned to allow for calling with nil.
   288  
   289  	// Reslice inds to have zero length.
   290  	inds = inds[:0]
   291  
   292  	// If zero elements requested, can just return.
   293  	if k == 0 {
   294  		return inds, nil
   295  	}
   296  
   297  	// If k < 0, return all of the found indices.
   298  	if k < 0 {
   299  		for i, val := range s {
   300  			if f(val) {
   301  				inds = append(inds, i)
   302  			}
   303  		}
   304  		return inds, nil
   305  	}
   306  
   307  	// Otherwise, find the first k elements.
   308  	nFound := 0
   309  	for i, val := range s {
   310  		if f(val) {
   311  			inds = append(inds, i)
   312  			nFound++
   313  			if nFound == k {
   314  				return inds, nil
   315  			}
   316  		}
   317  	}
   318  	// Finished iterating over the loop, which means k elements were not found.
   319  	return inds, errors.New("cmplxs: insufficient elements found")
   320  }
   321  
   322  // HasNaN returns true when the slice s has any values that are NaN and false
   323  // otherwise.
   324  func HasNaN(s []complex128) bool {
   325  	for _, v := range s {
   326  		if cmplx.IsNaN(v) {
   327  			return true
   328  		}
   329  	}
   330  	return false
   331  }
   332  
   333  // Imag places the imaginary components of src into dst.
   334  // It panics if the argument lengths do not match.
   335  func Imag(dst []float64, src []complex128) []float64 {
   336  	if len(dst) != len(src) {
   337  		panic(badDstLength)
   338  	}
   339  	if len(dst) == 0 {
   340  		return dst
   341  	}
   342  	for i, z := range src {
   343  		dst[i] = imag(z)
   344  	}
   345  	return dst
   346  }
   347  
   348  // LogSpan returns a set of n equally spaced points in log space between,
   349  // l and u where N is equal to len(dst). The first element of the
   350  // resulting dst will be l and the final element of dst will be u.
   351  // Panics if len(dst) < 2
   352  // Note that this call will return NaNs if either l or u are negative, and
   353  // will return all zeros if l or u is zero.
   354  // Also returns the mutated slice dst, so that it can be used in range, like:
   355  //
   356  //	for i, x := range LogSpan(dst, l, u) { ... }
   357  func LogSpan(dst []complex128, l, u complex128) []complex128 {
   358  	Span(dst, cmplx.Log(l), cmplx.Log(u))
   359  	for i := range dst {
   360  		dst[i] = cmplx.Exp(dst[i])
   361  	}
   362  	return dst
   363  }
   364  
   365  // MaxAbs returns the maximum absolute value in the input slice.
   366  // It panics if s is zero length.
   367  func MaxAbs(s []complex128) complex128 {
   368  	return s[MaxAbsIdx(s)]
   369  }
   370  
   371  // MaxAbsIdx returns the index of the maximum absolute value in the input slice.
   372  // If several entries have the maximum absolute value, the first such index is
   373  // returned.
   374  // It panics if s is zero length.
   375  func MaxAbsIdx(s []complex128) int {
   376  	if len(s) == 0 {
   377  		panic(zeroLength)
   378  	}
   379  	max := math.NaN()
   380  	var ind int
   381  	for i, v := range s {
   382  		if cmplx.IsNaN(v) {
   383  			continue
   384  		}
   385  		if a := cmplx.Abs(v); a > max || math.IsNaN(max) {
   386  			max = a
   387  			ind = i
   388  		}
   389  	}
   390  	return ind
   391  }
   392  
   393  // MinAbs returns the minimum absolute value in the input slice.
   394  // It panics if s is zero length.
   395  func MinAbs(s []complex128) complex128 {
   396  	return s[MinAbsIdx(s)]
   397  }
   398  
   399  // MinAbsIdx returns the index of the minimum absolute value in the input slice. If several
   400  // entries have the minimum absolute value, the first such index is returned.
   401  // It panics if s is zero length.
   402  func MinAbsIdx(s []complex128) int {
   403  	if len(s) == 0 {
   404  		panic(zeroLength)
   405  	}
   406  	min := math.NaN()
   407  	var ind int
   408  	for i, v := range s {
   409  		if cmplx.IsNaN(v) {
   410  			continue
   411  		}
   412  		if a := cmplx.Abs(v); a < min || math.IsNaN(min) {
   413  			min = a
   414  			ind = i
   415  		}
   416  	}
   417  	return ind
   418  }
   419  
   420  // Mul performs element-wise multiplication between dst
   421  // and s and stores the result in dst.
   422  // It panics if the argument lengths do not match.
   423  func Mul(dst, s []complex128) {
   424  	if len(dst) != len(s) {
   425  		panic(badLength)
   426  	}
   427  	for i, val := range s {
   428  		dst[i] *= val
   429  	}
   430  }
   431  
   432  // MulConj performs element-wise multiplication between dst
   433  // and the conjugate of s and stores the result in dst.
   434  // It panics if the argument lengths do not match.
   435  func MulConj(dst, s []complex128) {
   436  	if len(dst) != len(s) {
   437  		panic(badLength)
   438  	}
   439  	for i, val := range s {
   440  		dst[i] *= cmplx.Conj(val)
   441  	}
   442  }
   443  
   444  // MulConjTo performs element-wise multiplication between s
   445  // and the conjugate of t and stores the result in dst.
   446  // It panics if the argument lengths do not match.
   447  func MulConjTo(dst, s, t []complex128) []complex128 {
   448  	if len(s) != len(t) {
   449  		panic(badLength)
   450  	}
   451  	if len(dst) != len(s) {
   452  		panic(badDstLength)
   453  	}
   454  	for i, val := range t {
   455  		dst[i] = cmplx.Conj(val) * s[i]
   456  	}
   457  	return dst
   458  }
   459  
   460  // MulTo performs element-wise multiplication between s
   461  // and t and stores the result in dst.
   462  // It panics if the argument lengths do not match.
   463  func MulTo(dst, s, t []complex128) []complex128 {
   464  	if len(s) != len(t) {
   465  		panic(badLength)
   466  	}
   467  	if len(dst) != len(s) {
   468  		panic(badDstLength)
   469  	}
   470  	for i, val := range t {
   471  		dst[i] = val * s[i]
   472  	}
   473  	return dst
   474  }
   475  
   476  // NearestIdx returns the index of the element in s
   477  // whose value is nearest to v. If several such
   478  // elements exist, the lowest index is returned.
   479  // It panics if s is zero length.
   480  func NearestIdx(s []complex128, v complex128) int {
   481  	if len(s) == 0 {
   482  		panic(zeroLength)
   483  	}
   484  	switch {
   485  	case cmplx.IsNaN(v):
   486  		return 0
   487  	case cmplx.IsInf(v):
   488  		return MaxAbsIdx(s)
   489  	}
   490  	var ind int
   491  	dist := math.NaN()
   492  	for i, val := range s {
   493  		newDist := cmplx.Abs(v - val)
   494  		// A NaN distance will not be closer.
   495  		if math.IsNaN(newDist) {
   496  			continue
   497  		}
   498  		if newDist < dist || math.IsNaN(dist) {
   499  			dist = newDist
   500  			ind = i
   501  		}
   502  	}
   503  	return ind
   504  }
   505  
   506  // Norm returns the L-norm of the slice S, defined as
   507  // (sum_{i=1}^N abs(s[i])^L)^{1/L}
   508  // Special cases:
   509  // L = math.Inf(1) gives the maximum absolute value.
   510  // Does not correctly compute the zero norm (use Count).
   511  func Norm(s []complex128, L float64) float64 {
   512  	// Should this complain if L is not positive?
   513  	// Should this be done in log space for better numerical stability?
   514  	//	would be more cost
   515  	//	maybe only if L is high?
   516  	if len(s) == 0 {
   517  		return 0
   518  	}
   519  	var norm float64
   520  	switch {
   521  	case L == 2:
   522  		return c128.L2NormUnitary(s)
   523  	case L == 1:
   524  		for _, v := range s {
   525  			norm += cmplx.Abs(v)
   526  		}
   527  		return norm
   528  	case math.IsInf(L, 1):
   529  		for _, v := range s {
   530  			norm = math.Max(norm, cmplx.Abs(v))
   531  		}
   532  		return norm
   533  	default:
   534  		for _, v := range s {
   535  			norm += math.Pow(cmplx.Abs(v), L)
   536  		}
   537  		return math.Pow(norm, 1/L)
   538  	}
   539  }
   540  
   541  // Prod returns the product of the elements of the slice.
   542  // Returns 1 if len(s) = 0.
   543  func Prod(s []complex128) complex128 {
   544  	prod := 1 + 0i
   545  	for _, val := range s {
   546  		prod *= val
   547  	}
   548  	return prod
   549  }
   550  
   551  // Real places the real components of src into dst.
   552  // It panics if the argument lengths do not match.
   553  func Real(dst []float64, src []complex128) []float64 {
   554  	if len(dst) != len(src) {
   555  		panic(badDstLength)
   556  	}
   557  	if len(dst) == 0 {
   558  		return dst
   559  	}
   560  	for i, z := range src {
   561  		dst[i] = real(z)
   562  	}
   563  	return dst
   564  }
   565  
   566  // Reverse reverses the order of elements in the slice.
   567  //
   568  // Deprecated: This function simply calls [slices.Reverse].
   569  func Reverse(s []complex128) {
   570  	slices.Reverse(s)
   571  }
   572  
   573  // Same returns true when the input slices have the same length and all
   574  // elements have the same value with NaN treated as the same.
   575  func Same(s, t []complex128) bool {
   576  	if len(s) != len(t) {
   577  		return false
   578  	}
   579  	for i, v := range s {
   580  		w := t[i]
   581  		if v != w && !(cmplx.IsNaN(v) && cmplx.IsNaN(w)) {
   582  			return false
   583  		}
   584  	}
   585  	return true
   586  }
   587  
   588  // Scale multiplies every element in dst by the scalar c.
   589  func Scale(c complex128, dst []complex128) {
   590  	if len(dst) > 0 {
   591  		c128.ScalUnitary(c, dst)
   592  	}
   593  }
   594  
   595  // ScaleReal multiplies every element in dst by the real scalar f.
   596  func ScaleReal(f float64, dst []complex128) {
   597  	for i, z := range dst {
   598  		dst[i] = complex(f*real(z), f*imag(z))
   599  	}
   600  }
   601  
   602  // ScaleRealTo multiplies the elements in s by the real scalar f and
   603  // stores the result in dst.
   604  // It panics if the slice argument lengths do not match.
   605  func ScaleRealTo(dst []complex128, f float64, s []complex128) []complex128 {
   606  	if len(dst) != len(s) {
   607  		panic(badDstLength)
   608  	}
   609  	for i, z := range s {
   610  		dst[i] = complex(f*real(z), f*imag(z))
   611  	}
   612  	return dst
   613  }
   614  
   615  // ScaleTo multiplies the elements in s by c and stores the result in dst.
   616  // It panics if the slice argument lengths do not match.
   617  func ScaleTo(dst []complex128, c complex128, s []complex128) []complex128 {
   618  	if len(dst) != len(s) {
   619  		panic(badDstLength)
   620  	}
   621  	if len(dst) > 0 {
   622  		c128.ScalUnitaryTo(dst, c, s)
   623  	}
   624  	return dst
   625  }
   626  
   627  // Span returns a set of N equally spaced points between l and u, where N
   628  // is equal to the length of the destination. The first element of the destination
   629  // is l, the final element of the destination is u.
   630  // It panics if the length of dst is less than 2.
   631  //
   632  // Span also returns the mutated slice dst, so that it can be used in range expressions,
   633  // like:
   634  //
   635  //	for i, x := range Span(dst, l, u) { ... }
   636  func Span(dst []complex128, l, u complex128) []complex128 {
   637  	n := len(dst)
   638  	if n < 2 {
   639  		panic(shortSpan)
   640  	}
   641  
   642  	// Special cases for Inf and NaN.
   643  	switch {
   644  	case cmplx.IsNaN(l):
   645  		for i := range dst[:len(dst)-1] {
   646  			dst[i] = cmplx.NaN()
   647  		}
   648  		dst[len(dst)-1] = u
   649  		return dst
   650  	case cmplx.IsNaN(u):
   651  		for i := range dst[1:] {
   652  			dst[i+1] = cmplx.NaN()
   653  		}
   654  		dst[0] = l
   655  		return dst
   656  	case cmplx.IsInf(l) && cmplx.IsInf(u):
   657  		for i := range dst {
   658  			dst[i] = cmplx.Inf()
   659  		}
   660  		return dst
   661  	case cmplx.IsInf(l):
   662  		for i := range dst[:len(dst)-1] {
   663  			dst[i] = l
   664  		}
   665  		dst[len(dst)-1] = u
   666  		return dst
   667  	case cmplx.IsInf(u):
   668  		for i := range dst[1:] {
   669  			dst[i+1] = u
   670  		}
   671  		dst[0] = l
   672  		return dst
   673  	}
   674  
   675  	step := (u - l) / complex(float64(n-1), 0)
   676  	for i := range dst {
   677  		dst[i] = l + step*complex(float64(i), 0)
   678  	}
   679  	return dst
   680  }
   681  
   682  // Sub subtracts, element-wise, the elements of s from dst.
   683  // It panics if the argument lengths do not match.
   684  func Sub(dst, s []complex128) {
   685  	if len(dst) != len(s) {
   686  		panic(badLength)
   687  	}
   688  	c128.AxpyUnitaryTo(dst, -1, s, dst)
   689  }
   690  
   691  // SubTo subtracts, element-wise, the elements of t from s and
   692  // stores the result in dst.
   693  // It panics if the argument lengths do not match.
   694  func SubTo(dst, s, t []complex128) []complex128 {
   695  	if len(s) != len(t) {
   696  		panic(badLength)
   697  	}
   698  	if len(dst) != len(s) {
   699  		panic(badDstLength)
   700  	}
   701  	c128.AxpyUnitaryTo(dst, -1, t, s)
   702  	return dst
   703  }
   704  
   705  // Sum returns the sum of the elements of the slice.
   706  func Sum(s []complex128) complex128 {
   707  	return c128.Sum(s)
   708  }