Complex plots - 2D Graphics

Skip to content
Back to top
View this page
Edit this page

# Complex plots

AUTHORS:

- Robert Bradshaw (2009): initial version

- David Lowry-Duda (2022): incorporate matplotlib colormaps

class sage.plot.complex_plot.ComplexPlot(rgb_data, x_range, y_range, options)

  Bases: `GraphicPrimitive`

  The GraphicsPrimitive to display complex functions in using the domain coloring method

  INPUT:

  - `rgb_data` – an array of colored points to be plotted

  - `x_range` – a minimum and maximum x value for the plot

  - `y_range` – a minimum and maximum y value for the plot

  get_minmax_data()

    Return a dictionary with the bounding box data.

    EXAMPLES:

    Sage

        sage: p = complex_plot(lambda z: z, (-1, 2), (-3, 4))
        sage: sorted(p.get_minmax_data().items())
        [('xmax', 2.0), ('xmin', -1.0), ('ymax', 4.0), ('ymin', -3.0)]
        sage: p = complex_plot(lambda z: z, (1, 2), (3, 4))
        sage: sorted(p.get_minmax_data().items())
        [('xmax', 2.0), ('xmin', 1.0), ('ymax', 4.0), ('ymin', 3.0)]

    Python

        >>> from sage.all import *
        >>> p = complex_plot(lambda z: z, (-Integer(1), Integer(2)), (-Integer(3), Integer(4)))
        >>> sorted(p.get_minmax_data().items())
        [('xmax', 2.0), ('xmin', -1.0), ('ymax', 4.0), ('ymin', -3.0)]
        >>> p = complex_plot(lambda z: z, (Integer(1), Integer(2)), (Integer(3), Integer(4)))
        >>> sorted(p.get_minmax_data().items())
        [('xmax', 2.0), ('xmin', 1.0), ('ymax', 4.0), ('ymin', 3.0)]

sage.plot.complex_plot.add_contours_to_rgb(rgb, delta, dark_rate=0.5)

  Return an rgb array from given array of \((r, g, b)\) and \((delta)\).

  Each input \((r, g, b)\) is modified by `delta` to be lighter or darker depending on the size of `delta`. Negative `delta` values darken the color, while positive `delta` values lighten the pixel.

  We assume that the `delta` values come from a function like `sage.plot.complex_plot.mag_to_lightness()`, which maps magnitudes to the range \([-1, +1]\).

  INPUT:

  - `rgb` – a grid of length 3 tuples \((r, g, b)\), as an \(N \times M \times 3\) numpy array

  - `delta` – a grid of values as an \(N \times M\) numpy array; these represent how much to change the lightness of each \((r, g, b)\). Values should be in \([-1, 1]\).

  - `dark_rate` – a positive number (default: 0.5); affects how strongly visible the contours appear

  OUTPUT:

  An \(N \times M \times 3\) floating point Numpy array `X`, where `X[i,j]` is an (r, g, b) tuple.

  See also

  - `sage.plot.complex_plot.complex_to_rgb()`,

  - `sage.plot.complex_plot.add_lightness_smoothing_to_rgb()`

  ALGORITHM:

  Each pixel and lightness-delta is mapped from \((r, g, b, delta) \mapsto (h, l, s, delta)\) using the standard RGB-to-HLS formula.

  Then the lightness is adjusted via \(l \mapsto l' = l + 0.5 \cdot delta\).

  Finally map \((h, l', s) \mapsto (r, g, b)\) using the standard HLS-to-RGB formula.

  EXAMPLES:

  Sage

      sage: # needs numpy
      sage: import numpy as np
      sage: from sage.plot.complex_plot import add_contours_to_rgb
      sage: add_contours_to_rgb(np.array([[[0, 0.25, 0.5]]]),  # abs tol 1e-4
      ....:                     np.array([[0.75]]))
      array([[[0.25 , 0.625, 1.   ]]])
      sage: add_contours_to_rgb(np.array([[[0, 0, 0]]]),  # abs tol 1e-4
      ....:                     np.array([[1]]))
      array([[[0.5, 0.5, 0.5]]])
      sage: add_contours_to_rgb(np.array([[[1, 1, 1]]]),  # abs tol 1e-4
      ....:                     np.array([[-0.5]]))
      array([[[0.75, 0.75, 0.75]]])

  Python

      >>> from sage.all import *
      >>> # needs numpy
      >>> import numpy as np
      >>> from sage.plot.complex_plot import add_contours_to_rgb
      >>> add_contours_to_rgb(np.array([[[Integer(0), RealNumber('0.25'), RealNumber('0.5')]]]),  # abs tol 1e-4
      ...                     np.array([[RealNumber('0.75')]]))
      array([[[0.25 , 0.625, 1.   ]]])
      >>> add_contours_to_rgb(np.array([[[Integer(0), Integer(0), Integer(0)]]]),  # abs tol 1e-4
      ...                     np.array([[Integer(1)]]))
      array([[[0.5, 0.5, 0.5]]])
      >>> add_contours_to_rgb(np.array([[[Integer(1), Integer(1), Integer(1)]]]),  # abs tol 1e-4
      ...                     np.array([[-RealNumber('0.5')]]))
      array([[[0.75, 0.75, 0.75]]])

  Raising `dark_rate` leads to bigger adjustments:

  Sage

      sage: add_contours_to_rgb(np.array([[[0.5, 0.5, 0.5]]]),  # abs tol 1e-4        # needs numpy
      ....:                     np.array([[0.5]]), dark_rate=0.1)
      array([[[0.55, 0.55, 0.55]]])
      sage: add_contours_to_rgb(np.array([[[0.5, 0.5, 0.5]]]),  # abs tol 1e-4        # needs numpy
      ....:                     np.array([[0.5]]), dark_rate=0.5)
      array([[[0.75, 0.75, 0.75]]])

  [Python]

      >>> from sage.all import *
      >>> add_contours_to_rgb(np.array([[[RealNumber('0.5'), RealNumber('0.5'), RealNumber('0.5')]]]),  # abs tol 1e-4        # needs numpy
      ...                     np.array([[RealNumber('0.5')]]), dark_rate=RealNumber('0.1'))
      array([[[0.55, 0.55, 0.55]]])
      >>> add_contours_to_rgb(np.array([[[RealNumber('0.5'), RealNumber('0.5'), RealNumber('0.5')]]]),  # abs tol 1e-4        # needs numpy
      ...                     np.array([[RealNumber('0.5')]]), dark_rate=RealNumber('0.5'))
      array([[[0.75, 0.75, 0.75]]])

sage.plot.complex_plot.add_lightness_smoothing_to_rgb(rgb, delta)

  Return an rgb array from given array of colors and lightness adjustments.

  This smoothly adds lightness from black (when `delta` is \(-1\)) to white (when `delta` is \(1\)).

  Each input \((r, g, b)\) is modified by `delta` to be lighter or darker depending on the size of `delta`. When `delta` is \(-1\), the output is black. When `delta` is \(+1\), the output is white. Colors piecewise-linearly vary from black to the initial \((r, g, b)\) to white.

  We assume that the `delta` values come from a function like `sage.plot.complex_plot.mag_to_lightness()`, which maps magnitudes to the range \([-1, +1]\).

  INPUT:

  - `rgb` – a grid of length 3 tuples \((r, g, b)\), as an \(N \times M \times 3\) numpy array

  - `delta` – a grid of values as an \(N \times M\) numpy array; these represent how much to change the lightness of each \((r, g, b)\). Values should be in \([-1, 1]\).

  OUTPUT:

  An \(N \times M \times 3\) floating point Numpy array `X`, where `X[i,j]` is an (r, g, b) tuple.

  See also

  - `sage.plot.complex_plot.complex_to_rgb()`

  - `sage.plot.complex_plot.add_contours_to_rgb()`

  EXAMPLES:

  We can call this on grids of values:

  Sage

      sage: # needs numpy
      sage: import numpy as np
      sage: from sage.plot.complex_plot import add_lightness_smoothing_to_rgb
      sage: add_lightness_smoothing_to_rgb(  # abs tol 1e-4
      ....:     np.array([[[0, 0.25, 0.5]]]), np.array([[0.75]]))
      array([[[0.75  , 0.8125, 0.875 ]]])
      sage: add_lightness_smoothing_to_rgb(  # abs tol 1e-4
      ....:     np.array([[[0, 0.25, 0.5]]]), np.array([[0.75]]))
      array([[[0.75  , 0.8125, 0.875 ]]])

  Python

      >>> from sage.all import *
      >>> # needs numpy
      >>> import numpy as np
      >>> from sage.plot.complex_plot import add_lightness_smoothing_to_rgb
      >>> add_lightness_smoothing_to_rgb(  # abs tol 1e-4
      ...     np.array([[[Integer(0), RealNumber('0.25'), RealNumber('0.5')]]]), np.array([[RealNumber('0.75')]]))
      array([[[0.75  , 0.8125, 0.875 ]]])
      >>> add_lightness_smoothing_to_rgb(  # abs tol 1e-4
      ...     np.array([[[Integer(0), RealNumber('0.25'), RealNumber('0.5')]]]), np.array([[RealNumber('0.75')]]))
      array([[[0.75  , 0.8125, 0.875 ]]])

sage.plot.complex_plot.complex_plot(f, x_range, y_range, contoured=False, tiled=False, cmap=None, contour_type='logarithmic', contour_base=None, dark_rate=0.5, nphases=10, plot_points=100, interpolation='catrom', **options)

  `complex_plot` takes a complex function of one variable, \(f(z)\) and plots output of the function over the specified `x_range` and `y_range` as demonstrated below. The magnitude of the output is indicated by the brightness and the argument is represented by the hue.

  By default, zero magnitude corresponds to black output, infinite magnitude corresponds to white output. The options `contoured`, `tiled`, and `cmap` affect the output.

  `complex_plot(f, (xmin, xmax), (ymin, ymax), contoured, tiled, cmap, ...)`

  INPUT:

  - `f` – a function of a single complex value \(x + iy\)

  - `(xmin, xmax)` – 2-tuple, the range of `x` values

  - `(ymin, ymax)` – 2-tuple, the range of `y` values

  - `cmap` – `None`, or the string name of a matplotlib colormap, or an instance of a matplotlib Colormap, or the special string `'matplotlib'` (default: `None`); if `None`, then hues are chosen from a standard color wheel, cycling from red to yellow to blue. If `matplotlib`, then hues are chosen from a preset matplotlib colormap.

  The following named parameter inputs can be used to add contours and adjust their distribution:

  - `contoured` – boolean (default: `False`); causes the magnitude to be indicated by logarithmically spaced ‘contours’. The magnitude along one contour is either twice or half the magnitude along adjacent contours.

  - `dark_rate` – a positive number (default: 0.5); affects how quickly magnitudes affect how light/dark the image is. When there are contours, this affects how visible each contour is. Large values (near 1.0) have very strong, immediate effects, while small values (near 0.0) have gradual effects.

  - `tiled` – boolean (default: `False`); causes the magnitude to be indicated by logarithmically spaced ‘contours’ as in `contoured`, and in addition for there to be \(10\) evenly spaced phase contours.

  - `nphases` – positive integer (default: 10); when `tiled=True`, this is the number of divisions the phase is divided into

  - `contour_type` – either `'logarithmic'`, or `'linear'` (default: `'logarithmic'`); causes added contours to be of given type when `contoured=True`.

  - `contour_base` – positive integer; when `contour_type` is `'logarithmic'`, this sets logarithmic contours at multiples of `contour_base` apart. When `contour_type` is `'linear'`, this sets contours at distances of `contour_base` apart. If `None`, then a default is chosen depending on `contour_type`.

  The following inputs may also be passed in as named parameters:

  - `plot_points` – integer (default: 100); number of points to plot in each direction of the grid

  - `interpolation` – string (default: `'catrom'`); the interpolation method to use: `'bilinear'`, `'bicubic'`, `'spline16'`, `'spline36'`, `'quadric'`, `'gaussian'`, `'sinc'`, `'bessel'`, `'mitchell'`, `'lanczos'`, `'catrom'`, `'hermite'`, `'hanning'`, `'hamming'`, `'kaiser'`

  Any additional parameters will be passed to `show()`, as long as they’re valid.

  Note

  Matplotlib colormaps can be chosen or customized to cater to different types of vision. The colormaps ‘cividis’ and ‘viridis’ in matplotlib are designed to be perceptually uniform to a broader audience. The colormap ‘turbo’ is similar to the default but with more even contrast. See [NAR2018] for more information about colormap choice for scientific visualization.

  EXAMPLES:

  Here we plot a couple of simple functions:

  Sage

      sage: complex_plot(sqrt(x), (-5, 5), (-5, 5))                                   # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> complex_plot(sqrt(x), (-Integer(5), Integer(5)), (-Integer(5), Integer(5)))                                   # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Sage

      sage: complex_plot(sin(x), (-5, 5), (-5, 5))                                    # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> complex_plot(sin(x), (-Integer(5), Integer(5)), (-Integer(5), Integer(5)))                                    # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Sage

      sage: complex_plot(log(x), (-10, 10), (-10, 10))                                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> complex_plot(log(x), (-Integer(10), Integer(10)), (-Integer(10), Integer(10)))                                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Sage

      sage: complex_plot(exp(x), (-10, 10), (-10, 10))                                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> complex_plot(exp(x), (-Integer(10), Integer(10)), (-Integer(10), Integer(10)))                                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  A plot with a different choice of colormap:

  Sage

      sage: complex_plot(exp(x), (-10, 10), (-10, 10), cmap='viridis')                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> complex_plot(exp(x), (-Integer(10), Integer(10)), (-Integer(10), Integer(10)), cmap='viridis')                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  A function with some nice zeros and a pole:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3))                                         # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)))                                         # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  The same function as above, but with contours. Contours render poorly with few plot points, so we use 300 here:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3), plot_points=300, contoured=True)        # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)), plot_points=Integer(300), contoured=True)        # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  The same function as above, but tiled and with the plasma colormap:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3),                                         # needs sage.symbolic
      ....:              plot_points=300, tiled=True, cmap='plasma')
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)),                                         # needs sage.symbolic
      ...              plot_points=Integer(300), tiled=True, cmap='plasma')
      Graphics object consisting of 1 graphics primitive

  When using `tiled=True`, the number of phase subdivisions can be controlled by adjusting `nphases`. We make the same plot with fewer tilings:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3), plot_points=300,                        # needs sage.symbolic
      ....:              tiled=True, nphases=5, cmap='plasma')
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)), plot_points=Integer(300),                        # needs sage.symbolic
      ...              tiled=True, nphases=Integer(5), cmap='plasma')
      Graphics object consisting of 1 graphics primitive

  It is also possible to use linear contours. We plot the same function above on an inset, setting contours to appear \(1\) apart:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (0, 1), (0, 1), plot_points=300,                          # needs sage.symbolic
      ....:              contoured=True, contour_type='linear', contour_base=1)
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (Integer(0), Integer(1)), (Integer(0), Integer(1)), plot_points=Integer(300),                          # needs sage.symbolic
      ...              contoured=True, contour_type='linear', contour_base=Integer(1))
      Graphics object consisting of 1 graphics primitive

  Note that tightly spaced contours can lead to Moiré patterns and aliasing problems. For example:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3), plot_points=300,                        # needs sage.symbolic
      ....:              contoured=True, contour_type='linear', contour_base=1)
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)), plot_points=Integer(300),                        # needs sage.symbolic
      ...              contoured=True, contour_type='linear', contour_base=Integer(1))
      Graphics object consisting of 1 graphics primitive

  When choosing colormaps, cyclic colormaps such as twilight or hsv might be considered more appropriate for showing changes in phase without sharp color contrasts:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3), plot_points=300, cmap='twilight')       # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)), plot_points=Integer(300), cmap='twilight')       # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Passing matplotlib as the colormap gives a special colormap that is similar to the default:

  Sage

      sage: f(z) = z^5 + z - 1 + 1/z                                                  # needs sage.symbolic
      sage: complex_plot(f, (-3, 3), (-3, 3),                                         # needs sage.symbolic
      ....:              plot_points=300, contoured=True, cmap='matplotlib')
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> __tmp__=var("z"); f = symbolic_expression(z**Integer(5) + z - Integer(1) + Integer(1)/z                                                  ).function(z)# needs sage.symbolic
      >>> complex_plot(f, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)),                                         # needs sage.symbolic
      ...              plot_points=Integer(300), contoured=True, cmap='matplotlib')
      Graphics object consisting of 1 graphics primitive

  Here is the identity, useful for seeing what values map to what colors:

  Sage

      sage: complex_plot(lambda z: z, (-3, 3), (-3, 3))                               # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> complex_plot(lambda z: z, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)))                               # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  The Riemann Zeta function:

  Sage

      sage: complex_plot(zeta, (-30,30), (-30,30))                                    # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> complex_plot(zeta, (-Integer(30),Integer(30)), (-Integer(30),Integer(30)))                                    # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  For advanced usage, it is possible to tweak many parameters. Increasing `dark_rate` will make regions become darker/lighter faster when there are no contours:

  Sage

      sage: complex_plot(zeta, (-30, 30), (-30, 30), dark_rate=1.0)                   # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> complex_plot(zeta, (-Integer(30), Integer(30)), (-Integer(30), Integer(30)), dark_rate=RealNumber('1.0'))                   # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Decreasing `dark_rate` has the opposite effect. When there are contours, adjust `dark_rate` affects how visible contours are. Compare:

  Sage

      sage: complex_plot(zeta, (-1, 9), (10, 20), plot_points=200,    # long time, needs sage.symbolic
      ....:              contoured=True, cmap='twilight', dark_rate=0.2)
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> complex_plot(zeta, (-Integer(1), Integer(9)), (Integer(10), Integer(20)), plot_points=Integer(200),    # long time, needs sage.symbolic
      ...              contoured=True, cmap='twilight', dark_rate=RealNumber('0.2'))
      Graphics object consisting of 1 graphics primitive

  and:

  Sage

      sage: complex_plot(zeta, (-1, 9), (10, 20), plot_points=200,    # long time, needs sage.symbolic
      ....:              contoured=True, cmap='twilight', dark_rate=0.75)
      Graphics object consisting of 1 graphics primitive

  [Python]

      >>> from sage.all import *
      >>> complex_plot(zeta, (-Integer(1), Integer(9)), (Integer(10), Integer(20)), plot_points=Integer(200),    # long time, needs sage.symbolic
      ...              contoured=True, cmap='twilight', dark_rate=RealNumber('0.75'))
      Graphics object consisting of 1 graphics primitive

  In practice, different values of `dark_rate` will work well with different colormaps.

  Extra options will get passed on to show(), as long as they are valid:

  Sage

      sage: complex_plot(lambda z: z, (-3, 3), (-3, 3), figsize=[1,1])                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Python

      >>> from sage.all import *
      >>> complex_plot(lambda z: z, (-Integer(3), Integer(3)), (-Integer(3), Integer(3)), figsize=[Integer(1),Integer(1)])                # needs sage.symbolic
      Graphics object consisting of 1 graphics primitive

  Sage

      sage: complex_plot(lambda z: z, (-3, 3), (-3, 3)).show(figsize=[1,1])  # These are equivalent                   # needs sage.symbolic

  [Python]

      >>> from sage.all import *
      >>> complex_plot(lambda z: z, (-Integer(3), Integer(3)), (-Integer(3), Integer(3))).show(figsize=[Integer(1),Integer(1)])  # These are equivalent                   # needs sage.symbolic

  REFERENCES:

  Plotting complex functions with colormaps follows the strategy from [LD2021] and incorporates contour techniques described in [WegSem2010].

sage.plot.complex_plot.complex_to_cmap_rgb(z_values, cmap='turbo', contoured=False, tiled=False, contour_type='logarithmic', contour_base=None, dark_rate=0.5, nphases=10)

  Convert a grid of complex numbers to a grid of rgb values using colors taken from given colormap.

  INPUT:

  - `z_values` – a grid of complex numbers, as a list of lists

  - `cmap` – the string name of a matplotlib colormap, or an instance of a matplotlib Colormap (default: `'turbo'`)

  - `contoured` – boolean (default: `False`); causes magnitude to be indicated through contour-like adjustments to lightness

  - `tiled` – boolean (default: `False`); causes magnitude and argument to be indicated through contour-like adjustments to lightness

  - `nphases` – positive integer (default: 10); when `tiled=True`, this is the number of divisions the phase is divided into

  - `contour_type` – either `'logarithmic'`, or `'linear'` (default: `'logarithmic'`); causes added contours to be of given type when `contoured=True`.

  - `contour_base` – positive integer; when `contour_type` is `'logarithmic'`, this sets logarithmic contours at multiples of `contour_base` apart. When `contour_type` is `'linear'`, this sets contours at distances of `contour_base` apart. If `None`, then a default is chosen depending on `contour_type`.

  - `dark_rate` – a positive number (default: 0.5); affects how quickly magnitudes affect how light/dark the image is. When there are contours, this affects how visible each contour is. Large values (near 1.0) have very strong, immediate effects, while small values (near 0.0) have gradual effects.

  OUTPUT:

  An \(N \times M \times 3\) floating point Numpy array `X`, where `X[i,j]` is an (r, g, b) tuple.

  See also

  `sage.plot.complex_plot.complex_to_rgb()`

  EXAMPLES:

  We can call this on grids of complex numbers:

  Sage

      sage: from sage.plot.complex_plot import complex_to_cmap_rgb
      sage: complex_to_cmap_rgb([[0, 1, 1000]])  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.49669808, 0.76400071, 0.18024425],
              [0.87320419, 0.99643856, 0.72730967]]])
      sage: complex_to_cmap_rgb([[0, 1, 1000]], cmap='viridis')  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.0984475 , 0.4375291 , 0.42487821],
              [0.68959896, 0.84592555, 0.84009311]]])

  Python

      >>> from sage.all import *
      >>> from sage.plot.complex_plot import complex_to_cmap_rgb
      >>> complex_to_cmap_rgb([[Integer(0), Integer(1), Integer(1000)]])  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.49669808, 0.76400071, 0.18024425],
              [0.87320419, 0.99643856, 0.72730967]]])
      >>> complex_to_cmap_rgb([[Integer(0), Integer(1), Integer(1000)]], cmap='viridis')  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.0984475 , 0.4375291 , 0.42487821],
              [0.68959896, 0.84592555, 0.84009311]]])

  We can change contour types and the distances between contours:

  Sage

      sage: complex_to_cmap_rgb([[0, 1 + 1j, 3 + 4j]], contoured=True,  # abs tol 1e-4
      ....:                     contour_type='logarithmic', contour_base=3)
      array([[[0.64362   , 0.98999   , 0.23356   ],
              [0.93239357, 0.81063338, 0.21955399],
              [0.95647342, 0.74861225, 0.14963982]]])
      sage: complex_to_cmap_rgb([[0, 1 + 1j, 3 + 4j]], cmap='turbo',   # abs tol 1e-4
      ....:                     contoured=True, contour_type='linear', contour_base=3)
      array([[[0.71246796, 0.9919238 , 0.3816262 ],
              [0.92617785, 0.79322304, 0.14779989],
              [0.95156284, 0.72025117, 0.05370383]]])

  [Python]

      >>> from sage.all import *
      >>> complex_to_cmap_rgb([[Integer(0), Integer(1) + ComplexNumber(0, '1'), Integer(3) + ComplexNumber(0, '4')]], contoured=True,  # abs tol 1e-4
      ...                     contour_type='logarithmic', contour_base=Integer(3))
      array([[[0.64362   , 0.98999   , 0.23356   ],
              [0.93239357, 0.81063338, 0.21955399],
              [0.95647342, 0.74861225, 0.14963982]]])
      >>> complex_to_cmap_rgb([[Integer(0), Integer(1) + ComplexNumber(0, '1'), Integer(3) + ComplexNumber(0, '4')]], cmap='turbo',   # abs tol 1e-4
      ...                     contoured=True, contour_type='linear', contour_base=Integer(3))
      array([[[0.71246796, 0.9919238 , 0.3816262 ],
              [0.92617785, 0.79322304, 0.14779989],
              [0.95156284, 0.72025117, 0.05370383]]])

  We see that changing `dark_rate` affects how visible contours are. In this example, we set `contour_base=5` and note that the points \(0\) and \(1 + i\) are far away from contours, but \(2.9 + 4i\) is near (and just below) a contour. Raising `dark_rate` should have strong effects on the last coloration and weaker effects on the others:

  Sage

      sage: complex_to_cmap_rgb([[0, 1 + 1j, 2.9 + 4j]], cmap='turbo',  # abs tol 1e-4
      ....:                     contoured=True, dark_rate=0.05, contour_base=5)
      array([[[0.64362   , 0.98999   , 0.23356   ],
              [0.93334746, 0.81330523, 0.23056563],
              [0.96357185, 0.75337736, 0.19440913]]])
      sage: complex_to_cmap_rgb([[0, 1 + 1j, 2.9 + 4j]], cmap='turbo',  # abs tol 1e-4
      ....:                     contoured=True, dark_rate=0.85, contour_base=5)
      array([[[0.64362   , 0.98999   , 0.23356   ],
              [0.93874682, 0.82842892, 0.29289564],
              [0.57778954, 0.42703289, 0.02612716]]])

  Python

      >>> from sage.all import *
      >>> complex_to_cmap_rgb([[Integer(0), Integer(1) + ComplexNumber(0, '1'), RealNumber('2.9') + ComplexNumber(0, '4')]], cmap='turbo',  # abs tol 1e-4
      ...                     contoured=True, dark_rate=RealNumber('0.05'), contour_base=Integer(5))
      array([[[0.64362   , 0.98999   , 0.23356   ],
              [0.93334746, 0.81330523, 0.23056563],
              [0.96357185, 0.75337736, 0.19440913]]])
      >>> complex_to_cmap_rgb([[Integer(0), Integer(1) + ComplexNumber(0, '1'), RealNumber('2.9') + ComplexNumber(0, '4')]], cmap='turbo',  # abs tol 1e-4
      ...                     contoured=True, dark_rate=RealNumber('0.85'), contour_base=Integer(5))
      array([[[0.64362   , 0.98999   , 0.23356   ],
              [0.93874682, 0.82842892, 0.29289564],
              [0.57778954, 0.42703289, 0.02612716]]])

sage.plot.complex_plot.complex_to_rgb(z_values, contoured=False, tiled=False, contour_type='logarithmic', contour_base=None, dark_rate=0.5, nphases=10)

  Convert a grid of complex numbers to a grid of rgb values using a default choice of colors.

  INPUT:

  - `z_values` – a grid of complex numbers, as a list of lists

  - `contoured` – boolean (default: `False`); causes magnitude to be indicated through contour-like adjustments to lightness

  - `tiled` – boolean (default: `False`); causes magnitude and argument to be indicated through contour-like adjustments to lightness

  - `nphases` – positive integer (default: 10); when `tiled=True`, this is the number of divisions the phase is divided into

  - `contour_type` – either `'logarithmic'`, or `'linear'` (default: `'logarithmic'`); causes added contours to be of given type when `contoured=True`.

  - `contour_base` – positive integer; when `contour_type` is `'logarithmic'`, this sets logarithmic contours at multiples of `contour_base` apart. When `contour_type` is `'linear'`, this sets contours at distances of `contour_base` apart. If `None`, then a default is chosen depending on `contour_type`.

  - `dark_rate` – a positive number (default: 0.5); affects how quickly magnitudes affect how light/dark the image is. When there are contours, this affects how visible each contour is. Large values (near \(1.0\)) have very strong, immediate effects, while small values (near \(0.0\)) have gradual effects.

  OUTPUT:

  An \(N \times M \times 3\) floating point Numpy array `X`, where `X[i,j]` is an (r,g,b) tuple.

  See also

  `sage.plot.complex_plot.complex_to_cmap_rgb()`

  EXAMPLES:

  We can call this on grids of complex numbers:

  Sage

      sage: from sage.plot.complex_plot import complex_to_rgb
      sage: complex_to_rgb([[0, 1, 1000]])  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.77172568, 0.        , 0.        ],
              [1.        , 0.64421177, 0.64421177]]])
      sage: complex_to_rgb([[0, 1j, 1000j]])  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.38586284, 0.77172568, 0.        ],
              [0.82210588, 1.        , 0.64421177]]])
      sage: complex_to_rgb([[0, 1, 1000]], contoured=True)   # abs tol 1e-4
      array([[[1.        , 0.        , 0.        ],
              [1.        , 0.15      , 0.15      ],
              [0.66710786, 0.        , 0.        ]]])
      sage: complex_to_rgb([[0, 1, 1000]], tiled=True)   # abs tol 1e-4
      array([[[1.        , 0.        , 0.        ],
              [1.        , 0.15      , 0.15      ],
              [0.90855393, 0.        , 0.        ]]])

  Python

      >>> from sage.all import *
      >>> from sage.plot.complex_plot import complex_to_rgb
      >>> complex_to_rgb([[Integer(0), Integer(1), Integer(1000)]])  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.77172568, 0.        , 0.        ],
              [1.        , 0.64421177, 0.64421177]]])
      >>> complex_to_rgb([[Integer(0), ComplexNumber(0, '1'), ComplexNumber(0, '1000')]])  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.38586284, 0.77172568, 0.        ],
              [0.82210588, 1.        , 0.64421177]]])
      >>> complex_to_rgb([[Integer(0), Integer(1), Integer(1000)]], contoured=True)   # abs tol 1e-4
      array([[[1.        , 0.        , 0.        ],
              [1.        , 0.15      , 0.15      ],
              [0.66710786, 0.        , 0.        ]]])
      >>> complex_to_rgb([[Integer(0), Integer(1), Integer(1000)]], tiled=True)   # abs tol 1e-4
      array([[[1.        , 0.        , 0.        ],
              [1.        , 0.15      , 0.15      ],
              [0.90855393, 0.        , 0.        ]]])

  We can change contour types and the distances between contours:

  Sage

      sage: complex_to_rgb([[0, 1 + 1j, 3 + 4j]],  # abs tol 1e-4
      ....:                contoured=True, contour_type='logarithmic', contour_base=3)
      array([[[1.        , 0.        , 0.        ],
              [0.99226756, 0.74420067, 0.        ],
              [0.91751324, 0.81245954, 0.        ]]])
      sage: complex_to_rgb([[0, 1 + 1j, 3 + 4j]],  # abs tol 1e-4
      ....:                contoured=True, contour_type='linear', contour_base=3)
      array([[[1.        , 0.15      , 0.15      ],
              [0.91429774, 0.6857233 , 0.        ],
              [0.81666667, 0.72315973, 0.        ]]])

  [Python]

      >>> from sage.all import *
      >>> complex_to_rgb([[Integer(0), Integer(1) + ComplexNumber(0, '1'), Integer(3) + ComplexNumber(0, '4')]],  # abs tol 1e-4
      ...                contoured=True, contour_type='logarithmic', contour_base=Integer(3))
      array([[[1.        , 0.        , 0.        ],
              [0.99226756, 0.74420067, 0.        ],
              [0.91751324, 0.81245954, 0.        ]]])
      >>> complex_to_rgb([[Integer(0), Integer(1) + ComplexNumber(0, '1'), Integer(3) + ComplexNumber(0, '4')]],  # abs tol 1e-4
      ...                contoured=True, contour_type='linear', contour_base=Integer(3))
      array([[[1.        , 0.15      , 0.15      ],
              [0.91429774, 0.6857233 , 0.        ],
              [0.81666667, 0.72315973, 0.        ]]])

  Lowering `dark_rate` causes colors to go to black more slowly near \(0\):

  Sage

      sage: complex_to_rgb([[0, 0.5, 1]], dark_rate=0.4)  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.65393731, 0.        , 0.        ],
              [0.77172568, 0.        , 0.        ]]])
      sage: complex_to_rgb([[0, 0.5, 1]], dark_rate=0.2)  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.71235886, 0.        , 0.        ],
              [0.77172568, 0.        , 0.        ]]])

  Python

      >>> from sage.all import *
      >>> complex_to_rgb([[Integer(0), RealNumber('0.5'), Integer(1)]], dark_rate=RealNumber('0.4'))  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.65393731, 0.        , 0.        ],
              [0.77172568, 0.        , 0.        ]]])
      >>> complex_to_rgb([[Integer(0), RealNumber('0.5'), Integer(1)]], dark_rate=RealNumber('0.2'))  # abs tol 1e-4
      array([[[0.        , 0.        , 0.        ],
              [0.71235886, 0.        , 0.        ],
              [0.77172568, 0.        , 0.        ]]])

sage.plot.complex_plot.hls_to_rgb(hls)

  Convert array of hls values (each in the range \([0, 1]\)) to a numpy array of rgb values (each in the range \([0, 1]\))

  INPUT:

  - `hls` – an \(N \times 3\) array of floats in the range \([0, 1]\); the hls values at each point. (Note that the input can actually be of any dimension, such as \(N \times M \times 3\), as long as the last dimension has length \(3\)).

  OUTPUT:

  An \(N \times 3\) Numpy array of floats in the range \([0, 1]\), with the same dimensions as the input array.

  See also

  `sage.plot.complex_plot.rgb_to_hls()`

  EXAMPLES:

  We convert a row of floats and verify that we can convert back using `rgb_to_hls`:

  Sage

      sage: from sage.plot.complex_plot import rgb_to_hls, hls_to_rgb
      sage: hls = [[0.2, 0.4, 0.5], [0.1, 0.3, 1.0]]
      sage: rgb = hls_to_rgb(hls)
      sage: rgb  # abs tol 1e-4
      array([[0.52, 0.6 , 0.2 ],
             [0.6 , 0.36, 0.  ]])
      sage: rgb_to_hls(rgb)  # abs tol 1e-4
      array([[0.2, 0.4, 0.5],
             [0.1, 0.3, 1. ]])

  Python

      >>> from sage.all import *
      >>> from sage.plot.complex_plot import rgb_to_hls, hls_to_rgb
      >>> hls = [[RealNumber('0.2'), RealNumber('0.4'), RealNumber('0.5')], [RealNumber('0.1'), RealNumber('0.3'), RealNumber('1.0')]]
      >>> rgb = hls_to_rgb(hls)
      >>> rgb  # abs tol 1e-4
      array([[0.52, 0.6 , 0.2 ],
             [0.6 , 0.36, 0.  ]])
      >>> rgb_to_hls(rgb)  # abs tol 1e-4
      array([[0.2, 0.4, 0.5],
             [0.1, 0.3, 1. ]])

  Multidimensional inputs can be given as well:

  Sage

      sage: multidim_arr = [[[0, 0.2, 0.4], [0, 1, 0]], [[0, 0, 0], [0.5, 0.6, 0.9]]]
      sage: hls_to_rgb(multidim_arr)  # abs tol 1e-4
      array([[[0.28, 0.12, 0.12],
              [1.  , 1.  , 1.  ]],
             [[0.  , 0.  , 0.  ],
              [0.24, 0.96, 0.96]]])

  [Python]

      >>> from sage.all import *
      >>> multidim_arr = [[[Integer(0), RealNumber('0.2'), RealNumber('0.4')], [Integer(0), Integer(1), Integer(0)]], [[Integer(0), Integer(0), Integer(0)], [RealNumber('0.5'), RealNumber('0.6'), RealNumber('0.9')]]]
      >>> hls_to_rgb(multidim_arr)  # abs tol 1e-4
      array([[[0.28, 0.12, 0.12],
              [1.  , 1.  , 1.  ]],
             [[0.  , 0.  , 0.  ],
              [0.24, 0.96, 0.96]]])

sage.plot.complex_plot.rgb_to_hls(rgb)

  Convert array of rgb values (each in the range \([0, 1]\)) to a numpy array of hls values (each in the range \([0, 1]\))

  INPUT:

  - `rgb` – an \(N \times 3\) array of floats with values in the range \([0, 1]\); the rgb values at each point. (Note that the input can actually be of any dimension, such as \(N \times M \times 3\), as long as the last dimension has length \(3\)).

  OUTPUT:

  An \(N \times 3\) Numpy array of floats in the range \([0, 1]\), with the same dimensions as the input array.

  See also

  `sage.plot.complex_plot.hls_to_rgb()`

  EXAMPLES:

  We convert a row of floats and verify that we can convert back using `hls_to_rgb`:

  Sage

      sage: from sage.plot.complex_plot import rgb_to_hls, hls_to_rgb
      sage: rgb = [[0.2, 0.4, 0.5], [0.1, 0.3, 1.0]]
      sage: hls = rgb_to_hls(rgb)
      sage: hls   # abs tol 1e-4
      array([[0.55555556, 0.35      , 0.42857143],
             [0.62962963, 0.55      , 1.        ]])
      sage: hls_to_rgb(hls)  # abs tol 1e-4
      array([[0.2, 0.4, 0.5],
             [0.1, 0.3, 1. ]])

  Python

      >>> from sage.all import *
      >>> from sage.plot.complex_plot import rgb_to_hls, hls_to_rgb
      >>> rgb = [[RealNumber('0.2'), RealNumber('0.4'), RealNumber('0.5')], [RealNumber('0.1'), RealNumber('0.3'), RealNumber('1.0')]]
      >>> hls = rgb_to_hls(rgb)
      >>> hls   # abs tol 1e-4
      array([[0.55555556, 0.35      , 0.42857143],
             [0.62962963, 0.55      , 1.        ]])
      >>> hls_to_rgb(hls)  # abs tol 1e-4
      array([[0.2, 0.4, 0.5],
             [0.1, 0.3, 1. ]])

  Multidimensional inputs can be given as well:

  Sage

      sage: multidim_arr = [[[0, 0.2, 0.4], [1, 1, 1]], [[0, 0, 0], [0.5, 0.6, 0.9]]]
      sage: rgb_to_hls(multidim_arr)   # abs tol 1e-4
      array([[[0.58333333, 0.2       , 1.        ],
              [0.        , 1.        , 0.        ]],
             [[0.        , 0.        , 0.        ],
              [0.625     , 0.7       , 0.66666667]]])

  [Python]

      >>> from sage.all import *
      >>> multidim_arr = [[[Integer(0), RealNumber('0.2'), RealNumber('0.4')], [Integer(1), Integer(1), Integer(1)]], [[Integer(0), Integer(0), Integer(0)], [RealNumber('0.5'), RealNumber('0.6'), RealNumber('0.9')]]]
      >>> rgb_to_hls(multidim_arr)   # abs tol 1e-4
      array([[[0.58333333, 0.2       , 1.        ],
              [0.        , 1.        , 0.        ]],
             [[0.        , 0.        , 0.        ],
              [0.625     , 0.7       , 0.66666667]]])