changeset 31927:fef004fa8575

Improve Matlab compatibility of linspace for Inf endpoints. Return an N-element vector if both START and END are the same. When N is odd and [START, END] are [-Inf, Inf] return 0 for middle return value. For all other elements besides START and END return NaN when one endpoint is not finite. * data.cc (Flinspace): Update and add new BIST tests for linspace. * CRowVector.cc, dRowVector.cc, fCRowVector.cc, fRowVector.cc (linspace): Add special case in input validation for START==END and return an N-element vector of value END. Check whether variable delta is Inf value and change delta to NaN if it is. For complex calculations, a separate isinf() check is done for real and imaginary parts of delta. For middle point when N is odd, return 0 if START == -END, NaN if delta was NaN, or otherwise (START + END) / 2. * logspace.m: Update BIST tests.
author Rik <rik@octave.org>
date Fri, 24 Mar 2023 14:42:24 -0700
parents cd5be42a81ab
children a8473e7db0f4 69c15474d829
files libinterp/corefcn/data.cc liboctave/array/CRowVector.cc liboctave/array/dRowVector.cc liboctave/array/fCRowVector.cc liboctave/array/fRowVector.cc scripts/general/logspace.m
diffstat 6 files changed, 130 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/data.cc	Fri Mar 24 16:08:15 2023 -0400
+++ b/libinterp/corefcn/data.cc	Fri Mar 24 14:42:24 2023 -0700
@@ -5745,27 +5745,41 @@
 %!assert (numel (linspace (0, 1, 2-eps)), 1)
 %!assert (linspace (10, 20, 2.1), [10 20])
 %!assert (linspace (10, 20, 2.9), [10 20])
-%!assert (1 ./ linspace (-0, 0, 4), [-Inf, Inf, Inf, Inf])
 %!assert (linspace (Inf, Inf, 3), [Inf, Inf, Inf])
 %!assert (linspace (-Inf, -Inf, 3), [-Inf, -Inf, -Inf])
 %!assert (linspace (-Inf, Inf, 3), [-Inf, 0, Inf])
+## Octave prefers to return NaN which indicates failure of algorithm.
+%!assert (linspace (-Inf, Inf, 4), [-Inf, NaN, NaN, Inf])
+%!assert (linspace (-Inf, 0, 3), [-Inf, NaN, 0])
+%!assert (linspace (-Inf, 0, 4), [-Inf, NaN, NaN, 0])
 %!assert (linspace (Inf + 1i, Inf + 1i, 3), [Inf + 1i, Inf + 1i, Inf + 1i])
-%!assert (linspace (-Inf + 1i, Inf + 1i, 3), [-Inf + 1i, NaN + 1i, Inf + 1i])
+%!assert (linspace (-Inf - 1i, Inf + 1i, 3), [-Inf - 1i, 0 + 0i, Inf + 1i])
+%!assert (linspace (-Inf - 1i, Inf + 2i, 3), [-Inf - 1i, NaN + 0.5i, Inf + 2i])
+%!assert (linspace (-Inf - 3i, Inf + 0i, 4),
+%!        [-Inf - 3i, NaN - 2i, NaN - 1i, Inf + 0i])
+%!assert (linspace (complex (-1, -Inf), complex (1, Inf), 3),
+%!          [complex(-1, -Inf), 0 + 0i, complex(1, Inf)])
+%!assert (linspace (complex (-1, -Inf), complex (2, Inf), 3),
+%!          [complex(-1, -Inf), complex(0.5, NaN), complex(2, Inf)])
+%!assert (linspace (complex (-3, -Inf), complex (0, Inf), 4),
+%!        [complex(-3, -Inf), complex(-2, NaN), complex(-1, NaN), complex(0, Inf)])
 
 ## FIXME: Octave is not fully Matlab-compatible for some combinations of
 ##        Inf/-Inf endpoints.  See bug #56933.  This was dubbed "Won't Fix"
-##        so these tests have been removed from the test suite by commenting
-##        them out.  If the behavior in the future is made compatible these
-##        tests can be re-instated.
+##        as Octave prefers to return NaN for some of these conditions to
+##        better reflect that the algorithm has failed.  If the behavior in
+##        the future is made compatible these tests can be re-instated.
 ##%!assert <56933> (linspace (-Inf, Inf, 4), [-Inf, -Inf, Inf, Inf])
 ##%!assert <56933> (linspace (-Inf, Inf, 5), [-Inf, -Inf, 0, Inf, Inf])
 ##%!assert <56933> (linspace (0, Inf, 4), [0, Inf, Inf, Inf])
 ##%!assert <56933> (linspace (0, -Inf, 4), [0, -Inf, -Inf, -Inf])
 ##%!assert <56933> (linspace (-Inf, 0, 4), [-Inf, NaN, NaN, 0])
 ##%!assert <56933> (linspace (Inf, 0, 4), [Inf, NaN, NaN, 0])
-
-%!error linspace ()
-%!error linspace (1, 2, 3, 4)
+##%!assert (1 ./ linspace (-0, 0, 4), [-Inf, Inf, Inf, Inf])
+
+## Test input validation
+%!error <Invalid call> linspace ()
+%!error <Invalid call> linspace (1, 2, 3, 4)
 %!error <N must be a scalar> linspace (1, 2, [3, 4])
 %!error <START, END must be scalars or vectors> linspace (ones (2,2), 2, 3)
 %!error <START, END must be scalars or vectors> linspace (2, ones (2,2), 3)
--- a/liboctave/array/CRowVector.cc	Fri Mar 24 16:08:15 2023 -0400
+++ b/liboctave/array/CRowVector.cc	Fri Mar 24 14:42:24 2023 -0700
@@ -436,6 +436,11 @@
       retval.resize (1, x2);
       return retval;
     }
+  else if (x1 == x2)
+    {
+      retval.resize (n_in, x2);
+      return retval;
+    }
 
   // Use unsigned type (guaranteed n_in > 1 at this point) so that divisions
   // by 2 can be replaced by compiler with shift right instructions.
@@ -449,7 +454,17 @@
   retval.xelem (n-1) = x2;
 
   // Construct linspace symmetrically from both ends.
+  bool isnan_delta = false;
   Complex delta = (x2 - x1) / (n - 1.0);
+  if (octave::math::isinf (delta))
+    {
+      if (octave::math::isinf (delta.real ()))
+        delta.real (octave::numeric_limits<double>::NaN ());
+      if (octave::math::isinf (delta.imag ()))
+        delta.imag (octave::numeric_limits<double>::NaN ());
+      isnan_delta = true;
+    }
+
   unsigned_octave_idx_type n2 = n/2;
   for (unsigned_octave_idx_type i = 1; i < n2; i++)
     {
@@ -457,7 +472,22 @@
       retval.xelem (n-1-i) = x2 - static_cast<double> (i)*delta;
     }
   if (n % 2 == 1)  // Middle element if number of elements is odd.
-    retval.xelem (n2) = (x1 == -x2 ? 0 : (x1 + x2) / 2.0);
+    {
+      if (x1 == -x2)
+        retval.xelem (n2) = 0;
+      else
+        {
+          Complex c = (x1 + x2) / 2.0;
+          if (isnan_delta)
+            {
+              if (octave::math::isnan (delta.real ()))
+                c.real (octave::numeric_limits<double>::NaN ());
+              if (octave::math::isnan (delta.imag ()))
+                c.imag (octave::numeric_limits<double>::NaN ());
+            }
+          retval.xelem (n2) = c;
+        }
+    }
 
   return retval;
 }
--- a/liboctave/array/dRowVector.cc	Fri Mar 24 16:08:15 2023 -0400
+++ b/liboctave/array/dRowVector.cc	Fri Mar 24 14:42:24 2023 -0700
@@ -279,6 +279,11 @@
       retval.resize (1, x2);
       return retval;
     }
+  else if (x1 == x2)
+    {
+      retval.resize (n_in, x2);
+      return retval;
+    }
 
   // Use unsigned type (guaranteed n_in > 1 at this point) so that divisions
   // by 2 can be replaced by compiler with shift right instructions.
@@ -292,7 +297,14 @@
   retval.xelem (n-1) = x2;
 
   // Construct linspace symmetrically from both ends.
+  bool isnan_delta = false;
   double delta = (x2 - x1) / (n - 1);
+  if (octave::math::isinf (delta))
+    {
+      delta = octave::numeric_limits<double>::NaN ();
+      isnan_delta = true;
+    }
+
   unsigned_octave_idx_type n2 = n/2;
   for (unsigned_octave_idx_type i = 1; i < n2; i++)
     {
@@ -300,7 +312,14 @@
       retval.xelem (n-1-i) = x2 - i*delta;
     }
   if (n % 2 == 1)  // Middle element if number of elements is odd.
-    retval.xelem (n2) = (x1 == -x2 ? 0 : (x1 + x2) / 2);
+    {
+      if (x1 == -x2)
+        retval.xelem (n2) = 0;
+      else if (isnan_delta)
+        retval.xelem (n2) = octave::numeric_limits<double>::NaN ();
+      else
+        retval.xelem (n2) = (x1 + x2) / 2;
+    }
 
   return retval;
 }
--- a/liboctave/array/fCRowVector.cc	Fri Mar 24 16:08:15 2023 -0400
+++ b/liboctave/array/fCRowVector.cc	Fri Mar 24 14:42:24 2023 -0700
@@ -438,6 +438,11 @@
       retval.resize (1, x2);
       return retval;
     }
+  else if (x1 == x2)
+    {
+      retval.resize (n_in, x2);
+      return retval;
+    }
 
   // Use unsigned type (guaranteed n_in > 1 at this point) so that divisions
   // by 2 can be replaced by compiler with shift right instructions.
@@ -451,7 +456,17 @@
   retval.xelem (n-1) = x2;
 
   // Construct linspace symmetrically from both ends.
+  bool isnan_delta = false;
   FloatComplex delta = (x2 - x1) / (n - 1.0f);
+  if (octave::math::isinf (delta))
+    {
+      if (octave::math::isinf (delta.real ()))
+        delta.real (octave::numeric_limits<float>::NaN ());
+      if (octave::math::isinf (delta.imag ()))
+        delta.imag (octave::numeric_limits<float>::NaN ());
+      isnan_delta = true;
+    }
+
   unsigned_octave_idx_type n2 = n/2;
   for (unsigned_octave_idx_type i = 1; i < n2; i++)
     {
@@ -459,7 +474,22 @@
       retval.xelem (n-1-i) = x2 - static_cast<float> (i)*delta;
     }
   if (n % 2 == 1)  // Middle element if number of elements is odd.
-    retval.xelem (n2) = (x1 == -x2 ? 0 : (x1 + x2) / 2.0f);
+    {
+      if (x1 == -x2)
+        retval.xelem (n2) = 0;
+      else
+        {
+          FloatComplex c = (x1 + x2) / 2.0f;
+          if (isnan_delta)
+            {
+              if (octave::math::isnan (delta.real ()))
+                c.real (octave::numeric_limits<float>::NaN ());
+              if (octave::math::isnan (delta.imag ()))
+                c.imag (octave::numeric_limits<float>::NaN ());
+            }
+          retval.xelem (n2) = c;
+        }
+    }
 
   return retval;
 }
--- a/liboctave/array/fRowVector.cc	Fri Mar 24 16:08:15 2023 -0400
+++ b/liboctave/array/fRowVector.cc	Fri Mar 24 14:42:24 2023 -0700
@@ -279,6 +279,11 @@
       retval.resize (1, x2);
       return retval;
     }
+  else if (x1 == x2)
+    {
+      retval.resize (n_in, x2);
+      return retval;
+    }
 
   // Use unsigned type (guaranteed n_in > 1 at this point) so that divisions
   // by 2 can be replaced by compiler with shift right instructions.
@@ -292,7 +297,14 @@
   retval.xelem (n-1) = x2;
 
   // Construct linspace symmetrically from both ends.
+  bool isnan_delta = false;
   float delta = (x2 - x1) / (n - 1);
+  if (octave::math::isinf (delta))
+    {
+      delta = octave::numeric_limits<float>::NaN ();
+      isnan_delta = true;
+    }
+
   unsigned_octave_idx_type n2 = n/2;
   for (unsigned_octave_idx_type i = 1; i < n2; i++)
     {
@@ -300,7 +312,14 @@
       retval.xelem (n-1-i) = x2 - i*delta;
     }
   if (n % 2 == 1)  // Middle element if number of elements is odd.
-    retval.xelem (n2) = (x1 == -x2 ? 0 : (x1 + x2) / 2);
+    {
+      if (x1 == -x2)
+        retval.xelem (n2) = 0;
+      else if (isnan_delta)
+        retval.xelem (n2) = octave::numeric_limits<float>::NaN ();
+      else
+        retval.xelem (n2) = (x1 + x2) / 2;
+    }
 
   return retval;
 }
--- a/scripts/general/logspace.m	Fri Mar 24 16:08:15 2023 -0400
+++ b/scripts/general/logspace.m	Fri Mar 24 14:42:24 2023 -0700
@@ -130,15 +130,13 @@
 %!testif ; ismac () <55538>
 %! assert (logspace (-Inf + 1i, Inf + 1i, 3),
 %!         [0, NaN + NaN * 1i, complex(-Inf, Inf)])
-%!assert (logspace (0, Inf, 3), [1, Inf, Inf])
-%!assert (logspace (0, -Inf, 3), [1, 0, 0])
+## Octave prefers to return NaN which indicates failure of algorithm.
+## Tests can be re-instated if full Matlab-compatibility is coded.
+%!#assert (logspace (0, Inf, 3), [1, Inf, Inf])
+%!#assert (logspace (0, -Inf, 3), [1, 0, 0])
 %!assert (logspace (Inf, -Inf, 3), [Inf, 1, 0])
-
-## FIXME: These are bizarre corner cases for Matlab compatibility.  See
-## bug #56933.  This is marked as "Won't Fix", but if linspace is updated at
-## some point then these tests can be re-instated.
-%!#assert (logspace (-Inf, 0, 3), [0, NaN, 1])
-%!#assert (logspace (Inf, 0, 3), [Inf, NaN, 1])
+%!assert (logspace (-Inf, 0, 3), [0, NaN, 1])
+%!assert (logspace (Inf, 0, 3), [Inf, NaN, 1])
 
 ## Test input validation
 %!error <Invalid call> logspace ()