Repository: yt-project/yt

Issue:
BUG: invalid auto symlog parameters
### Bug report

**Bug summary**

In some instances, trying to plot an image containing both negative values and extremely small values results in a crash in matplotlib.

This bug was first discovered and reported on slack by @BenWibking.
He provided a reproducer script and an example dataset where this bug happens in production
https://cloudstor.aarnet.edu.au/plus/s/TBFG6m48BXVKdkE

**Code for reproduction**
Here's a pure yt reproducer
```python
import numpy as np
import yt

shape = (64,64,1)
arr = np.full(shape, 5.e-324)
arr[0, 0] = -1e12
arr[1, 1] = 200
d = {'scalar': arr}

ds = yt.load_uniform_grid(d, shape)
p = yt.SlicePlot(ds, "z", ('stream', 'scalar'))
p.save("/tmp/output.png")
```

**Actual outcome**

```
yt : [INFO     ] 2022-03-24 10:58:41,550 Parameters: current_time              = 0.0
yt : [INFO     ] 2022-03-24 10:58:41,550 Parameters: domain_dimensions         = [64 64  1]
yt : [INFO     ] 2022-03-24 10:58:41,550 Parameters: domain_left_edge          = [0. 0. 0.]
yt : [INFO     ] 2022-03-24 10:58:41,551 Parameters: domain_right_edge         = [1. 1. 1.]
yt : [INFO     ] 2022-03-24 10:58:41,551 Parameters: cosmological_simulation   = 0
yt : [INFO     ] 2022-03-24 10:58:41,795 xlim = 0.000000 1.000000
yt : [INFO     ] 2022-03-24 10:58:41,795 ylim = 0.000000 1.000000
yt : [INFO     ] 2022-03-24 10:58:41,795 xlim = 0.000000 1.000000
yt : [INFO     ] 2022-03-24 10:58:41,795 ylim = 0.000000 1.000000
yt : [INFO     ] 2022-03-24 10:58:41,796 Making a fixed resolution buffer of (('stream', 'scalar')) 800 by 800
yt : [WARNING  ] 2022-03-24 10:58:41,885 Plotting ('stream', 'scalar'): Both positive and negative values. Min = -1000000000000.0017 dimensionless, Max = 200.00000000000034 dimensionless.
yt : [WARNING  ] 2022-03-24 10:58:41,885 Switching to symlog colorbar scaling.
/Users/robcleme/.pyenv/versions/yt-dev/lib/python3.10/site-packages/unyt/array.py:1778: RuntimeWarning: overflow encountered in true_divide
  out_arr = func(
Traceback (most recent call last):
  File "/Users/robcleme/dev/yt-project/test-data-dir/norm_bug/yt_bug_reproducer/minimal.py", line 12, in <module>
    p = yt.SlicePlot(ds, "z", ('stream', 'scalar'))
  File "/Users/robcleme/dev/yt-project/yt/yt/visualization/plot_window.py", line 1975, in __init__
    PWViewerMPL.__init__(
  File "/Users/robcleme/dev/yt-project/yt/yt/visualization/plot_window.py", line 851, in __init__
    PlotWindow.__init__(self, *args, **kwargs)
  File "/Users/robcleme/dev/yt-project/yt/yt/visualization/plot_window.py", line 251, in __init__
    self._setup_plots()
  File "/Users/robcleme/dev/yt-project/yt/yt/visualization/plot_window.py", line 1118, in _setup_plots
    self.plots[f] = WindowPlotMPL(
  File "/Users/robcleme/dev/yt-project/yt/yt/visualization/plot_window.py", line 2641, in __init__
    self._init_image(data, cbname, cblinthresh, cmap, extent, aspect)
  File "/Users/robcleme/dev/yt-project/yt/yt/visualization/base_plot_types.py", line 303, in _init_image
    self.cb = self.figure.colorbar(self.image, self.cax, format=formatter)
  File "/Users/robcleme/.pyenv/versions/yt-dev/lib/python3.10/site-packages/matplotlib/figure.py", line 1158, in colorbar
    cb = cbar.Colorbar(cax, mappable, **cb_kw)
  File "/Users/robcleme/.pyenv/versions/yt-dev/lib/python3.10/site-packages/matplotlib/colorbar.py", line 484, in __init__
    self._reset_locator_formatter_scale()
  File "/Users/robcleme/.pyenv/versions/yt-dev/lib/python3.10/site-packages/matplotlib/colorbar.py", line 1175, in _reset_locator_formatter_scale
    self._process_values()
  File "/Users/robcleme/.pyenv/versions/yt-dev/lib/python3.10/site-packages/matplotlib/colorbar.py", line 1117, in _process_values
    b = self.norm.inverse(b)
  File "/Users/robcleme/.pyenv/versions/yt-dev/lib/python3.10/site-packages/matplotlib/colors.py", line 1554, in inverse
    raise ValueError("Invalid vmin or vmax")
ValueError: Invalid vmin or vmax
```

**Version Information**
  * yt version: 4.0.2 or main branch
  * Other Libraries (if applicable): Matplotlib 3.5




Discussion:

Resolving patch:
diff --git a/yt/visualization/base_plot_types.py b/yt/visualization/base_plot_types.py
--- a/yt/visualization/base_plot_types.py
+++ b/yt/visualization/base_plot_types.py
@@ -219,9 +219,11 @@ def _init_image(self, data, cbnorm, cblinthresh, cmap, extent, aspect):
 
         if cbnorm == "symlog":
             # if cblinthresh is not specified, try to come up with a reasonable default
-            min_abs_val = np.min(np.abs((zmin, zmax)))
-            if cblinthresh is None:
-                cblinthresh = np.nanmin(np.absolute(data)[data != 0])
+            min_abs_val, max_abs_val = np.sort(np.abs((zmin, zmax)))
+            if cblinthresh is not None:
+                pass
+            elif min_abs_val > 0:
+                cblinthresh = min_abs_val
             elif zmin * zmax > 0 and cblinthresh < min_abs_val:
                 warnings.warn(
                     f"Cannot set a symlog norm with linear threshold {cblinthresh} "
@@ -229,6 +231,10 @@ def _init_image(self, data, cbnorm, cblinthresh, cmap, extent, aspect):
                     "Switching to log norm."
                 )
                 cbnorm = "log10"
+            else:
+                cblinthresh = max_abs_val / 1000
+            if cblinthresh is None:
+                cblinthresh = np.nanmin(np.absolute(data)[data != 0])
 
         if cbnorm == "log10":
             cbnorm_cls = matplotlib.colors.LogNorm


Test patch:
diff --git a/tests/tests.yaml b/tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -170,10 +170,10 @@ answer_tests:
   local_axialpix_008: # PR 3628
     - yt/geometry/coordinates/tests/test_axial_pixelization.py:test_axial_pixelization
 
-  local_cylindrical_background_012:  # PR 3793
+  local_cylindrical_background_013:  # PR 3859
     - yt/geometry/coordinates/tests/test_cylindrical_coordinates.py:test_noise_plots
 
-  local_spherical_background_006:  # PR 3793
+  local_spherical_background_007:  # PR 3859
     - yt/geometry/coordinates/tests/test_spherical_coordinates.py:test_noise_plots
 
   #local_particle_trajectory_001: