From 663cdd2efc4e630845c304454a69f7a5b3cdfb24 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 23 Nov 2021 14:06:02 +0000 Subject: [PATCH 1/2] Fix compatibility with Matplotlib 3.5 The problem was `qiskit.visualization.bloch.Arrow3D`, which subclassed `matplotlib.patches.FancyArrowPatch` (a 2D patch), but not the 3D machinery. It is now made an unholy multiple-inherited abomination of both the 2D patch and `mpl_toolkits.mplot3d.art3d.Patch3D`; the latter is a relatively thin wrapper (in terms of attributes) around the 2D patch, so this is not too terrible. Matplotlib 3.5 calls the `Patch3D.do_3d_projection` method using a deprecated parameter, triggering two warnings, unless the artist's module appears to have come from `mpl_toolkits.mplot3d.art3d`, even if the new calling convention is respected. The warning is only triggered when the figure is drawn, which may well be outside of our control, so we cannot suppress the warnings. Instead, we just lie about the module the arrow patch was defined in, to trick it into not warning, because we use the new calling convention. This is supported at least as far back as Matplotlib 3.3, which is the current minimum supported version. The nasty hack should be removable once Matplotlib 3.6 is the minimum version because the deprecation period will expire. --- azure-pipelines.yml | 2 +- qiskit/visualization/bloch.py | 27 +++++++++++++++---- .../fix-matplotlib-3.5-40f6d1a109ae06fe.yaml | 4 +++ requirements-dev.txt | 2 +- setup.py | 2 +- 5 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/fix-matplotlib-3.5-40f6d1a109ae06fe.yaml diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 90f095c7330a..652366de5b31 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -778,7 +778,7 @@ stages: python -m pip install --upgrade pip pip install -U -r requirements.txt -r requirements-dev.txt -c constraints.txt pip install -c constraints.txt -e . - pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "qiskit-ignis" "matplotlib>=3.3.0,<3.5" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt + pip install "qiskit-ibmq-provider" "qiskit-aer" "z3-solver" "qiskit-ignis" "matplotlib>=3.3.0" sphinx nbsphinx sphinx_rtd_theme cvxpy -c constraints.txt python setup.py build_ext --inplace sudo apt install -y graphviz pandoc pip check diff --git a/qiskit/visualization/bloch.py b/qiskit/visualization/bloch.py index ab4352813b92..0aba604c75ef 100644 --- a/qiskit/visualization/bloch.py +++ b/qiskit/visualization/bloch.py @@ -54,19 +54,36 @@ import matplotlib.pyplot as plt from matplotlib.patches import FancyArrowPatch from mpl_toolkits.mplot3d import Axes3D, proj3d +from mpl_toolkits.mplot3d.art3d import Patch3D from .utils import matplotlib_close_if_inline -class Arrow3D(FancyArrowPatch): +class Arrow3D(Patch3D, FancyArrowPatch): """Makes a fancy arrow""" - def __init__(self, xs, ys, zs, *args, **kwargs): - FancyArrowPatch.__init__(self, (0, 0), (0, 0), *args, **kwargs) - self._verts3d = xs, ys, zs + # Nasty hack around a poorly implemented deprecation warning in Matplotlib 3.5 that issues two + # deprecation warnings if an artist's module does not claim to be part of the below module. + # This revolves around the method `Patch3D.do_3d_projection(self, renderer=None)`. The + # `renderer` argument has been deprecated since Matplotlib 3.4, but in 3.5 some internal calls + # during `Axes3D` display started calling the method. If an artist does not have this module, + # then it issues a deprecation warning, and calls it by passing the `renderer` parameter as + # well, which consequently triggers another deprecation warning. We should be able to remove + # this once 3.6 is the minimum supported version, because the deprecation period ends then. + __module__ = "mpl_toolkits.mplot3d.art3d" + + def __init__(self, xs, ys, zs, zdir="z", **kwargs): + # The Patch3D.__init__() method just calls its own super() method and then + # self.set_3d_properties, but it its __init__ signature is actually pretty incompatible with + # how it goes on to call set_3d_properties, so we just have to do things ourselves. The + # parent of Patch3D is Patch, which is also a parent of FancyArrowPatch, so its __init__ is + # still getting suitably called. + # pylint: disable=super-init-not-called + FancyArrowPatch.__init__(self, (0, 0), (0, 0), **kwargs) + self.set_3d_properties(tuple(zip(xs, ys)), zs, zdir) def draw(self, renderer): - xs3d, ys3d, zs3d = self._verts3d + xs3d, ys3d, zs3d = zip(*self._segment3d) x_s, y_s, _ = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M) self.set_positions((x_s[0], y_s[0]), (x_s[1], y_s[1])) FancyArrowPatch.draw(self, renderer) diff --git a/releasenotes/notes/fix-matplotlib-3.5-40f6d1a109ae06fe.yaml b/releasenotes/notes/fix-matplotlib-3.5-40f6d1a109ae06fe.yaml new file mode 100644 index 000000000000..07062479072b --- /dev/null +++ b/releasenotes/notes/fix-matplotlib-3.5-40f6d1a109ae06fe.yaml @@ -0,0 +1,4 @@ +fixes: + - | + Fixed a compatibility issue with Matplotlib 3.5, where the Bloch sphere would fail to render if it had any vectors attached, such as by using :obj:`~qiskit.visualization.plot_bloch_vector`. + See `#7272 `__ for more detail. diff --git a/requirements-dev.txt b/requirements-dev.txt index 6a754a3616d8..e1d497635d78 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,7 +4,7 @@ ipython<7.22.0 ipykernel<5.5.2 ipywidgets>=7.3.0 jupyter -matplotlib>=3.3,<3.5 +matplotlib>=3.3 pillow>=4.2.1 black==21.4b2 pydot diff --git a/setup.py b/setup.py index 700addfc56ca..1844215c3610 100755 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ visualization_extras = [ - "matplotlib>=3.3,<3.5", + "matplotlib>=3.3", "ipywidgets>=7.3.0", "pydot", "pillow>=4.2.1", From 58cf34b3d0ec2e0c92c427cccd77b9edee4eb28d Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 23 Nov 2021 16:19:28 +0000 Subject: [PATCH 2/2] Remove useless word Co-authored-by: Matthew Treinish --- qiskit/visualization/bloch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/visualization/bloch.py b/qiskit/visualization/bloch.py index 0aba604c75ef..97f1c28af148 100644 --- a/qiskit/visualization/bloch.py +++ b/qiskit/visualization/bloch.py @@ -74,7 +74,7 @@ class Arrow3D(Patch3D, FancyArrowPatch): def __init__(self, xs, ys, zs, zdir="z", **kwargs): # The Patch3D.__init__() method just calls its own super() method and then - # self.set_3d_properties, but it its __init__ signature is actually pretty incompatible with + # self.set_3d_properties, but its __init__ signature is actually pretty incompatible with # how it goes on to call set_3d_properties, so we just have to do things ourselves. The # parent of Patch3D is Patch, which is also a parent of FancyArrowPatch, so its __init__ is # still getting suitably called.