Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Deprecate redundant log-scale transform classes. #12832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/api/next_api_changes/2018-11-18-AL.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Deprecations
````````````

- The ``LogTransformBase``, ``Log10Transform``, ``Log2Transform``,
``NaturalLogTransformLog``, ``InvertedLogTransformBase``,
``InvertedLog10Transform``, ``InvertedLog2Transform``, and
``InvertedNaturalLogTransform`` classes (all defined in
:mod:`matplotlib.scales`) are deprecated. As a replacement, use the general
`LogTransform` and `InvertedLogTransform` classes, whose constructors take a
*base* argument.
109 changes: 66 additions & 43 deletions lib/matplotlib/scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def set_default_locators_and_formatters(self, axis):
axis.set_minor_locator(NullLocator())


@cbook.deprecated("3.1", alternative="LogTransform")
class LogTransformBase(Transform):
input_dims = 1
output_dims = 1
Expand All @@ -206,28 +207,14 @@ def __init__(self, nonpos='clip'):
self._clip = {"clip": True, "mask": False}[nonpos]

def transform_non_affine(self, a):
# Ignore invalid values due to nans being passed to the transform
with np.errstate(divide="ignore", invalid="ignore"):
out = np.log(a)
out /= np.log(self.base)
if self._clip:
# SVG spec says that conforming viewers must support values up
# to 3.4e38 (C float); however experiments suggest that
# Inkscape (which uses cairo for rendering) runs into cairo's
# 24-bit limit (which is apparently shared by Agg).
# Ghostscript (used for pdf rendering appears to overflow even
# earlier, with the max value around 2 ** 15 for the tests to
# pass. On the other hand, in practice, we want to clip beyond
# np.log10(np.nextafter(0, 1)) ~ -323
# so 1000 seems safe.
out[a <= 0] = -1000
return out
return LogTransform.transform_non_affine(self, a)

def __str__(self):
return "{}({!r})".format(
type(self).__name__, "clip" if self._clip else "mask")


@cbook.deprecated("3.1", alternative="InvertedLogTransform")
class InvertedLogTransformBase(Transform):
input_dims = 1
output_dims = 1
Expand All @@ -241,79 +228,118 @@ def __str__(self):
return "{}()".format(type(self).__name__)


@cbook.deprecated("3.1", alternative="LogTransform")
class Log10Transform(LogTransformBase):
base = 10.0

def inverted(self):
return InvertedLog10Transform()


@cbook.deprecated("3.1", alternative="InvertedLogTransform")
class InvertedLog10Transform(InvertedLogTransformBase):
base = 10.0

def inverted(self):
return Log10Transform()


@cbook.deprecated("3.1", alternative="LogTransform")
class Log2Transform(LogTransformBase):
base = 2.0

def inverted(self):
return InvertedLog2Transform()


@cbook.deprecated("3.1", alternative="InvertedLogTransform")
class InvertedLog2Transform(InvertedLogTransformBase):
base = 2.0

def inverted(self):
return Log2Transform()


@cbook.deprecated("3.1", alternative="LogTransform")
class NaturalLogTransform(LogTransformBase):
base = np.e

def inverted(self):
return InvertedNaturalLogTransform()


@cbook.deprecated("3.1", alternative="InvertedLogTransform")
class InvertedNaturalLogTransform(InvertedLogTransformBase):
base = np.e

def inverted(self):
return NaturalLogTransform()


class LogTransform(LogTransformBase):
class LogTransform(Transform):
input_dims = 1
output_dims = 1
is_separable = True
has_inverse = True

def __init__(self, base, nonpos='clip'):
LogTransformBase.__init__(self, nonpos)
Transform.__init__(self)
self.base = base
self._clip = {"clip": True, "mask": False}[nonpos]

def __str__(self):
return "{}(base={}, nonpos={!r})".format(
type(self).__name__, self.base, "clip" if self._clip else "mask")

def transform_non_affine(self, a):
# Ignore invalid values due to nans being passed to the transform.
with np.errstate(divide="ignore", invalid="ignore"):
log = {np.e: np.log, 2: np.log2, 10: np.log10}.get(self.base)
if log: # If possible, do everything in a single call to Numpy.
out = log(a)
Copy link
Member

@jklymak jklymak Nov 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the optimization you are referring to? I guess I think this is a good idea since it covers 99.9% of the cases, and would instead suggest changing the images or increasing the tolerance. But I'm not an expert on how expensive the un-optimized code is...

else:
out = np.log(a)
out /= np.log(self.base)
if self._clip:
# SVG spec says that conforming viewers must support values up
# to 3.4e38 (C float); however experiments suggest that
# Inkscape (which uses cairo for rendering) runs into cairo's
# 24-bit limit (which is apparently shared by Agg).
# Ghostscript (used for pdf rendering appears to overflow even
# earlier, with the max value around 2 ** 15 for the tests to
# pass. On the other hand, in practice, we want to clip beyond
# np.log10(np.nextafter(0, 1)) ~ -323
# so 1000 seems safe.
out[a <= 0] = -1000
return out

def inverted(self):
return InvertedLogTransform(self.base)


class InvertedLogTransform(InvertedLogTransformBase):
input_dims = 1
output_dims = 1
is_separable = True
has_inverse = True

def __init__(self, base):
InvertedLogTransformBase.__init__(self)
Transform.__init__(self)
self.base = base

def __str__(self):
return "{}(base={})".format(type(self).__name__, self.base)

def transform_non_affine(self, a):
return ma.power(self.base, a)

def inverted(self):
return LogTransform(self.base)


class LogScale(ScaleBase):
"""
A standard logarithmic scale. Care is taken so non-positive
values are not plotted.

For computational efficiency (to push as much as possible to Numpy
C code in the common cases), this scale provides different
transforms depending on the base of the logarithm:

- base 10 (:class:`Log10Transform`)
- base 2 (:class:`Log2Transform`)
- base e (:class:`NaturalLogTransform`)
- arbitrary base (:class:`LogTransform`)
A standard logarithmic scale. Care is taken to only plot positive values.
"""
name = 'log'

Expand Down Expand Up @@ -365,18 +391,13 @@ def __init__(self, axis, **kwargs):
if base <= 0 or base == 1:
raise ValueError('The log base cannot be <= 0 or == 1')

if base == 10.0:
self._transform = self.Log10Transform(nonpos)
elif base == 2.0:
self._transform = self.Log2Transform(nonpos)
elif base == np.e:
self._transform = self.NaturalLogTransform(nonpos)
else:
self._transform = self.LogTransform(base, nonpos)

self.base = base
self._transform = self.LogTransform(base, nonpos)
self.subs = subs

@property
def base(self):
return self._transform.base

def set_default_locators_and_formatters(self, axis):
"""
Set the locators and formatters to specialized versions for
Expand Down Expand Up @@ -436,10 +457,12 @@ def forward(values: array-like) -> array-like

"""
forward, inverse = functions
self.base = base
self.subs = None
transform = FuncTransform(forward, inverse) + LogTransform(base)
self._transform = transform
self._transform = FuncTransform(forward, inverse) + LogTransform(base)

@property
def base(self):
return self._transform._b.base # Base of the LogTransform.

def get_transform(self):
"""
Expand Down
3 changes: 1 addition & 2 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5282,8 +5282,7 @@ def test_title_location_roundtrip():


@image_comparison(baseline_images=["loglog"], remove_text=True,
extensions=['png'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0))
extensions=['png'], tol=0.02)
def test_loglog():
fig, ax = plt.subplots()
x = np.arange(1, 11)
Expand Down
3 changes: 1 addition & 2 deletions lib/matplotlib/tests/test_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ def test_logscale_transform_repr():


@image_comparison(baseline_images=['logscale_nonpos_values'], remove_text=True,
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'], style='mpl20')
extensions=['png'], tol=0.02, style='mpl20')
def test_logscale_nonpos_values():
np.random.seed(19680801)
xs = np.random.normal(size=int(1e3))
Expand Down