diff --git a/python/quantum-pecos/pyproject.toml b/python/quantum-pecos/pyproject.toml index 10697ffb4..63d45cf6c 100644 --- a/python/quantum-pecos/pyproject.toml +++ b/python/quantum-pecos/pyproject.toml @@ -33,7 +33,6 @@ dependencies = [ "numpy>=1.15.0", "scipy>=1.1.0", "networkx>=2.1.0", - "matplotlib>=2.2.0", ] classifiers = [ "Development Status :: 4 - Beta", @@ -61,6 +60,7 @@ guppy = [ "selene-sim~=0.2.0", # Then selene-sim (dependency of guppylang) ] visualization = [ + "matplotlib>=2.2.0", "plotly~=5.9.0", ] all = [ diff --git a/python/quantum-pecos/src/pecos/qeccs/plot.py b/python/quantum-pecos/src/pecos/qeccs/plot.py index 923cfdfaf..7bf8a6bce 100644 --- a/python/quantum-pecos/src/pecos/qeccs/plot.py +++ b/python/quantum-pecos/src/pecos/qeccs/plot.py @@ -21,9 +21,9 @@ from typing import TYPE_CHECKING, TypeVar import networkx as nx -from matplotlib import pyplot as plt if TYPE_CHECKING: + from pecos.protocols import LogicalInstructionProtocol, QECCProtocol T = TypeVar("T") @@ -54,6 +54,8 @@ def plot_qecc( **kwargs: Additional keyword arguments (will raise exception if any are provided). """ + from matplotlib import pyplot as plt + if kwargs: msg = f"keys {kwargs.keys()} not recognized!" raise Exception(msg) @@ -166,6 +168,8 @@ def plot_instr( **kwargs: Additional keyword arguments (will raise exception if any are provided) """ + from matplotlib import pyplot as plt + if kwargs: msg = f"keys {kwargs.keys()} not recognized!" raise Exception(msg) diff --git a/python/quantum-pecos/src/pecos/qeclib/color488/plot_layout.py b/python/quantum-pecos/src/pecos/qeclib/color488/plot_layout.py index 47c4b575d..aba447701 100644 --- a/python/quantum-pecos/src/pecos/qeclib/color488/plot_layout.py +++ b/python/quantum-pecos/src/pecos/qeclib/color488/plot_layout.py @@ -13,10 +13,11 @@ from typing import TYPE_CHECKING -import matplotlib.pyplot as plt import networkx as nx +from matplotlib import pyplot as plt if TYPE_CHECKING: + from pecos.qeclib.color488 import Color488 @@ -36,6 +37,8 @@ def plot_layout( Returns: The matplotlib pyplot module with the plot rendered. """ + import matplotlib.pyplot as plt + positions, polygons = color488.get_layout() # Calculate the mid-point for each polygon diff --git a/python/quantum-pecos/src/pecos/qeclib/surface/visualization/lattice_2d.py b/python/quantum-pecos/src/pecos/qeclib/surface/visualization/lattice_2d.py index e19d9d6ca..e838a0bb6 100644 --- a/python/quantum-pecos/src/pecos/qeclib/surface/visualization/lattice_2d.py +++ b/python/quantum-pecos/src/pecos/qeclib/surface/visualization/lattice_2d.py @@ -5,12 +5,12 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -import matplotlib.pyplot as plt import numpy as np -from matplotlib.patches import Circle, PathPatch -from matplotlib.path import Path if TYPE_CHECKING: + from matplotlib import pyplot as plt + from matplotlib.path import Path + from pecos.qeclib.surface.patches.patch_base import SurfacePatch @@ -75,6 +75,9 @@ def plot_colored_polygons( polygon_colors (dict[int, int]): List of indices into `colors` for each polygon. config (Lattice2DConfig | None): Optional Lattice2DConfig object. """ + import matplotlib.pyplot as plt + from matplotlib.patches import Circle, PathPatch + c = config # Plot setup @@ -208,6 +211,8 @@ def create_cup_path( Returns: Path: A matplotlib path representing the cup shape. """ + from matplotlib.path import Path + # Calculate midpoint of the base mid_base = ((base1[0] + base2[0]) / 2, (base1[1] + base2[1]) / 2) diff --git a/python/quantum-pecos/src/pecos/tools/pseudo_threshold_tools.py b/python/quantum-pecos/src/pecos/tools/pseudo_threshold_tools.py index 075ba3b2d..e0a0ec92e 100644 --- a/python/quantum-pecos/src/pecos/tools/pseudo_threshold_tools.py +++ b/python/quantum-pecos/src/pecos/tools/pseudo_threshold_tools.py @@ -22,7 +22,6 @@ from typing import TYPE_CHECKING -import matplotlib.pyplot as plt import numpy as np from scipy.optimize import brentq, curve_fit, newton @@ -324,6 +323,8 @@ def plot( p_start(float): Starting point for the plot axes. If None, automatically determined. p_end(float): Ending point for the plot axes. If None, automatically determined. """ + import matplotlib.pyplot as plt + if p_start is None: p_start = min(plog) * 0.9 diff --git a/python/quantum-pecos/tests/conftest.py b/python/quantum-pecos/tests/conftest.py index 2dc58ee6f..07d166b8c 100644 --- a/python/quantum-pecos/tests/conftest.py +++ b/python/quantum-pecos/tests/conftest.py @@ -11,11 +11,15 @@ """Test configuration and shared fixtures.""" -# Configure matplotlib to use non-interactive backend for tests +# Configure matplotlib to use non-interactive backend for tests (if available) # This must be done before importing matplotlib.pyplot to avoid GUI backend issues on Windows -import matplotlib as mpl +try: + import matplotlib as mpl -mpl.use("Agg") + mpl.use("Agg") +except ImportError: + # matplotlib is optional - only needed for visualization tests + pass # Note: llvmlite functionality is now always available via Rust (pecos_rslib.ir and pecos_rslib.binding) # No need for conditional test skipping diff --git a/ruff.toml b/ruff.toml index 653e4728c..d2a16cd24 100644 --- a/ruff.toml +++ b/ruff.toml @@ -200,6 +200,10 @@ ignore = [ "python/quantum-pecos/src/pecos/frontends/*.py" = ["PLC0415"] # All frontends have optional dependencies "python/quantum-pecos/src/pecos/frontends/guppy_frontend.py" = ["PLC0415", "S603", "S607"] # Also uses subprocess for external tools "python/quantum-pecos/src/pecos/frontends/selene_native_backend.py" = ["S311"] # Uses random for test placeholders +"python/quantum-pecos/src/pecos/qeccs/plot.py" = ["PLC0415"] # matplotlib: optional visualization dependency +"python/quantum-pecos/src/pecos/qeclib/color488/plot_layout.py" = ["PLC0415"] # matplotlib: optional visualization dependency +"python/quantum-pecos/src/pecos/qeclib/surface/visualization/lattice_2d.py" = ["PLC0415"] # matplotlib: optional visualization dependency +"python/quantum-pecos/src/pecos/tools/pseudo_threshold_tools.py" = ["PLC0415"] # matplotlib: optional visualization dependency # Examples - relaxed rules for demonstration code "python/pecos-rslib/examples/*.py" = ["PLC0415", "INP001"] # Lazy imports, no __init__.py needed diff --git a/uv.lock b/uv.lock index 145fd08b7..061bed69a 100644 --- a/uv.lock +++ b/uv.lock @@ -3515,7 +3515,6 @@ name = "quantum-pecos" version = "0.7.0.dev4" source = { editable = "python/quantum-pecos" } dependencies = [ - { name = "matplotlib" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -3529,6 +3528,7 @@ dependencies = [ [package.optional-dependencies] all = [ { name = "guppylang" }, + { name = "matplotlib" }, { name = "plotly" }, { name = "selene-sim" }, ] @@ -3542,6 +3542,7 @@ guppy = [ { name = "selene-sim" }, ] visualization = [ + { name = "matplotlib" }, { name = "plotly" }, ] @@ -3550,7 +3551,7 @@ requires-dist = [ { name = "cupy-cuda13x", marker = "python_full_version >= '3.11' and extra == 'cuda'", specifier = ">=13.0.0" }, { name = "cuquantum-python-cu13", marker = "python_full_version >= '3.11' and extra == 'cuda'", specifier = ">=25.3.0" }, { name = "guppylang", marker = "extra == 'guppy'", specifier = ">=0.21.0" }, - { name = "matplotlib", specifier = ">=2.2.0" }, + { name = "matplotlib", marker = "extra == 'visualization'", specifier = ">=2.2.0" }, { name = "networkx", specifier = ">=2.1.0" }, { name = "numpy", specifier = ">=1.15.0" }, { name = "pecos-rslib", editable = "python/pecos-rslib" },