Skip to content

Commit

Permalink
Consolidate packaging functions into framework.package_deps
Browse files Browse the repository at this point in the history
Queries and tests of installed package versions were spread across a few
different modules. Here they are combined into one module
(`package_deps.py`) as suggested in
qiskit-community#1427.
  • Loading branch information
wshanks committed Mar 21, 2024
1 parent 6817445 commit 50f692a
Show file tree
Hide file tree
Showing 13 changed files with 69 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from typing import Any, List, Dict, TYPE_CHECKING

from qiskit_experiments.data_processing.discriminator import BaseDiscriminator
from qiskit_experiments.warnings import HAS_SKLEARN
from qiskit_experiments.framework.package_deps import HAS_SKLEARN

if TYPE_CHECKING:
from sklearn.discriminant_analysis import (
Expand Down
6 changes: 0 additions & 6 deletions qiskit_experiments/database_service/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

"""Experiment utility functions."""

import importlib.metadata
import io
import zipfile
import logging
Expand All @@ -37,11 +36,6 @@
LOG = logging.getLogger(__name__)


def qiskit_version():
"""Return the Qiskit version."""
return {p: importlib.metadata.distribution(p).version for p in ("qiskit", "qiskit-experiments")}


def parse_timestamp(utc_dt: Union[datetime, str]) -> datetime:
"""Parse a UTC ``datetime`` object or string.
Expand Down
1 change: 0 additions & 1 deletion qiskit_experiments/framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,3 @@
)
from .json import ExperimentEncoder, ExperimentDecoder
from .restless_mixin import RestlessMixin
from .package_deps import numpy_version
2 changes: 1 addition & 1 deletion qiskit_experiments/framework/analysis_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

from qiskit_experiments.database_service.device_component import DeviceComponent, to_component
from qiskit_experiments.database_service.exceptions import ExperimentDataError
from qiskit_experiments.database_service.utils import qiskit_version
from qiskit_experiments.framework.package_deps import qiskit_version

LOG = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/framework/experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
)
from qiskit_experiments.framework.json import ExperimentEncoder, ExperimentDecoder
from qiskit_experiments.database_service.utils import (
qiskit_version,
plot_to_svg_bytes,
ThreadSafeOrderedDict,
ThreadSafeList,
Expand All @@ -59,6 +58,7 @@
from qiskit_experiments.framework import BackendData
from qiskit_experiments.framework.containers import ArtifactData
from qiskit_experiments.framework import ExperimentStatus, AnalysisStatus, AnalysisCallback
from qiskit_experiments.framework.package_deps import qiskit_version
from qiskit_experiments.database_service.exceptions import (
ExperimentDataError,
ExperimentEntryNotFound,
Expand Down
60 changes: 55 additions & 5 deletions qiskit_experiments/framework/package_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,62 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Functions for checking dependency versions.
Functions for checking and reporting installed package versions.
"""

from importlib.metadata import version
from __future__ import annotations

from functools import lru_cache
from importlib.metadata import version as metadata_version

def numpy_version():
"""Returns the current numpy version in (major, minor) form."""
return tuple(map(int, version("numpy").split(".")[:2]))
from packaging.version import Version

from qiskit.utils.lazy_tester import LazyImportTester


__all__ = ["HAS_SKLEARN", "HAS_DYNAMICS", "qiskit_version", "version_is_at_least"]


HAS_SKLEARN = LazyImportTester(
{
"sklearn.discriminant_analysis": (
"LinearDiscriminantAnalysis",
"QuadraticDiscriminantAnalysis",
)
},
name="scikit-learn",
install="pip install scikit-learn",
)

HAS_DYNAMICS = LazyImportTester(
"qiskit_dynamics",
name="qiskit-dynamics",
install="pip install qiskit-dynamics",
)


def qiskit_version() -> dict[str, str]:
"""Return a deict with Qiskit names and versions."""
return {p: metadata_version(p) for p in ("qiskit", "qiskit-experiments")}


@lru_cache(maxsize=None)
def version_is_at_least(package: str, version: str) -> bool:
"""Return True if the installed version of package greater than minimum version
Args:
package: Name of the package
version: Minimum version name as a string. This should just include
major, minor, and micro parts. The function will add ``.dev0`` to
also catch any pre-release versions (otherwise ``0.5.0a1`` would
evaluate as less than ``0.5.0``).
Returns:
True if installed version greater than ``version``. False if it is less.
Raises:
PackageNotFoundError:
If ``package`` is not installed.
"""
installed_version = Version(metadata_version(package))
return installed_version > Version(f"{version}.dev0")
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, ExperimentData
from qiskit_experiments.data_processing import SkQDA
from qiskit_experiments.visualization import BasePlotter, IQPlotter, MplDrawer, PlotStyle
from qiskit_experiments.warnings import HAS_SKLEARN
from qiskit_experiments.framework.package_deps import HAS_SKLEARN

if TYPE_CHECKING:
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
Expand Down
5 changes: 3 additions & 2 deletions qiskit_experiments/library/tomography/tomography_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel

from qiskit_experiments.exceptions import AnalysisError
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options, numpy_version
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options
from qiskit_experiments.framework.package_deps import version_is_at_least
from .fitters import (
tomography_fitter_data,
postprocess_fitter,
Expand Down Expand Up @@ -311,7 +312,7 @@ def _fidelity_result(
bs_fidelities = []
for _ in range(self.options.target_bootstrap_samples):
# TODO: remove conditional once numpy is pinned at 1.22 and above
if numpy_version() >= (1, 22):
if version_is_at_least("numpy", "1.22"):
sampled_data = rng.multinomial(shot_data, prob_data)
else:
sampled_data = np.zeros_like(outcome_data)
Expand Down
15 changes: 3 additions & 12 deletions qiskit_experiments/test/pulse_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@
"""A Pulse simulation backend based on Qiskit-Dynamics"""

import datetime
import importlib.metadata
from functools import cached_property
from itertools import chain
from typing import Any, Dict, List, Optional, Tuple, Union

import numpy as np
from packaging.version import Version

from qiskit import QuantumCircuit
from qiskit.circuit import CircuitInstruction
Expand All @@ -38,9 +35,9 @@
from qiskit.result import Result, Counts
from qiskit.transpiler import InstructionProperties, Target

from qiskit_experiments.warnings import HAS_DYNAMICS
from qiskit_experiments.data_processing.discriminator import BaseDiscriminator
from qiskit_experiments.exceptions import QiskitError
from qiskit_experiments.framework.package_deps import HAS_DYNAMICS, version_is_at_least
from qiskit_experiments.test.utils import FakeJob


Expand Down Expand Up @@ -513,7 +510,7 @@ def __init__(
self.rabi_rate_12 = 6.876

if noise is True:
if self._dynamics_ge_05:
if version_is_at_least("qiskit-dynamics", "0.5.0"):
solver_args = {
"array_library": "numpy",
"vectorized": True,
Expand All @@ -524,7 +521,7 @@ def __init__(
}
static_dissipators = [t1_dissipator]
else:
if self._dynamics_ge_05:
if version_is_at_least("qiskit-dynamics", "0.5.0"):
solver_args = {
"array_library": "numpy",
}
Expand Down Expand Up @@ -632,9 +629,3 @@ def __init__(
self._simulated_pulse_unitaries = {
(schedule.name, (0,), ()): self.solve(schedule, (0,)) for schedule in default_schedules
}

@cached_property
def _dynamics_ge_05(self):
"""True if installed version of qiskit-dynamics>=0.5.0.dev0"""
dyn_version = Version(importlib.metadata.distribution("qiskit-dynamics").version)
return dyn_version > Version("0.5.0.dev0")
31 changes: 0 additions & 31 deletions qiskit_experiments/warnings.py

This file was deleted.

2 changes: 1 addition & 1 deletion test/data_processing/test_discriminator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from qiskit.exceptions import MissingOptionalLibraryError

from qiskit_experiments.data_processing import SkLDA, SkQDA
from qiskit_experiments.warnings import HAS_SKLEARN
from qiskit_experiments.framework.package_deps import HAS_SKLEARN


def requires_sklearn(func):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qiskit_experiments.library import MultiStateDiscrimination
from qiskit_experiments.test.pulse_backend import SingleTransmonTestBackend

from qiskit_experiments.warnings import HAS_SKLEARN
from qiskit_experiments.framework.package_deps import HAS_SKLEARN


def requires_sklearn(func):
Expand Down
4 changes: 2 additions & 2 deletions test/visualization/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from qiskit.exceptions import QiskitError

from qiskit_experiments.visualization.utils import DataExtentCalculator
from qiskit_experiments.framework.package_deps import numpy_version
from qiskit_experiments.framework.package_deps import version_is_at_least


@ddt
Expand All @@ -49,7 +49,7 @@ def _dummy_data(
# The result is a list of pairs representing a moving window of size 2.
# TODO: remove the old code once numpy is above 1.20.
dummy_data = []
if numpy_version() >= (1, 20):
if version_is_at_least("numpy", "1.20"):
for (x_min, x_max), (y_min, y_max) in it.product(
*np.lib.stride_tricks.sliding_window_view(bin_edges, 2, 1)
):
Expand Down

0 comments on commit 50f692a

Please sign in to comment.