changeset 27759:992e702ef0d7

Add stream* functions (patch #9859). * libinterp/corefcn/stream-euler.cc: New file. * libinterp/corefcn/module.mk: Add new file to list of source files. * stream2.m, stream3.m, streamline.m, streamtube.m: New functions. * plot.txi: Add documentation of new functions to manual. * __unimplemented__.m: Remove implemented functions from list. * NEWS: Announce new functions.
author Markus Meisinger <chloros2@gmx.de>
date Sat, 23 Nov 2019 11:47:16 +0100
parents fc8668e1cb26
children 16e83787f970
files NEWS doc/interpreter/plot.txi libinterp/corefcn/module.mk libinterp/corefcn/stream-euler.cc scripts/help/__unimplemented__.m scripts/plot/draw/module.mk scripts/plot/draw/stream2.m scripts/plot/draw/stream3.m scripts/plot/draw/streamline.m scripts/plot/draw/streamtube.m
diffstat 10 files changed, 1500 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Sun Nov 17 10:26:50 2019 +0100
+++ b/NEWS	Sat Nov 23 11:47:16 2019 +0100
@@ -140,6 +140,10 @@
 * `rotx`
 * `roty`
 * `rotz`
+* `stream2`
+* `stream3`
+* `streamline`
+* `streamtube`
 * `uisetfont`
 * `verLessThan`
 * `web`
--- a/doc/interpreter/plot.txi	Sun Nov 17 10:26:50 2019 +0100
+++ b/doc/interpreter/plot.txi	Sat Nov 23 11:47:16 2019 +0100
@@ -245,6 +245,14 @@
 
 @DOCSTRING(quiver3)
 
+@DOCSTRING(streamtube)
+
+@DOCSTRING(streamline)
+
+@DOCSTRING(stream2)
+
+@DOCSTRING(stream3)
+
 @DOCSTRING(compass)
 
 @DOCSTRING(feather)
--- a/libinterp/corefcn/module.mk	Sun Nov 17 10:26:50 2019 +0100
+++ b/libinterp/corefcn/module.mk	Sat Nov 23 11:47:16 2019 +0100
@@ -238,6 +238,7 @@
   %reldir%/spparms.cc \
   %reldir%/sqrtm.cc \
   %reldir%/stack-frame.cc \
+  %reldir%/stream-euler.cc \
   %reldir%/strfind.cc \
   %reldir%/strfns.cc \
   %reldir%/sub2ind.cc \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libinterp/corefcn/stream-euler.cc	Sat Nov 23 11:47:16 2019 +0100
@@ -0,0 +1,530 @@
+/*
+
+Copyright (C) 2019 Markus Meisinger
+
+This file is part of Octave.
+
+Octave is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Octave is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<https://www.gnu.org/licenses/>.
+
+*/
+
+/*
+
+ References:
+
+ @article{
+    title = {Particle Tracing Algorithms for 3D Curvilinear Grids},
+    year = {2000},
+    author = {Nielson, Gregory and Uller, H. and Sadarjoen, I. and Walsum, Theo and Hin, Andrea and Post, Frits}
+ }
+
+ @article{
+    title = {Sources of error in the graphical analysis of CFD results},
+    publisher = {Journal of Scientific Computing},
+    year = {1988},
+    volume = {3},
+    number = {2},
+    pages = {149--164},
+    author = {Buning, Pieter G.},
+ }
+
+*/
+
+#if defined (HAVE_CONFIG_H)
+#  include "config.h"
+#endif
+
+#include "defun.h"
+#include "error.h"
+#include "ovl.h"
+
+// Coordinates of a point in C-Space (unit square mesh)
+
+typedef struct
+{
+  double x, y;
+} Vector2;
+
+// The integer- and the fractional value from a point in C-Space.
+// Equivalent to the cell index the point is located in and the local
+// coordinates of the point in the cell.
+
+typedef struct
+{
+  double fcx, fcy;
+  signed long idx, idy;
+} Cell2;
+
+typedef struct
+{
+  double x, y, z;
+} Vector3;
+
+typedef struct
+{
+  double fcx, fcy, fcz;
+  signed long idx, idy, idz;
+} Cell3;
+
+static inline void
+number_to_fractional (signed long *id, double *fc, const double u)
+{
+  *id = floor (u);
+  *fc = u - *id;
+}
+
+static inline octave_idx_type
+handle_border_index (const octave_idx_type id, const octave_idx_type N)
+{
+  return (id < N - 1 ? id : N - 2);
+}
+
+static inline void
+handle_border (octave_idx_type *id2, double *fc2, const octave_idx_type id1,
+               const double fc1, const octave_idx_type N)
+{
+  if (id1 < N - 1)
+    {
+      *id2 = id1;
+      *fc2 = fc1;
+    }
+  else
+    {
+      *id2 = N - 2;
+      *fc2 = 1.0;
+    }
+}
+
+static inline double
+bilinear (const double u11, const double u21, const double u12,
+          const double u22, const double x, const double y)
+{
+  return (u11 * (1-x) * (1-y) +
+          u21 * x * (1-y) +
+          u12 * (1-x) * y +
+          u22 * x * y);
+}
+
+static inline Cell2
+vector_to_cell2d (const Vector2 X)
+{
+  Cell2 Z;
+
+  number_to_fractional (&Z.idx, &Z.fcx, X.x);
+  number_to_fractional (&Z.idy, &Z.fcy, X.y);
+
+  return (Z);
+}
+
+static inline bool
+is_in_definition_set2d (const Cell2 X, const octave_idx_type cols,
+                        const octave_idx_type rows)
+{
+ return ( (((X.idx >= 0) && (X.idx < cols-1)) ||
+           ((X.idx == cols-1) && (X.fcx == 0.0))) &&
+          (((X.idy >= 0) && (X.idy < rows-1)) ||
+           ((X.idy == rows-1) && (X.fcy == 0.0))) );
+}
+
+static inline Vector2
+add2d (const Cell2 X, const Vector2 Y)
+{
+  Vector2 Z = {X.idx + X.fcx + Y.x,
+               X.idy + X.fcy + Y.y};
+
+  return (Z);
+}
+
+static inline Vector2
+vector_interpolation2d (const Cell2 X, const Matrix& u, const Matrix& v,
+                        const octave_idx_type cols, const octave_idx_type rows)
+{
+  Vector2 V;
+  double fcx, fcy;
+  octave_idx_type idx, idy;
+
+  handle_border (&idx, &fcx, X.idx, X.fcx, cols);
+  handle_border (&idy, &fcy, X.idy, X.fcy, rows);
+
+  V.x = bilinear (u(idy, idx), u(idy, idx+1), u(idy+1, idx),
+                  u(idy+1, idx+1), fcx, fcy);
+  V.y = bilinear (v(idy, idx), v(idy, idx+1), v(idy+1, idx),
+                  v(idy+1, idx+1), fcx, fcy);
+
+  return (V);
+}
+
+// Apply the Jacobian matrix on the vector V.
+// The step vector length is set to h.
+
+static inline Vector2
+calculate_step_vector2d (const Cell2 X, const Vector2 V,
+                         const RowVector& tx, const RowVector& ty,
+                         const octave_idx_type cols, const octave_idx_type rows,
+                         const double h)
+{
+  Vector2 S;
+
+  const octave_idx_type idx = handle_border_index (X.idx, cols);
+  const octave_idx_type idy = handle_border_index (X.idy, rows);
+
+  const double x = V.x * tx(idx);
+  const double y = V.y * ty(idy);
+  const double n = 1.0 / sqrt (x*x + y*y);
+  S.x = h * n * x;
+  S.y = h * n * y;
+
+  return (S);
+}
+
+static inline bool
+is_singular2d (const Vector2 V)
+{
+  return ((octave::math::isnan (V.x) || octave::math::isnan (V.y)) ||
+          ((V.x == 0) && (V.y == 0)));
+}
+
+static void
+euler2d (const octave_idx_type cols, const octave_idx_type rows,
+         const Matrix& u, const Matrix& v,
+         const RowVector& tx, const RowVector& ty,
+         const double zeta, const double xi,
+         const double h, const octave_idx_type maxnverts,
+         Matrix& buffer, octave_idx_type *nverts)
+{
+  Vector2 V0, V1, S0, X1, Xnxt, S1;
+  const Vector2 X0 = {zeta, xi};
+  Cell2 X0f, X1f;
+
+  octave_idx_type i = 0;
+
+  buffer(i, 0) = X0.x;
+  buffer(i, 1) = X0.y;
+
+  X0f = vector_to_cell2d (X0);
+  while (true)
+    {
+      if (! is_in_definition_set2d (X0f, cols, rows))
+        break;
+
+      V0 = vector_interpolation2d (X0f, u, v, cols, rows);
+      if (is_singular2d (V0))
+        break;
+
+      S0 = calculate_step_vector2d (X0f, V0, tx, ty, cols, rows, h);
+
+      X1 = add2d (X0f, S0);
+      X1f = vector_to_cell2d (X1);
+      if (! is_in_definition_set2d (X1f, cols, rows))
+        break;
+
+      V1 = vector_interpolation2d (X1f, u, v, cols, rows);
+      if (is_singular2d (V1))
+        break;
+
+      S1 = calculate_step_vector2d (X1f, V1, tx, ty, cols, rows, h);
+
+      // Runge Kutta - Heun's Scheme
+      const Vector2 S = {0.5 * (S0.x + S1.x),
+                         0.5 * (S0.y + S1.y)};
+      Xnxt = add2d (X0f, S);
+
+      X0f = vector_to_cell2d (Xnxt);
+      if (! is_in_definition_set2d (X0f, cols, rows))
+        break;
+
+      i++;
+      buffer(i, 0) = Xnxt.x;
+      buffer(i, 1) = Xnxt.y;
+
+      if (i + 1 >= maxnverts)
+        break;
+    }
+
+  *nverts = i + 1;
+}
+
+static inline double
+trilinear (const double u111, const double u211, const double u121,
+           const double u221, const double u112, const double u212,
+           const double u122, const double u222,
+           const double x, const double y, const double z)
+{
+  return (u111 * (1-x) * (1-y) * (1-z) +
+          u211 * x * (1-y) * (1-z) +
+          u121 * (1-x) * y * (1-z) +
+          u221 * x * y * (1-z) +
+          u112 * (1-x) * (1-y) * z +
+          u212 * x * (1-y) * z +
+          u122 * (1-x) * y * z +
+          u222 * x * y * z);
+}
+
+static inline Cell3
+vector_to_cell3d (const Vector3 X)
+{
+  Cell3 Z;
+
+  number_to_fractional (&Z.idx, &Z.fcx, X.x);
+  number_to_fractional (&Z.idy, &Z.fcy, X.y);
+  number_to_fractional (&Z.idz, &Z.fcz, X.z);
+
+  return (Z);
+}
+
+static inline bool
+is_in_definition_set3d (const Cell3 X, const octave_idx_type nx,
+                        const octave_idx_type ny, const octave_idx_type nz)
+{
+  return ( (((X.idx >= 0) && (X.idx < nx-1)) ||
+            ((X.idx == nx-1) && (X.fcx == 0.0))) &&
+           (((X.idy >= 0) && (X.idy < ny-1)) ||
+            ((X.idy == ny-1) && (X.fcy == 0.0))) &&
+           (((X.idz >= 0) && (X.idz < nz-1)) ||
+            ((X.idz == nz-1) && (X.fcz == 0.0))) );
+}
+
+static inline Vector3
+add3d (const Cell3 X, const Vector3 Y)
+{
+  Vector3 Z = {X.idx + X.fcx + Y.x,
+               X.idy + X.fcy + Y.y,
+               X.idz + X.fcz + Y.z};
+
+  return (Z);
+}
+
+static inline Vector3
+vector_interpolation3d (const Cell3 X, const NDArray& u, const NDArray& v,
+                        const NDArray& w, const octave_idx_type nx,
+                        const octave_idx_type ny, const octave_idx_type nz)
+{
+  Vector3 V;
+  double fcx, fcy, fcz;
+  octave_idx_type idx, idy, idz;
+
+  handle_border (&idx, &fcx, X.idx, X.fcx, nx);
+  handle_border (&idy, &fcy, X.idy, X.fcy, ny);
+  handle_border (&idz, &fcz, X.idz, X.fcz, nz);
+
+  V.x = trilinear (u(idy, idx, idz), u(idy, idx+1, idz),
+                   u(idy+1, idx, idz), u(idy+1, idx+1, idz),
+                   u(idy, idx, idz+1), u(idy, idx+1, idz+1),
+                   u(idy+1, idx, idz+1), u(idy+1, idx+1, idz+1),
+                   fcx, fcy, fcz);
+  V.y = trilinear (v(idy, idx, idz), v(idy, idx+1, idz),
+                   v(idy+1, idx, idz), v(idy+1, idx+1, idz),
+                   v(idy, idx, idz+1), v(idy, idx+1, idz+1),
+                   v(idy+1, idx, idz+1), v(idy+1, idx+1, idz+1),
+                   fcx, fcy, fcz);
+  V.z = trilinear (w(idy, idx, idz), w(idy, idx+1, idz),
+                   w(idy+1, idx, idz), w(idy+1, idx+1, idz),
+                   w(idy, idx, idz+1), w(idy, idx+1, idz+1),
+                   w(idy+1, idx, idz+1), w(idy+1, idx+1, idz+1),
+                   fcx, fcy, fcz);
+
+  return (V);
+}
+
+static inline Vector3
+calculate_step_vector3d (const Cell3 X, const Vector3 V,
+                         const RowVector& tx, const RowVector& ty, const RowVector& tz,
+                         const octave_idx_type nx, const octave_idx_type ny,
+                         const octave_idx_type nz, const double h)
+{
+  Vector3 S;
+
+  const octave_idx_type idx = handle_border_index (X.idx, nx);
+  const octave_idx_type idy = handle_border_index (X.idy, ny);
+  const octave_idx_type idz = handle_border_index (X.idz, nz);
+
+  const double x = V.x * tx(idx);
+  const double y = V.y * ty(idy);
+  const double z = V.z * tz(idz);
+  const double n = 1.0 / sqrt (x*x + y*y + z*z);
+  S.x = h * n * x;
+  S.y = h * n * y;
+  S.z = h * n * z;
+
+  return (S);
+}
+
+static inline bool
+is_singular3d (const Vector3 V)
+{
+  return ((octave::math::isnan (V.x) || octave::math::isnan (V.y) ||
+           octave::math::isnan (V.z)) ||
+          ((V.x == 0) && (V.y == 0) && (V.z == 0)));
+}
+
+static void
+euler3d (const octave_idx_type nx, const octave_idx_type ny, const octave_idx_type nz,
+         const NDArray& u, const NDArray& v, const NDArray& w,
+         const RowVector& tx, const RowVector& ty, const RowVector& tz,
+         const double zeta, const double xi, const double rho,
+         const double h, const octave_idx_type maxnverts,
+         Matrix& buffer, octave_idx_type *nverts)
+{
+  Vector3 V0, V1, S0, X1, Xnxt, S1;
+  const Vector3 X0 = {zeta, xi, rho};
+  Cell3 X0f, X1f;
+
+  octave_idx_type i = 0;
+  buffer(i, 0) = X0.x;
+  buffer(i, 1) = X0.y;
+  buffer(i, 2) = X0.z;
+
+  X0f = vector_to_cell3d (X0);
+  while (true)
+    {
+      if (! is_in_definition_set3d (X0f, nx, ny, nz))
+        break;
+
+      V0 = vector_interpolation3d (X0f, u, v, w, nx, ny, nz);
+      if (is_singular3d (V0))
+        break;
+
+      S0 = calculate_step_vector3d (X0f, V0, tx, ty, tz, nx, ny, nz, h);
+
+      X1 = add3d (X0f, S0);
+
+      X1f = vector_to_cell3d (X1);
+      if (! is_in_definition_set3d (X1f, nx, ny, nz))
+        break;
+
+      V1 = vector_interpolation3d (X1f, u, v, w, nx, ny, nz);
+      if (is_singular3d (V1))
+        break;
+
+      S1 = calculate_step_vector3d (X1f, V1, tx, ty, tz, nx, ny, nz, h);
+
+      // Runge Kutta - Heun's Scheme
+      const Vector3 S = {0.5 * (S0.x + S1.x),
+                         0.5 * (S0.y + S1.y),
+                         0.5 * (S0.z + S1.z)};
+      Xnxt = add3d (X0f, S);
+
+      X0f = vector_to_cell3d (Xnxt);
+      if (! is_in_definition_set3d (X0f, nx, ny, nz))
+        break;
+
+      i++;
+      buffer(i, 0) = Xnxt.x;
+      buffer(i, 1) = Xnxt.y;
+      buffer(i, 2) = Xnxt.z;
+
+      if (i + 1 >= maxnverts)
+        break;
+
+    }
+
+  *nverts = i + 1;
+}
+
+static octave_value
+streameuler2d_internal (const octave_value_list& args)
+{
+
+  const int nargin = args.length ();
+  if (nargin != 8)
+    print_usage ();
+
+  const Matrix U = args(0).matrix_value ();
+  const Matrix V = args(1).matrix_value ();
+  const RowVector TX = args(2).row_vector_value ();
+  const RowVector TY = args(3).row_vector_value ();
+  const double zeta = args(4).double_value ();
+  const double xi = args(5).double_value ();
+  const double h = args(6).double_value ();
+  const octave_idx_type maxnverts = args(7).idx_type_value ();
+
+  const octave_idx_type rows = U.rows ();
+  const octave_idx_type cols = U.columns ();
+
+  octave_idx_type nverts;
+  Matrix buffer (maxnverts, 2);
+
+  euler2d (cols, rows, U, V, TX, TY, zeta, xi, h, maxnverts,
+           buffer, &nverts);
+
+  Matrix xy = buffer.extract (0, 0, nverts-1, 1);
+
+  return octave_value (xy);
+}
+
+static octave_value
+streameuler3d_internal (const octave_value_list& args, const char *fcn)
+{
+
+  const int nargin = args.length ();
+  if (nargin != 11)
+    print_usage ();
+
+  const NDArray U = args(0).array_value ();
+  const NDArray V = args(1).array_value ();
+  const NDArray W = args(2).array_value ();
+  const RowVector TX = args(3).row_vector_value ();
+  const RowVector TY = args(4).row_vector_value ();
+  const RowVector TZ = args(5).row_vector_value ();
+  const double zeta = args(6).double_value ();
+  const double xi = args(7).double_value ();
+  const double rho = args(8).double_value ();
+  const double h = args(9).double_value ();
+  const octave_idx_type maxnverts = args(10).idx_type_value ();
+
+  const dim_vector dims = args(0).dims ();
+  const int ndims = dims.ndims ();
+  if (ndims != 3)
+    error ("%s: dimension must be 3", fcn);
+
+  octave_idx_type nverts;
+  Matrix buffer (maxnverts, 3);
+
+  euler3d (dims(1), dims(0), dims(2), U, V, W, TX, TY, TZ, zeta, xi, rho,
+           h, maxnverts, buffer, &nverts);
+
+  Matrix xyz = buffer.extract (0, 0, nverts-1, 2);
+
+  return octave_value (xyz);
+}
+
+DEFUN (streameuler2d, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {} streameuler2d (@var{U}, @var{V}, @var{TX}, @var{TY}, @var{ZETA}, @var{XI}, @var{H}, @var{MAXNVERTS})
+Calculates the streamline in a vector field [@var{U}, @var{V}] starting from a
+seed point at position [@var{ZETA}, @var{XI}]. The integrator used is
+Heun's Scheme. The step size can be controlled by @var{H}. The Jacobian
+matrix can be defined for each grid cell by [@var{TX}, @var{TY}].
+
+@seealso{streamline, stream2, stream3, streameuler3d}
+@end deftypefn */)
+{
+  return streameuler2d_internal (args);
+}
+
+DEFUN (streameuler3d, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn  {} {} streameuler3d (@var{U}, @var{V}, @var{W}, @var{TX}, @var{TY}, @var{TZ}, @var{ZETA}, @var{XI}, @var{RHO}, @var{H}, @var{MAXNVERTS})
+Calculates the streamline in a vector field [@var{U}, @var{V}, @var{W}] starting
+from a seed point at position [@var{ZETA}, @var{XI}, @var{RHO}]. The integrator
+used is Heun's Scheme. The step size can be controlled by @var{H}. The Jacobian
+matrix can be defined for each grid cell by [@var{TX}, @var{TY}, @var{TZ}].
+
+@seealso{streamline, stream2, stream3, streameuler2d}
+@end deftypefn */)
+{
+  return streameuler3d_internal (args, "streameuler3d");
+}
+
--- a/scripts/help/__unimplemented__.m	Sun Nov 17 10:26:50 2019 +0100
+++ b/scripts/help/__unimplemented__.m	Sat Nov 23 11:47:16 2019 +0100
@@ -1207,13 +1207,9 @@
   "step",
   "stopasync",
   "str2mat",
-  "stream2",
-  "stream3",
-  "streamline",
   "streamparticles",
   "streamribbon",
   "streamslice",
-  "streamtube",
   "string",
   "strings",
   "strip",
--- a/scripts/plot/draw/module.mk	Sun Nov 17 10:26:50 2019 +0100
+++ b/scripts/plot/draw/module.mk	Sat Nov 23 11:47:16 2019 +0100
@@ -94,6 +94,10 @@
   %reldir%/stem.m \
   %reldir%/stem3.m \
   %reldir%/stemleaf.m \
+  %reldir%/stream2.m \
+  %reldir%/stream3.m \
+  %reldir%/streamline.m \
+  %reldir%/streamtube.m \
   %reldir%/surf.m \
   %reldir%/surface.m \
   %reldir%/surfc.m \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/stream2.m	Sat Nov 23 11:47:16 2019 +0100
@@ -0,0 +1,199 @@
+## Copyright (C) 2019 Markus Meisinger
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{xy} =} stream2 (@var{x}, @var{y}, @var{u}, @var{v}, @var{sx}, @var{sy})
+## @deftypefnx {} {@var{xy} =} stream2 (@var{u}, @var{v}, @var{sx}, @var{sy})
+## @deftypefnx {} {@var{xy} =} stream2 (@dots{}, "@var{options}")
+## Compute 2D streamline data.
+##
+## Calculates streamlines of a vector field given by [@var{u}, @var{v}].
+## The vector field is defined over a rectangular grid given by
+## [@var{x}, @var{y}]. The streamlines start at the seed points
+## [@var{sx}, @var{sy}]. The returned value @var{xy} contains a cell array
+## of vertex arrays. If the starting point is outside the vector field,
+## [] is returned.
+##
+## The input parameter @var{options} is a 2D vector of the form
+## [@var{stepsize}, @var{maxnumbervertices}]. The first parameter specifies
+## the step size used for trajectory integration (default 0.1). It is
+## allowed to set a negative value to control the direction of integration.
+## The second parameter specifies the maximum number of segments used to
+## create a streamline (default 10000).
+##
+## The return value @var{xy} is a @nospell{nverts x 2} matrix containing the
+## coordinates of the field line segments.
+##
+## Example:
+##
+## @example
+## @group
+## [x, y] = meshgrid (0:3);
+## u = 2 * x;
+## v = y;
+## xy = stream2 (x, y, u, v, 1.0, 0.5);
+## @end group
+## @end example
+##
+## @seealso{streamline, stream3}
+##
+## @end deftypefn
+
+## References:
+##
+## @article{
+##    title = {Particle Tracing Algorithms for 3D Curvilinear Grids},
+##    year = {2000},
+##    author = {Nielson, Gregory and Uller, H. and Sadarjoen, I. and Walsum, Theo and Hin, Andrea and Post, Frits}
+## }
+##
+## @article{
+##    title = {Sources of error in the graphical analysis of CFD results},
+##    publisher = {Journal of Scientific Computing},
+##    year = {1988},
+##    volume = {3},
+##    number = {2},
+##    pages = {149--164},
+##    author = {Buning, Pieter G.},
+## }
+
+function xy = stream2 (varargin)
+
+  options = [];
+  switch (length (varargin))
+    case (0)
+      print_usage ();
+    case {4,5}
+      if (length (varargin) == 4)
+        [u, v, spx, spy] = varargin{:};
+      else
+        [u, v, spx, spy, options] = varargin{:};
+      endif
+      [m, n] = size (u);
+      [x, y] = meshgrid (1:n, 1:m);
+    case (6)
+      [x, y, u, v, spx, spy] = varargin{:};
+    case (7)
+      [x, y, u, v, spx, spy, options] = varargin{:};
+    otherwise
+      error ("stream2: unknown input parameter count");
+  endswitch
+
+  h = 0.1;
+  maxnverts = 10000;
+  if (! isempty (options))
+    switch (length (options))
+      case (1)
+        h = options(1);
+      case (2)
+        h = options(1);
+        maxnverts = options(2);
+      otherwise
+        error ("stream2: wrong options length");
+    endswitch
+  endif
+
+  if ((! isnumeric (h)) || (h == 0))
+    error ("stream2: step size error");
+  endif
+  if ((! isnumeric (maxnverts)) || (maxnverts < 1))
+    error ("stream2: max num vertices error");
+  endif
+  if (! (isequal (size (u), size (v), size (x), size (y)) && ...
+         isequal (size (spx), size (spy))) )
+    error ("stream2: matrix dimensions must match");
+  endif
+  if (iscomplex (u) || iscomplex (v) || iscomplex (x) || ...
+      iscomplex (y) || iscomplex (spx) || iscomplex (spy))
+    error ("stream2: input must be real valued");
+  endif
+
+  gx = x(1,:);
+  gy = y(:,1).';
+
+  ## Jacobian Matrix
+  dx = diff (gx);
+  dy = diff (gy);
+  ## "<" used to check if the mesh is ascending
+  if (any (dx <= 0) || any (dy <= 0) || ...
+      any (isnan (dx)) || any (isnan (dy)))
+    error ("stream2: ill shaped elements in mesh");
+  endif
+  tx = 1./dx;
+  ty = 1./dy;
+  ## "Don't cares" used for handling points located on the border
+  tx(end + 1) = 0;
+  ty(end + 1) = 0;
+  dx(end + 1) = 0;
+  dy(end + 1) = 0;
+
+  px = spx(:);
+  py = spy(:);
+
+  for nseed = 1:length (px)
+
+    xp = px(nseed);
+    yp = py(nseed);
+    idx = find (logical (diff (gx <= xp)), 1);
+    if (gx(end) == xp)
+      idx = numel (gx);
+    endif
+    idy = find (logical (diff (gy <= yp)), 1);
+    if (gy(end) == yp)
+      idy = numel (gy);
+    endif
+
+    if (isempty (idx) || isempty (idy))
+      xy{nseed} = [];
+    else
+      ## Transform seed from P coordinates to C coordinates
+      zeta = (idx - 1) + (xp - gx(idx)) * tx(idx);
+      xi = (idy - 1) + (yp - gy(idy)) * ty(idy);
+
+      C = streameuler2d (u, v, tx, ty, zeta, xi, h, maxnverts);
+
+      ## Transform from C coordinates to P coordinates
+      idu = floor (C(:,1));
+      idv = floor (C(:,2));
+      xy{nseed} = [gx(idu + 1).' + (C(:,1) - idu).*(dx(idu + 1).'), ...
+                   gy(idv + 1).' + (C(:,2) - idv).*(dy(idv + 1).')];
+    endif
+
+  endfor
+
+endfunction
+
+%!demo
+%! clf;
+%! [x, y] = meshgrid (-5:5, -4:4);
+%! u = x - 2 * y;
+%! v = 2 * x - 3 * y;
+%! sx = [3, 0, -1, -2, -3, 0, 1, 2];
+%! sy = [3, 3, 3, 3, -3, -3, -3, -3];
+%! h = streamline (x, y, u, v, sx, sy, 0.05);
+%! set (h, "color", "r");
+%! hold on;
+%! quiver (x, y, u, v);
+%! scatter (sx(:), sy(:), 20, "filled", "o", "markerfacecolor", "r");
+%! grid on;
+%! title ("Asymptotically Stable Equilibrium");
+%! axis equal;
+
+%!test
+%! xy = stream2([1,1,1;2,2,2;3,3,3], [1,1,1;2,2,2;3,3,3], 1, 1, [0.01,5]);
+%! assert (numel (xy{:}), 10);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/stream3.m	Sat Nov 23 11:47:16 2019 +0100
@@ -0,0 +1,223 @@
+## Copyright (C) 2019 Markus Meisinger
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {@var{xyz} =} stream3 (@var{x}, @var{y}, @var{z}, @var{u}, @var{v}, @var{w}, @var{sx}, @var{sy}, @var{sz})
+## @deftypefnx {} {@var{xyz} =} stream3 (@var{u}, @var{v}, @var{w}, @var{sx}, @var{sy}, @var{sz})
+## @deftypefnx {} {@var{xyz} =} stream3 (@dots{}, "@var{options}")
+## Compute 3D streamline data.
+##
+## Calculates streamlines of a vector field given by [@var{u}, @var{v}, @var{w}].
+## The vector field is defined over a rectangular grid given by
+## [@var{x}, @var{y}, @var{z}]. The streamlines start at the seed points
+## [@var{sx}, @var{sy}, @var{sz}]. The returned value @var{xyz}
+## contains a cell array of vertex arrays. If the starting point is outside
+## the vector field, [] is returned.
+##
+## The input parameter @var{options} is a 2D vector of the form
+## [@var{stepsize}, @var{maxnumbervertices}]. The first parameter specifies
+## the step size used for trajectory integration (default 0.1). It is
+## allowed to set a negative value to control the direction of integration.
+## The second parameter specifies the maximum number of segments used to
+## create a streamline (default 10000).
+##
+## The return value @var{xyz} is a @nospell{nverts x 3} matrix containing the
+## coordinates of the field line segments.
+##
+## Example:
+##
+## @example
+## @group
+## [x, y, z] = meshgrid (0:3);
+## u = 2 * x;
+## v = y;
+## w = 3 * z;
+## xyz = stream3 (x, y, z, u, v, w, 1.0, 0.5, 0.0);
+## @end group
+## @end example
+##
+## @seealso{streamline, stream2}
+##
+## @end deftypefn
+
+## References:
+##
+## @article{
+##    title = {Particle Tracing Algorithms for 3D Curvilinear Grids},
+##    year = {2000},
+##    author = {Nielson, Gregory and Uller, H. and Sadarjoen, I. and Walsum, Theo and Hin, Andrea and Post, Frits}
+## }
+##
+## @article{
+##    title = {Sources of error in the graphical analysis of CFD results},
+##    publisher = {Journal of Scientific Computing},
+##    year = {1988},
+##    volume = {3},
+##    number = {2},
+##    pages = {149--164},
+##    author = {Buning, Pieter G.},
+## }
+
+function xyz = stream3 (varargin)
+
+  options = [];
+  switch (length (varargin))
+    case (0)
+      print_usage ();
+    case {6,7}
+      if (length (varargin) == 6)
+        [u, v, w, spx, spy, spz] = varargin{:};
+      else
+        [u, v, w, spx, spy, spz, options] = varargin{:};
+      endif
+      [m, n, p] = size (u);
+      [x, y, z] = meshgrid (1:n, 1:m, 1:p);
+    case (9)
+      [x, y, z, u, v, w, spx, spy, spz] = varargin{:};
+    case (10)
+      [x, y, z, u, v, w, spx, spy, spz, options] = varargin{:};
+    otherwise
+      error ("stream3: unknown input parameter count");
+  endswitch
+
+  h = 0.1;
+  maxnverts = 10000;
+  if (! isempty (options))
+    switch (length (options))
+      case (1)
+        h = options(1);
+      case (2)
+        h = options(1);
+        maxnverts = options(2);
+      otherwise
+        error ("stream3: wrong options length");
+    endswitch
+  endif
+
+  if ((! isnumeric (h)) || (h == 0))
+    error ("stream3: step size error");
+  endif
+  if ((! isnumeric (maxnverts)) || (maxnverts < 1))
+    error ("stream3: max num vertices error");
+  endif
+  if (! (isequal (size (u), size (v), size (w), size (x), size (y), size (z)) && ...
+         isequal (size (spx), size (spy))) )
+    error ("stream3: matrix dimensions must match");
+  endif
+  if (iscomplex (u) || iscomplex (v) || iscomplex (w) || ...
+      iscomplex (x) || iscomplex (y) || iscomplex (z) || ...
+      iscomplex (spx) || iscomplex (spy) || iscomplex (spz))
+    error ("stream3: input must be real valued");
+  endif
+
+  gx = x(1, :, 1);
+  gy = y(:, 1, 1).';
+  tmp = z(1, 1, :);
+  gz = tmp(:).';
+
+  ## Jacobian Matrix
+  dx = diff (gx);
+  dy = diff (gy);
+  dz = diff (gz);
+  ## "<" used to check if the mesh is ascending
+  if (any (dx <= 0) || any (dy <= 0) || any (dz <= 0) || ...
+      any (isnan (dx)) || any (isnan (dy)) || any (isnan (dz)))
+    error ("stream3: ill shaped elements in mesh");
+  endif
+  tx = 1./dx;
+  ty = 1./dy;
+  tz = 1./dz;
+  ## "Don't cares" used for handling points located on the border
+  tx(end + 1) = 0;
+  ty(end + 1) = 0;
+  tz(end + 1) = 0;
+  dx(end + 1) = 0;
+  dy(end + 1) = 0;
+  dz(end + 1) = 0;
+
+  px = spx(:);
+  py = spy(:);
+  pz = spz(:);
+
+  for nseed = 1:length (px)
+
+    xp = px(nseed);
+    yp = py(nseed);
+    zp = pz(nseed);
+    idx = find (logical (diff (gx <= xp)), 1);
+    if (gx(end) == xp)
+      idx = numel (gx);
+    endif
+    idy = find (logical (diff (gy <= yp)), 1);
+    if (gy(end) == yp)
+      idy = numel (gy);
+    endif
+    idz = find (logical (diff (gz <= zp)), 1);
+    if (gz(end) == zp)
+      idz = numel (gz);
+    endif
+
+    if (isempty (idx) || isempty (idy) || isempty (idz))
+      xyz{nseed} = [];
+    else
+      ## Transform seed from P coordinates to C coordinates
+      zeta = (idx - 1) + (xp - gx(idx)) * tx(idx);
+      xi = (idy - 1) + (yp - gy(idy)) * ty(idy);
+      rho = (idz - 1) + (zp - gz(idz)) * tz(idz);
+
+      C = streameuler3d (u, v, w, tx, ty, tz, zeta, xi, rho, ...
+                         h, maxnverts);
+
+      ## Transform from C coordinates to P coordinates
+      idu = floor (C(:, 1));
+      idv = floor (C(:, 2));
+      idw = floor (C(:, 3));
+      xyz{nseed} = [gx(idu + 1).' + (C(:, 1) - idu).*(dx(idu + 1).'), ...
+                    gy(idv + 1).' + (C(:, 2) - idv).*(dy(idv + 1).'), ...
+                    gz(idw + 1).' + (C(:, 3) - idw).*(dz(idw + 1).')];
+    endif
+
+  endfor
+
+endfunction
+
+%!demo
+%! clf;
+%! [x, y, z] = meshgrid (-30:1:30, -30:1:30, 0:1:50);
+%! s = 10;
+%! b = 8 / 3;
+%! r = 28;
+%! u = s * (y - x);
+%! v = r * x - y - x.*z;
+%! w = x.*y - b * z;
+%! hold on;
+%! sx = 0.1;
+%! sy = 0.1;
+%! sz = 0.1;
+%! plot3 (sx, sy, sz, ".r", "markersize", 15);
+%! h = streamline (x, y, z, u, v, w, sx, sy, sz, [0.1, 50000]);
+%! set (h, "color", "r");
+%! view (3);
+%! title ("Lorenz System");
+%! grid on;
+%! axis equal;
+
+%!test
+%! [u, v, w] = meshgrid (0:3, 0:3, 0:3);
+%! xyz = stream3 (u, v, w, 2, 2, 2, [0.01,5]);
+%! assert (numel (xyz{:}), 15);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/streamline.m	Sat Nov 23 11:47:16 2019 +0100
@@ -0,0 +1,160 @@
+## Copyright (C) 2019 Markus Meisinger
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {} streamline (@var{x}, @var{y}, @var{z}, @var{u}, @var{v}, @var{w}, @var{sx}, @var{sy}, @var{sz})
+## @deftypefnx {} {} streamline (@var{u}, @var{v}, @var{w}, @var{sx}, @var{sy}, @var{sz})
+## @deftypefnx {} {} streamline (@dots{}, "@var{options}")
+## @deftypefnx {} {} streamline (@var{hax}, @dots{})
+## @deftypefnx {} {@var{h} =} streamline (@dots{})
+## Plot streamlines of 2D or 3D vector fields.
+##
+## Plot streamlines of a 2D or 3D vector field given by
+## [@var{u}, @var{v}] or [@var{u}, @var{v}, @var{w}]. The vector field
+## is defined over a rectangular grid given by [@var{x}, @var{y}]
+## or [@var{x}, @var{y}, @var{z}]. The streamlines start at the seed
+## points [@var{sx}, @var{sy}] or [@var{sx}, @var{sy}, @var{sz}].
+##
+## The input parameter @var{options} is a 2D vector of the form
+## [@var{stepsize}, @var{maxnumbervertices}]. The first parameter specifies
+## the step size used for trajectory integration (default 0.1). It is
+## allowed to set a negative value to control the direction of integration.
+## The second parameter specifies the maximum number of segments used to
+## create a streamline (default 10000).
+##
+## If the first argument @var{hax} is an axes handle, then plot into this axes,
+## rather than the current axes returned by @code{gca}.
+##
+## The optional return value @var{h} is a graphics handle to the hggroup
+## comprising the field lines.
+##
+## Example:
+##
+## @example
+## @group
+## [x, y] = meshgrid (-1.5:0.2:2, -1:0.2:2);
+## u = - x / 4 - y;
+## v = x - y / 4;
+## streamline (x, y, u, v, 1.7, 1.5);
+## @end group
+## @end example
+##
+## @seealso{stream2, stream3}
+##
+## @end deftypefn
+
+function h = streamline (varargin)
+
+  if (nargin == 0)
+    print_usage ();
+  endif
+
+  [hax, varargin] = __plt_get_axis_arg__ ("streamline", varargin{:});
+
+  if (isempty (hax))
+    hax = gca ();
+  else
+    hax = hax(1);
+  endif
+
+  h = [];
+  argval = varargin(1);
+  switch (numel (size (argval{:})))
+    case (2)
+      xy = stream2 (varargin{:});
+      for i = 1:length (xy)
+        sl = xy{i};
+        if (! isempty (sl))
+          tmp = line (hax, "xdata", sl(:, 1), "ydata", sl(:, 2), "color", "b");
+          h = [h; tmp];
+        endif
+      endfor
+    case (3)
+      xyz = stream3 (varargin{:});
+      for i = 1:length (xyz)
+        sl = xyz{i};
+        if (! isempty (sl))
+          tmp = line (hax, "xdata", sl(:, 1), "ydata", sl(:, 2), "zdata", sl(:, 3), ...
+                      "color", "b");
+          h = [h; tmp];
+        endif
+      endfor
+    otherwise
+      error ("streamline: 2D or 3D input data only");
+  endswitch
+
+endfunction
+
+%!demo
+%! clf;
+%! [x, y] = meshgrid (-2:0.5:2);
+%! u = - y - x / 2;
+%! v = x - y / 2;
+%! [sx, sy] = meshgrid (-2:2:2);
+%! h = streamline (x, y, u, v, sx, sy);
+%! set (h, "color", "r");
+%! hold on;
+%! quiver (x, y, u, v);
+%! scatter (sx(:), sy(:), 20, "filled", "o", "markerfacecolor", "r");
+%! title ("Spiral Sink");
+%! grid on;
+%! axis equal;
+
+%!demo
+%! clf;
+%! [x, y, z] = meshgrid (-3:3);
+%! u = - x / 2 - y;
+%! v = x - y / 2;
+%! w = - z;
+%! [sx, sy, sz] = meshgrid (3, 0:1.5:1.5, 0:1.5:3);
+%! h = streamline (x, y, z, u, v, w, sx, sy, sz);
+%! set (h, "color", "r");
+%! hold on;
+%! quiver3 (x, y, z, u, v, w);
+%! scatter3 (sx(:), sy(:), sz(:), 20, "filled", "o", "markerfacecolor", "r");
+%! view (3);
+%! title ("Spiral Sink");
+%! grid on;
+%! axis equal;
+
+%!demo
+%! clf;
+%! [x, y, z] = meshgrid (-1:0.4:1, -1:0.4:1, -3:0.3:0);
+%! a = 0.08;
+%! b = 0.04;
+%! u = - a * x - y;
+%! v = x - a * y;
+%! w = - b * ones (size (x));
+%! hold on;
+%! sx = 1.0;
+%! sy = 0.0;
+%! sz = 0.0;
+%! plot3 (sx, sy, sz, ".r", "markersize", 15);
+%! t = linspace (0, 12 * 2 * pi(), 500);
+%! tx = exp (-a * t).*cos (t);
+%! ty = exp (-a * t).*sin (t);
+%! tz = - b * t;
+%! plot3 (tx, ty, tz, "-b");
+%! h = streamline (x, y, z, u, v, w, sx, sy, sz);
+%! set (h, "color", "r");
+%! view (3);
+%! title ("Heuns Scheme (red) vs. Analytical Solution (blue)");
+%! grid on;
+%! axis equal tight;
+
+%!error streamline ()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/streamtube.m	Sat Nov 23 11:47:16 2019 +0100
@@ -0,0 +1,371 @@
+## Copyright (C) 2019 Markus Meisinger
+##
+## This file is part of Octave.
+##
+## Octave is free software: you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation, either version 3 of the License, or
+## (at your option) any later version.
+##
+## Octave is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Octave; see the file COPYING.  If not, see
+## <https://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn  {} {} streamtube (@var{x}, @var{y}, @var{z}, @var{u}, @var{v}, @var{w}, @var{sx}, @var{sy}, @var{sz})
+## @deftypefnx {} {} streamtube (@var{u}, @var{v}, @var{w}, @var{sx}, @var{sy}, @var{sz})
+## @deftypefnx {} {} streamtube (@var{vertices}, @var{x}, @var{y}, @var{z}, @var{u}, @var{v}, @var{w})
+## @deftypefnx {} {} streamtube (@dots{}, "@var{options}")
+## @deftypefnx {} {} streamtube (@var{hax}, @dots{})
+## @deftypefnx {} {@var{h} =} streamtube (@dots{})
+## Calculate and display streamtubes.
+##
+## Streamtubes are approximated by connecting circular crossflow areas
+## along a streamline. The expansion of the flow is determined by the local
+## crossflow divergence.
+##
+## The vector field is given by [@var{u}, @var{v}, @var{w}] and is defined over a
+## rectangular grid given by [@var{x}, @var{y}, @var{z}]. The streamtubes start
+## at the seed points [@var{sx}, @var{sy}, @var{sz}].
+##
+## The tubes are colored depending on the local vector field strength.
+##
+## The input parameter @var{options} is a 2D vector of the form
+## [@var{scale}, @var{n}]. The first parameter scales the start radius of
+## the streamtubes (default 1). The second parameter specifies the number of
+## patches used for the streamtube circumference (default 20).
+##
+## Streamtube can be called with a cell array containing precomputed streamline
+## data. To do this, @var{vertices} must be created with the stream3 function.
+## This option is useful if you need to alter the integrator step size or the
+## maximum number of vertices of the streamline.
+##
+## If the first argument @var{hax} is an axes handle, then plot into this axes,
+## rather than the current axes returned by @code{gca}.
+##
+## The optional return value @var{h} is a graphics handle to the patch plot
+## objects created for each streamtube.
+##
+## Example:
+##
+## @example
+## @group
+## [x, y, z] = meshgrid (-1:0.1:1, -1:0.1:1, -3:0.1:0);
+## u = -x / 10 - y;
+## v = x - y / 10;
+## w = - ones (size (x)) / 10;
+## streamtube (x, y, z, u, v, w, 1, 0, 0);
+## @end group
+## @end example
+##
+## @seealso{streamline, stream3}
+##
+## @end deftypefn
+
+## References:
+##
+## @inproceedings{
+##    title = {Visualization of 3-D vector fields - Variations on a stream},
+##    author = {Dave Darmofal and Robert Haimes},
+##    year = {1992}
+## }
+
+function h = streamtube (varargin)
+
+  if (nargin == 0)
+    print_usage ();
+  endif
+
+  [hax, varargin] = __plt_get_axis_arg__ ("streamtube", varargin{:});
+
+  if (isempty (hax))
+    hax = gca ();
+  else
+    hax = hax(1);
+  endif
+
+  options = [];
+  xyz = [];
+  switch (length (varargin))
+    case (0)
+      print_usage ();
+    case (6)
+      [u, v, w, spx, spy, spz] = varargin{:};
+      [m, n, p] = size (u);
+      [x, y, z] = meshgrid (1:n, 1:m, 1:p);
+    case (7)
+      if (iscell (varargin{1}))
+        [xyz, x, y, z, u, v, w] = varargin{:};
+      else
+        [u, v, w, spx, spy, spz, options] = varargin{:};
+        [m, n, p] = size (u);
+        [x, y, z] = meshgrid (1:n, 1:m, 1:p);
+      endif
+    case (8)
+      [xyz, x, y, z, u, v, w, options] = varargin{:};
+    case (9)
+      [x, y, z, u, v, w, spx, spy, spz] = varargin{:};
+    case (10)
+      [x, y, z, u, v, w, spx, spy, spz, options] = varargin{:};
+    otherwise
+      error ("streamtube: unknown input parameter count");
+  endswitch
+
+  scale = 1;
+  n = 20;
+  if (! isempty (options))
+    switch (length (options))
+      case (1)
+        scale = options(1);
+      case (2)
+        scale = options(1);
+        n = options(2);
+      otherwise
+        error ("streamtube: wrong options length");
+    endswitch
+  endif
+
+  if ((! isnumeric (scale)) || (scale <= 0))
+    error ("streamtube: scale error");
+  endif
+  if ((! isnumeric (n)) || (n < 3))
+    error ("streamtube: number of polygons for tube circumference too small");
+  endif
+  if isempty (xyz)
+    xyz = stream3 (x, y, z, u, v, w, spx, spy, spz, 0.5);
+  endif
+
+  div = divergence (x, y, z, u, v, w);
+  vn = sqrt (u.*u + v.*v + w.*w);
+  vmax = max (max (max (vn)));
+  vmin = min (min (min (vn)));
+
+  ## Radius estimator
+  [ny, nx, nz] = size (x);
+  dx = (max (max (max (x))) - min (min (min (x)))) / nx;
+  dy = (max (max (max (y))) - min (min (min (y)))) / ny;
+  dz = (max (max (max (z))) - min (min (min (z)))) / nz;
+  r0 = scale * sqrt (dx * dx + dy * dy + dz * dz);
+
+  h = [];
+  for i = 1:length (xyz)
+
+    sl = xyz{i};
+    [nverts, ~] = size(sl);
+    if (! isempty (sl)) && (nverts > 2)
+
+      divsl = interp3 (x, y, z, div, sl(:, 1), sl(:, 2), sl(:, 3));
+      usl = interp3 (x, y, z, u, sl(:, 1), sl(:, 2), sl(:, 3));
+      vsl = interp3 (x, y, z, v, sl(:, 1), sl(:, 2), sl(:, 3));
+      wsl = interp3 (x, y, z, w, sl(:, 1), sl(:, 2), sl(:, 3));
+      vv = sqrt (usl.*usl + vsl.*vsl + wsl.*wsl);
+
+      tmp = plottube (hax, sl, divsl, vv, vmax, vmin, r0, n);
+      h = [h, tmp];
+
+    endif
+
+  endfor
+
+endfunction
+
+function h = plottube (hax, sl, divsl, vv, vmax, vmin, r0, npoly)
+
+  issingular = find (isnan (divsl), 1, "first");
+  if (! isempty (issingular))
+    maxnverts = issingular - 1;
+  else
+    maxnverts = length (sl);
+  endif
+  if (maxnverts < 3)
+    error ("streamtube: too less data to show");
+  endif
+
+  if (vmax == vmin)
+    colscale = 0.0;
+  else
+    colscale = 1.0 / (vmax - vmin);
+  endif
+
+  phi = linspace (0, 2*pi (), npoly);
+
+  X0 = sl(1, :);
+  X1 = sl(2, :);
+
+  ## 1st rotation axis
+  R = X1 - X0;
+
+  ## Initial radius
+  vold = vv(1);
+  vact = vv(2);
+  ract = r0 * exp (0.5 * divsl(2) * norm (R) / vact) * sqrt (vold / vact);
+  vold = vact;
+  rold = ract;
+
+  ## Guide point and its rotation to create a segment
+  N = get_guide_point (X0, X1);
+  K = ract * N;
+  XS = rotation (R, K, phi) + repmat (X1.', 1, npoly);
+
+  px = zeros (4, npoly * (maxnverts - 2));
+  py = zeros (4, npoly * (maxnverts - 2));
+  pz = zeros (4, npoly * (maxnverts - 2));
+  pc = zeros (4, npoly * (maxnverts - 2));
+
+  for j = 3:maxnverts
+
+    KK = K;
+    X0 = X1;
+    X1 = sl(j, :);
+    R = X1 - X0;
+
+    ## Tube radius
+    vact = vv(j);
+    ract = rold * exp (0.5 * divsl(j) * norm (R) / vact) * sqrt (vold / vact);
+    vold = vact;
+    rold = ract;
+
+    ## Project K onto R and get the difference in order to calculate the next
+    ## guiding point
+    Kp = KK - R * dot (KK, R) / (norm (R)^2);
+    K = ract * Kp / norm (Kp);
+
+    XSold = XS;
+    ## Rotate the guiding point around R and collect patch vertices
+    XS = rotation (R, K, phi) + repmat (X1.', 1, npoly);
+    [tx, ty, tz] = segment_patch_data (XS, XSold);
+
+    from = (j - 3) * npoly + 1;
+    to = (j + 1 - 3) * npoly;
+    px(:, from:to) = tx;
+    py(:, from:to) = ty;
+    pz(:, from:to) = tz;
+    pc(:, from:to) = colscale * (vact - vmin) * ones (4, npoly);
+
+  endfor
+
+  h = patch (hax, px, py, pz, pc);
+
+endfunction
+
+## Find N orthogonal to (X1 - X0)
+function N = get_guide_point (X0, X1)
+
+  S = X1 - X0;
+
+  if ((S(3) == 0) && (S(1) == -S(2)))
+    N = [- S(2) - S(3), S(1), S(1)];
+  else
+    N = [S(3), S(3), - S(1) - S(2)];
+  endif
+
+  N = N / norm(N);
+
+endfunction
+
+## Create patch data to draw a segment
+## from starting point XS to ending point XE
+function [px, py, pz] = segment_patch_data (XS, XE)
+
+  [~, npoly] = size (XS);
+
+  px = zeros (4, npoly);
+  py = zeros (4, npoly);
+  pz = zeros (4, npoly);
+
+  px(1, :) = XS(1, :);
+  px(2, :) = XE(1, :);
+  px(3, :) = [XE(1, 2:end), XE(1, 1)];
+  px(4, :) = [XS(1, 2:end), XS(1, 1)];
+
+  py(1, :) = XS(2, :);
+  py(2, :) = XE(2, :);
+  py(3, :) = [XE(2, 2:end), XE(2, 1)];
+  py(4, :) = [XS(2, 2:end), XS(2, 1)];
+
+  pz(1, :) = XS(3, :);
+  pz(2, :) = XE(3, :);
+  pz(3, :) = [XE(3, 2:end), XE(3, 1)];
+  pz(4, :) = [XS(3, 2:end), XS(3, 1)];
+
+endfunction
+
+## A: Axis of rotation
+## X: Guiding point
+## phi: Angles
+## Y: Rotated points
+function Y = rotation (A, X, phi)
+
+  U = A / norm (A);
+  cp = cos (phi);
+  sp = sin (phi);
+
+  ux = U(1);
+  uy = U(2);
+  uz = U(3);
+
+  Y(1, :) = X(1) * (cp + ux * ux * (1 - cp)) + ...
+            X(2) * (ux * uy * (1 - cp) - uz * sp) + ...
+            X(3) * (ux * uz * (1 - cp) + uy * sp);
+
+  Y(2, :) = X(1) * (uy * ux * (1 - cp) + uz * sp) + ...
+            X(2) * (cp + uy * uy * (1 - cp)) + ...
+            X(3) * (uy * uz * (1 - cp) - ux * sp);
+
+  Y(3, :) = X(1) * (uz * ux * (1 - cp) - uy * sp) + ...
+            X(2) * (uz * uy * (1 - cp) + ux * sp) + ...
+            X(3) * (cp + uz * uz * (1 - cp));
+
+endfunction
+
+%!demo
+%! clf;
+%! [x, y, z] = meshgrid (-1:0.1:1, -1:0.1:1, -3.5:0.1:0);
+%! a = 0.1;
+%! b = 0.1;
+%! u = - a * x - y;
+%! v = x - a * y;
+%! w = - b * ones (size (x));
+%! sx = 1.0;
+%! sy = 0.0;
+%! sz = 0.0;
+%! streamtube (x, y, z, u, v, w, sx, sy, sz, [1.2, 30]);
+%! colormap (jet);
+%! shading interp;
+%! view ([-47, 24]);
+%! camlight ();
+%! lighting gouraud;
+%! grid on;
+%! view (3);
+%! axis equal;
+%! set (gca, "cameraviewanglemode", "manual");
+%! title ("Spiral Sink");
+
+%!demo
+%! clf;
+%! [x, y, z] = meshgrid (-2:0.5:2);
+%! t = sqrt (1.0./(x.^2 + y.^2 + z.^2)).^3;
+%! u = - x.*t;
+%! v = - y.*t;
+%! w = - z.*t;
+%! [sx, sy, sz] = meshgrid (-2:4:2);
+%! xyz = stream3 (x, y, z, u, v, w, sx, sy, sz, [0.1, 60]);
+%! streamtube (xyz, x, y, z, u, v, w, [2, 50]);
+%! colormap (jet);
+%! shading interp;
+%! view ([-47, 24]);
+%! camlight ();
+%! lighting gouraud;
+%! grid on;
+%! view (3);
+%! axis equal;
+%! set (gca, "cameraviewanglemode", "manual");
+%! title ("Integration Towards Sink");
+
+%!error streamtube ()
+