diff --git a/doc/introduction/data.rst b/doc/introduction/data.rst index f283c43e48a..51625232b63 100644 --- a/doc/introduction/data.rst +++ b/doc/introduction/data.rst @@ -97,6 +97,36 @@ use them directly in a PennyLane circuits as follows: >>> print(circuit()) -1.0791430411076344 +Viewing Available Dataset Names +------------------------------- + +We can call the +:func:`~pennylane.data.list_data_names` function to get a snapshot of the names of the currently available datasets. +This function returns a list of strings as shown below. + +>>> qml.data.list_data_names() +["bars-and-stripes", + "downscaled-mnist", + "hamlib-max-3-sat", + "hamlib-maxcut", + "hamlib-travelling-salesperson-problem", + "hidden-manifold", + "hyperplanes", + "ketgpt", + "learning-dynamics-incoherently", + "linearly-separable", + "mnisq", + "mqt-bench", + "plus-minus", + "qchem", + "qspin", + "rydberggpt", + "two-curves"] + +Note that this example limits the results +of the function calls for clarity and that as more data becomes available, the results of these +function calls will change. + Viewing Available Datasets -------------------------- @@ -161,8 +191,9 @@ Quantum Datasets Functions and Classes .. autosummary:: :nosignatures: - ~pennylane.data.list_datasets ~pennylane.data.list_attributes + ~pennylane.data.list_data_names + ~pennylane.data.list_datasets ~pennylane.data.load ~pennylane.data.load_interactive ~pennylane.data.Dataset diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 237b52b1942..7f2a89b3bf8 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -24,6 +24,59 @@ [Haldane](https://journals.aps.org/prl/pdf/10.1103/PhysRevLett.61.2015) models on a lattice. [(#6201)](https://github.com/PennyLaneAI/pennylane/pull/6201/) +* A `has_sparse_matrix` property is added to `Operator` to indicate whether a sparse matrix is defined. + [(#6278)](https://github.com/PennyLaneAI/pennylane/pull/6278) + [(#6310)](https://github.com/PennyLaneAI/pennylane/pull/6310) + +

Improvements 🛠

+ +* RTD support for `qml.labs` added to API. + [(#6397)](https://github.com/PennyLaneAI/pennylane/pull/6397) + +* Module-level sandboxing added to `qml.labs` via pre-commit hooks. + [(#6369)](https://github.com/PennyLaneAI/pennylane/pull/6369) + +* `qml.matrix` now works with empty objects (such as empty tapes, `QNode`s and quantum functions that do + not call operations, single operators with empty decompositions). + [(#6347)](https://github.com/PennyLaneAI/pennylane/pull/6347) + +* PennyLane is now compatible with NumPy 2.0. + [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061) + [(#6258)](https://github.com/PennyLaneAI/pennylane/pull/6258) + [(#6342)](https://github.com/PennyLaneAI/pennylane/pull/6342) + +* PennyLane is now compatible with Jax 0.4.28. + [(#6255)](https://github.com/PennyLaneAI/pennylane/pull/6255) + +* `qml.qchem.excitations` now optionally returns fermionic operators. + [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) + +* The `diagonalize_measurements` transform now uses a more efficient method of diagonalization + when possible, based on the `pauli_rep` of the relevant observables. + [(#6113)](https://github.com/PennyLaneAI/pennylane/pull/6113/) + +* The `QuantumScript.copy` method now takes `operations`, `measurements`, `shots` and + `trainable_params` as keyword arguments. If any of these are passed when copying a + tape, the specified attributes will replace the copied attributes on the new tape. + [(#6285)](https://github.com/PennyLaneAI/pennylane/pull/6285) + [(#6363)](https://github.com/PennyLaneAI/pennylane/pull/6363) + +* Datasets are now downloaded via Dataset API. + [(#6126)](https://github.com/PennyLaneAI/pennylane/pull/6126) + +* The `Hermitian` operator now has a `compute_sparse_matrix` implementation. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225) + +* All PL templates are now unit tested to ensure JIT compatibility. + [(#6309)](https://github.com/PennyLaneAI/pennylane/pull/6309) + +* `qml.QutritBasisStatePreparation` is now JIT compatible. + [(#6308)](https://github.com/PennyLaneAI/pennylane/pull/6308) + +* `qml.AmplitudeAmplification` is now compatible with QJIT. + [(#6306)](https://github.com/PennyLaneAI/pennylane/pull/6306) + +

Calculating Polynomials 🔢

Readout Noise 📠

@@ -426,6 +479,7 @@ Diksha Dhawan, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, +Anthony Hayes, Austin Huang, Soran Jahangiri, Jacob Kitchen, diff --git a/pennylane/data/__init__.py b/pennylane/data/__init__.py index 2f07debe26d..10c34c74b35 100644 --- a/pennylane/data/__init__.py +++ b/pennylane/data/__init__.py @@ -37,6 +37,7 @@ load load_interactive list_attributes + list_data_names list_datasets In addition, various dataset types are provided @@ -207,16 +208,24 @@ class QuantumOscillator(qml.data.Dataset, data_name="quantum_oscillator", identi DatasetMolecule, DatasetNone, DatasetOperator, + DatasetPyTree, DatasetScalar, DatasetSparseArray, DatasetString, DatasetTuple, - DatasetPyTree, ) from .base import DatasetNotWriteableError from .base.attribute import AttributeInfo, DatasetAttribute, attribute from .base.dataset import Dataset, field -from .data_manager import DEFAULT, FULL, list_attributes, list_datasets, load, load_interactive +from .data_manager import ( + DEFAULT, + FULL, + list_attributes, + list_datasets, + list_data_names, + load, + load_interactive, +) __all__ = ( "AttributeInfo", @@ -240,6 +249,7 @@ class QuantumOscillator(qml.data.Dataset, data_name="quantum_oscillator", identi "load", "load_interactive", "list_attributes", + "list_data_names", "list_datasets", "DEFAULT", "FULL", diff --git a/pennylane/data/attributes/__init__.py b/pennylane/data/attributes/__init__.py index a699d6b5a05..7b7f3600cfb 100644 --- a/pennylane/data/attributes/__init__.py +++ b/pennylane/data/attributes/__init__.py @@ -20,11 +20,11 @@ from .molecule import DatasetMolecule from .none import DatasetNone from .operator import DatasetOperator +from .pytree import DatasetPyTree from .scalar import DatasetScalar from .sparse_array import DatasetSparseArray from .string import DatasetString from .tuple import DatasetTuple -from .pytree import DatasetPyTree __all__ = ( "DatasetArray", diff --git a/pennylane/data/base/graphql.py b/pennylane/data/base/graphql.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pennylane/data/data_manager/__init__.py b/pennylane/data/data_manager/__init__.py index e8f274c30e9..220a83e23c7 100644 --- a/pennylane/data/data_manager/__init__.py +++ b/pennylane/data/data_manager/__init__.py @@ -16,21 +16,29 @@ them. """ +import sys import urllib.parse from concurrent import futures from functools import lru_cache from pathlib import Path from time import sleep -from typing import Iterable, Mapping, Optional, Union -import sys +from typing import Any, Iterable, Mapping, Optional, Union + from requests import get, head from pennylane.data.base import Dataset from pennylane.data.base.hdf5 import open_hdf5_s3 from pennylane.data.data_manager import progress +from .graphql import ( + get_dataset_urls, + _get_parameter_tree, + list_data_names, + list_attributes, +) from .foldermap import DataPath, FolderMapView, ParamArg -from .params import DEFAULT, FULL, format_params +from .params import DEFAULT, FULL, format_params, provide_defaults + S3_URL = "https://datasets.cloud.pennylane.ai/datasets/h5" FOLDERMAP_URL = f"{S3_URL}/foldermap.json" @@ -52,7 +60,8 @@ def _get_data_struct(): response = get(DATA_STRUCT_URL, timeout=5.0) response.raise_for_status() - return response.json() + +S3_URL = "https://datasets.cloud.pennylane.ai/datasets/h5" def _download_partial( # pylint: disable=too-many-arguments @@ -119,15 +128,15 @@ def _download_full(s3_url: str, dest: Path, block_size: int, pbar_task: Optional f.write(block) -def _download_dataset( # pylint:disable=too-many-arguments - s3_url: str, +def _download_dataset( # pylint: disable=too-many-arguments + dataset_url: str, dest: Path, attributes: Optional[Iterable[str]], block_size: int, force: bool, pbar_task: Optional[progress.Task], ) -> None: - """Downloads the dataset at ``data_path`` to ``dest``, optionally downloading + """Downloads the dataset at ``dataset_url`` to ``dest``, optionally downloading only requested attributes. If ``attributes`` is not provided, every attribute will be requested. @@ -139,7 +148,7 @@ def _download_dataset( # pylint:disable=too-many-arguments if attributes is not None or dest.exists(): _download_partial( - s3_url, + dataset_url, dest=dest, attributes=attributes, overwrite=force, @@ -147,40 +156,39 @@ def _download_dataset( # pylint:disable=too-many-arguments pbar_task=pbar_task, ) else: - _download_full(s3_url, dest=dest, block_size=block_size, pbar_task=pbar_task) + _download_full(dataset_url, dest=dest, block_size=block_size, pbar_task=pbar_task) def _download_datasets( # pylint: disable=too-many-arguments - s3_base_url: str, + data_name: str, folder_path: Path, - data_paths: list[DataPath], + dataset_urls: list[str], + dataset_ids: list[str], attributes: Optional[Iterable[str]], force: bool, block_size: int, num_threads: int, pbar: Optional[progress.Progress], ) -> list[Path]: - """Downloads the datasets with given ``data_paths`` to ``folder_path``, copying the - directory structure of the bucket at ``s3_base_url``. + """Downloads the datasets with given ``dataset_urls`` to ``folder_path``. If ``pbar`` is provided, a progress task will be added for each requested dataset. Returns: list[Path]: List of downloaded dataset paths """ - # URL-escape special characters like '+', '$', and '%' in the data path - s3_urls = [f"{s3_base_url}/{urllib.parse.quote(str(data_path))}" for data_path in data_paths] + file_names = [dataset_id + ".h5" for dataset_id in dataset_ids] + dest_paths = [folder_path / data_name / data_id for data_id in file_names] - dest_paths = [folder_path / data_path for data_path in data_paths] for path_parents in set(path.parent for path in dest_paths): path_parents.mkdir(parents=True, exist_ok=True) if pbar is not None: if attributes is None: - file_sizes = [int(head(s3_url).headers["Content-Length"]) for s3_url in s3_urls] + file_sizes = [int(head(url).headers["Content-Length"]) for url in dataset_urls] else: # Can't get file sizes for partial downloads - file_sizes = (None for _ in s3_urls) + file_sizes = (None for _ in dataset_urls) pbar_tasks = [ pbar.add_task(str(dest_path.relative_to(folder_path)), total=file_size) @@ -190,11 +198,11 @@ def _download_datasets( # pylint: disable=too-many-arguments pbar_tasks = (None for _ in dest_paths) with futures.ThreadPoolExecutor(min(num_threads, len(dest_paths))) as pool: - for s3_url, dest_path, pbar_task in zip(s3_urls, dest_paths, pbar_tasks): + for url, dest_path, pbar_task in zip(dataset_urls, dest_paths, pbar_tasks): futs = [ pool.submit( _download_dataset, - s3_url, + url, dest_path, attributes=attributes, force=force, @@ -209,12 +217,11 @@ def _download_datasets( # pylint: disable=too-many-arguments return dest_paths -def _validate_attributes(data_struct: dict, data_name: str, attributes: Iterable[str]): +def _validate_attributes(data_name: str, attributes: Iterable[str]): """Checks that ``attributes`` contains only valid attributes for the given ``data_name``. If any attributes do not exist, raise a ValueError.""" - invalid_attributes = [ - attr for attr in attributes if attr not in data_struct[data_name]["attributes"] - ] + valid_attributes = list_attributes(data_name) + invalid_attributes = [attr for attr in attributes if attr not in valid_attributes] if not invalid_attributes: return @@ -223,7 +230,7 @@ def _validate_attributes(data_struct: dict, data_name: str, attributes: Iterable else: values_err = f"{invalid_attributes} are invalid attributes for '{data_name}'" - raise ValueError(f"{values_err}. Valid attributes are: {data_struct[data_name]['attributes']}") + raise ValueError(f"{values_err}. Valid attributes are: {valid_attributes}") def load( # pylint: disable=too-many-arguments @@ -257,7 +264,7 @@ def load( # pylint: disable=too-many-arguments Returns: list[:class:`~pennylane.data.Dataset`] - .. seealso:: :func:`~.load_interactive`, :func:`~.list_attributes`, :func:`~.list_datasets`. + .. seealso:: :func:`~.load_interactive`, :func:`~.list_attributes`, :func:`~.list_data_names`. **Example** @@ -320,25 +327,39 @@ def load( # pylint: disable=too-many-arguments >>> print(circuit()) -1.0791430411076344 """ - foldermap = _get_foldermap() - data_struct = _get_data_struct() - params = format_params(**params) if attributes: - _validate_attributes(data_struct, data_name, attributes) + _validate_attributes(data_name, attributes) + + folder_path = Path(folder_path) + + if data_name == "other": + data_name = params[0]["values"][0] + params = [] + + params = provide_defaults(data_name, params) + params = [param for param in params if ("values", ParamArg.FULL) not in list(param.items())] + + dataset_ids_and_urls = get_dataset_urls(data_name, params) + if dataset_ids_and_urls == []: + raise ValueError( + "No datasets exist for the provided configuration.\n" + "Please check the available datasets by using the ``qml.data.list_datasets()`` function." + ) - folder_path = Path(folder_path).resolve() - data_paths = [data_path for _, data_path in foldermap.find(data_name, **params)] + dataset_urls = [dataset_url for _, dataset_url in dataset_ids_and_urls] + dataset_ids = [dataset_id for dataset_id, _ in dataset_ids_and_urls] progress_bar = progress_bar if progress_bar is not None else sys.stdout.isatty() if progress_bar: with progress.Progress() as pbar: download_paths = _download_datasets( - S3_URL, + data_name, folder_path, - data_paths, + dataset_urls, + dataset_ids, attributes, force=force, block_size=block_size, @@ -348,9 +369,10 @@ def load( # pylint: disable=too-many-arguments else: download_paths = _download_datasets( - S3_URL, + data_name, folder_path, - data_paths, + dataset_urls, + dataset_ids, attributes, force=force, block_size=block_size, @@ -402,69 +424,55 @@ def remove_paths(foldermap): return remove_paths(_get_foldermap()) -def list_attributes(data_name): - r"""List the attributes that exist for a specific ``data_name``. +def _interactive_request_data_name(data_names): + """Prompt the user to select a data name.""" + print("Please select the data name from the following:") + for i, option in enumerate(data_names): + print(f"{i + 1}: {option}") + choice = input("Choice of data name: ").strip() + if choice not in data_names: + raise ValueError(f"Must select a single data name from {data_names}") + return choice - Args: - data_name (str): The type of the desired data - Returns: - list (str): A list of accepted attributes for a given data name +def _interactive_request_attributes(attribute_options): + """Prompt the user to select a list of attributes.""" + print( + 'Please select a list of attributes from the following available attributes or "full" for all attributes.' + ) + for i, option in enumerate(attribute_options): + print(f"{i + 1}: {option}") - .. seealso:: :func:`~.load_interactive`, :func:`~.list_datasets`, :func:`~.load`. + choice_input = input("Comma-separated list of attributes: ") + choices = [str(choice).strip() for choice in choice_input.strip("[]").split(",")] + if "full" in choices: + return attribute_options + if not (choices and set(choices).issubset(set(attribute_options))): + raise ValueError(f"Must select a list of attributes from {attribute_options}") - **Example** + return choices - >>> qml.data.list_attributes(data_name="qchem") - ['molname', - 'basis', - 'bondlength', - ... - 'vqe_params', - 'vqe_energy'] - """ - data_struct = _get_data_struct() - if data_name not in data_struct: - raise ValueError( - f"Currently the hosted datasets are of types: {list(data_struct)}, but got {data_name}." - ) - return data_struct[data_name]["attributes"] +def _interactive_requests(parameters, parameter_tree): + """Prompts the user to select parameters for datasets one at a time.""" -def _interactive_request_attributes(options): - """Prompt the user to select a list of attributes.""" - prompt = "Please select attributes:" - for i, option in enumerate(options): - if option == "full": - option = "full (all attributes)" - prompt += f"\n\t{i+1}) {option}" - print(prompt) - choices = input(f"Choice (comma-separated list of options) [1-{len(options)}]: ").split(",") - try: - choices = list(map(int, choices)) - except ValueError as e: - raise ValueError(f"Must enter a list of integers between 1 and {len(options)}") from e - if any(choice < 1 or choice > len(options) for choice in choices): - raise ValueError(f"Must enter a list of integers between 1 and {len(options)}") - return [options[choice - 1] for choice in choices] - - -def _interactive_request_single(node, param): - """Prompt the user to select a single option from a list.""" - options = list(node) - if len(options) == 1: - print(f"Using {options[0]} as it is the only {param} available.") - sleep(1) - return options[0] - print(f"Please select a {param}:") - print("\n".join(f"\t{i+1}) {option}" for i, option in enumerate(options))) - try: - choice = int(input(f"Choice [1-{len(options)}]: ")) - except ValueError as e: - raise ValueError(f"Must enter an integer between 1 and {len(options)}") from e - if choice < 1 or choice > len(options): - raise ValueError(f"Must enter an integer between 1 and {len(options)}") - return options[choice - 1] + branch = parameter_tree + for param in parameters: + + if len(branch["next"]) == 1: + branch = next(iter(branch["next"].values())) + continue + + print(f"Available options for {param}:") + for i, option in enumerate(branch["next"].keys()): + print(f"{i + 1}: {option}") + user_value = input(f"Please select a {param}:").strip() + try: + branch = branch["next"][user_value] + except KeyError as e: + raise ValueError(f"Must enter a valid {param}:") from e + + return branch def load_interactive(): @@ -475,14 +483,15 @@ def load_interactive(): **Example** - .. seealso:: :func:`~.load`, :func:`~.list_attributes`, :func:`~.list_datasets`. + .. seealso:: :func:`~.load`, :func:`~.list_attributes`, :func:`~.list_data_names`. .. code-block :: pycon >>> qml.data.load_interactive() - Please select a data name: - 1) qspin - 2) qchem + Please select the data name from the following: + 1: qspin + 2: qchem + 3: other Choice [1-2]: 1 Please select a sysname: ... @@ -503,37 +512,24 @@ def load_interactive(): force: False dest folder: /Users/jovyan/Downloads/datasets Would you like to continue? (Default is yes) [Y/n]: - """ - foldermap = _get_foldermap() - data_struct = _get_data_struct() + data_names = list_data_names() + data_name = _interactive_request_data_name(data_names) - node = foldermap - data_name = _interactive_request_single(node, "data name") + parameters, attribute_options, parameter_tree = _get_parameter_tree(data_name) - description = {} - value = data_name - - params = data_struct[data_name]["params"] - for param in params: - node = node[value] - value = _interactive_request_single(node, param) - description[param] = value - - attributes = _interactive_request_attributes( - [attribute for attribute in data_struct[data_name]["attributes"] if attribute not in params] - ) + dataset_id = _interactive_requests(parameters, parameter_tree) + attributes = _interactive_request_attributes(attribute_options) force = input("Force download files? (Default is no) [y/N]: ") in ["y", "Y"] dest_folder = Path( input("Folder to download to? (Default is pwd, will download to /datasets subdirectory): ") ) - print("\nPlease confirm your choices:") - print("dataset:", "/".join([data_name] + [description[param] for param in params])) print("attributes:", attributes) print("force:", force) print("dest folder:", dest_folder / "datasets") + print("dataset:", dataset_id) approve = input("Would you like to continue? (Default is yes) [Y/n]: ") if approve not in ["Y", "", "y"]: @@ -545,14 +541,13 @@ def load_interactive(): attributes=attributes, folder_path=dest_folder, force=force, - **description, )[0] __all__ = ( "load", "load_interactive", - "list_datasets", + "list_data_names", "list_attributes", "FULL", "DEFAULT", diff --git a/pennylane/data/data_manager/foldermap.py b/pennylane/data/data_manager/foldermap.py index f7324fa343e..8a511375252 100644 --- a/pennylane/data/data_manager/foldermap.py +++ b/pennylane/data/data_manager/foldermap.py @@ -38,7 +38,7 @@ class FolderMapView(Mapping[str, Union["FolderMapView", DataPath]]): file. A dictionary in the folder map can optionally specify a default - paramater using the '__default' key. This view hides that + parameter using the '__default' key. This view hides that key, and allows the default parameter to be accessed. For example, the underlying foldermap data will look like diff --git a/pennylane/data/data_manager/graphql.py b/pennylane/data/data_manager/graphql.py new file mode 100644 index 00000000000..c1057468ce3 --- /dev/null +++ b/pennylane/data/data_manager/graphql.py @@ -0,0 +1,155 @@ +""" +Module for containing graphql functionality for interacting with the Datasets Service API. +""" + +import os +from typing import Any, Optional + +from requests import post + +GRAPHQL_URL = os.getenv("DATASETS_ENDPOINT_URL", "https://cloud.pennylane.ai/graphql") + + +class GraphQLError(BaseException): + """Exception for GraphQL""" + + +def get_graphql(url: str, query: str, variables: Optional[dict[str, Any]] = None): + """ + Args: + url: The URL to send a query to. + query: The main body of the query to be sent. + variables: Additional input variables to the query body. + + Returns: + string: json response. + GraphQLError: if there no response is received or errors are received in the json response. + """ + + json = {"query": query} + + if variables: + json["variables"] = variables + + response = post(url=url, json=json, timeout=10, headers={"content-type": "application/json"}) + response.raise_for_status() + if "errors" in response.json(): + all_errors = ",".join(error["message"] for error in response.json()["errors"]) + raise GraphQLError(f"Errors in request: {all_errors}") + + return response.json() + + +def get_dataset_urls(class_id: str, parameters: dict[str, list[str]]) -> list[tuple[str, str]]: + """ + Args: + class_id: Dataset class id e.g 'qchem', 'qspin' + parameters: Dataset parameters e.g 'molname', 'basis' + + Returns: + list of tuples (dataset_id, dataset_url) + + Example usage: + >>> get_dataset_urls("qchem", {"molname": ["H2"], "basis": ["STO-3G"], "bondlength": ["0.5"]}) + [("H2_STO-3G_0.5", "https://cloud.pennylane.ai/datasets/h5/qchem/h2/sto-3g/0.5.h5")] + """ + + response = get_graphql( + GRAPHQL_URL, + """ + query GetDatasetsForDownload($datasetClassId: String!, $parameters: [DatasetParameterInput!]) { + datasetClass(id: $datasetClassId) { + datasets(parameters: $parameters) { + id + downloadUrl + } + } + } + """, + {"datasetClassId": class_id, "parameters": parameters}, + ) + + return [ + (resp["id"], resp["downloadUrl"]) for resp in response["data"]["datasetClass"]["datasets"] + ] + + +def list_data_names() -> list[str]: + """Get list of dataclass IDs.""" + response = get_graphql( + GRAPHQL_URL, + """ + query GetDatasetClasses { + datasetClasses { + id + } + } + """, + ) + return [dsc["id"] for dsc in response["data"]["datasetClasses"]] + + +def list_attributes(data_name) -> list[str]: + r"""List the attributes that exist for a specific ``data_name``. + + Args: + data_name (str): The type of the desired data + + Returns: + list (str): A list of accepted attributes for a given data name + + .. seealso:: :func:`~.load_interactive`, :func:`~.list_data_names`, :func:`~.load`. + + **Example** + + >>> qml.data.list_attributes(data_name="qchem") + ['molname', + 'basis', + 'bondlength', + ... + 'vqe_params', + 'vqe_energy'] + """ + + response = get_graphql( + GRAPHQL_URL, + """ + query ListAttributes($datasetClassId: String!) { + datasetClass(id: $datasetClassId) { + attributes { + name + } + } + } + """, + {"datasetClassId": data_name}, + ) + + return [attribute["name"] for attribute in response["data"]["datasetClass"]["attributes"]] + + +def _get_parameter_tree(class_id) -> tuple[list[str], list[str], dict]: + """Returns the (parameters, attributes, parameter_tree) for a given ``class_id``.""" + + response = get_graphql( + GRAPHQL_URL, + """ + query GetParameterTree($datasetClassId: String!) { + datasetClass(id: $datasetClassId) { + attributes { + name + } + parameters { + name + } + parameterTree + } + } + """, + {"datasetClassId": class_id}, + ) + + parameters = [param["name"] for param in response["data"]["datasetClass"]["parameters"]] + attributes = [atr["name"] for atr in response["data"]["datasetClass"]["attributes"]] + + return (parameters, attributes, response["data"]["datasetClass"]["parameterTree"]) diff --git a/pennylane/data/data_manager/params.py b/pennylane/data/data_manager/params.py index 16c3fe8e5c5..de5a3be27db 100644 --- a/pennylane/data/data_manager/params.py +++ b/pennylane/data/data_manager/params.py @@ -126,9 +126,36 @@ def format_param_args(param: ParamName, details: Any) -> Union[ParamArg, list[Pa return details -def format_params(**params: Any) -> dict[ParamName, Union[ParamArg, ParamVal]]: - """Converts params to a dictionary whose keys are parameter names and - whose values are single ``ParamaterArg`` objects or lists of parameter values.""" - return { +def format_params(**params: Any) -> list[dict[str:ParamName, str : Union[ParamArg, ParamVal]]]: + """Converts params to a list of dictionaries whose values are parameter names and + single ``ParamaterArg`` objects or lists of parameter values.""" + + input_params = { param_name: format_param_args(param_name, param) for param_name, param in params.items() } + return [{"name": k, "values": v} for k, v in input_params.items()] + + +def provide_defaults( + data_name: str, params: list[dict[str:ParamName, str : Union[ParamArg, ParamVal]]] +) -> list[dict[str:ParamName, str : Union[ParamArg, ParamVal]]]: + """ + Provides default parameters to the qchem and qspin query parameters if the parameter + names are missing from the provided ``params``. + """ + param_names = [param["name"] for param in params] + if data_name == "qchem": + if "basis" not in param_names: + params.append({"default": True, "name": "basis"}) + if "bondlength" not in param_names: + params.append({"default": True, "name": "bondlength"}) + + if data_name == "qspin": + if "periodicity" not in param_names: + params.append({"default": True, "name": "periodicity"}) + if "lattice" not in param_names: + params.append({"default": True, "name": "lattice"}) + if "layout" not in param_names: + params.append({"default": True, "name": "layout"}) + + return params diff --git a/pennylane/data/data_manager/progress/__init__.py b/pennylane/data/data_manager/progress/__init__.py index f11d8c2595f..3e9df7d8274 100644 --- a/pennylane/data/data_manager/progress/__init__.py +++ b/pennylane/data/data_manager/progress/__init__.py @@ -15,14 +15,10 @@ from typing import Any, Optional -from pennylane.data.data_manager.progress._default import ( - make_progress as make_progress_default, -) +from pennylane.data.data_manager.progress._default import make_progress as make_progress_default try: - from pennylane.data.data_manager.progress._rich import ( - make_progress as make_progress_rich, - ) + from pennylane.data.data_manager.progress._rich import make_progress as make_progress_rich except ImportError: # pragma: no cover make_progress_rich = None diff --git a/tests/data/data_manager/__init__.py b/tests/data/data_manager/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/data/data_manager/support.py b/tests/data/data_manager/support.py new file mode 100644 index 00000000000..d7bea1532bf --- /dev/null +++ b/tests/data/data_manager/support.py @@ -0,0 +1,1483 @@ +"""Test support for mocking GraphQL queries""" + +_list_attrs_resp = { + "data": { + "datasetClass": { + "attributes": [ + {"name": "basis_rot_groupings"}, + {"name": "basis_rot_samples"}, + {"name": "dipole_op"}, + {"name": "fci_energy"}, + {"name": "fci_spectrum"}, + {"name": "hamiltonian"}, + {"name": "hf_state"}, + {"name": "molecule"}, + {"name": "number_op"}, + {"name": "optimal_sector"}, + {"name": "paulix_ops"}, + {"name": "qwc_groupings"}, + {"name": "qwc_samples"}, + {"name": "sparse_hamiltonian"}, + {"name": "spin2_op"}, + {"name": "spinz_op"}, + {"name": "symmetries"}, + {"name": "tapered_dipole_op"}, + {"name": "tapered_hamiltonian"}, + {"name": "tapered_hf_state"}, + {"name": "tapered_num_op"}, + {"name": "tapered_spin2_op"}, + {"name": "tapered_spinz_op"}, + {"name": "vqe_energy"}, + {"name": "vqe_gates"}, + {"name": "vqe_params"}, + ] + } + } +} + +_get_urls_resp = { + "data": { + "datasetClass": { + "datasets": [ + { + "id": "h2_sto-3g_0.46", + "downloadUrl": "https://cloud.pennylane.ai/datasets/download/h2_sto-3g_0.46", + }, + { + "id": "h2_sto-3g_1.16", + "downloadUrl": "https://cloud.pennylane.ai/datasets/download/h2_sto-3g_1.16", + }, + { + "id": "h2_sto-3g_1.0", + "downloadUrl": "https://cloud.pennylane.ai/datasets/download/h2_sto-3g_1.0", + }, + ] + } + } +} + +_rydberggpt_url_resp = { + "data": { + "datasetClass": { + "id": "rydberggpt", + "datasets": [ + { + "id": "rydberggpt", + "downloadUrl": "https://cloud.pennylane.ai/datasets/v2/download/rydberggpt", + } + ], + } + } +} + +_dataclass_ids = {"data": {"datasetClasses": [{"id": "other"}, {"id": "qchem"}, {"id": "qspin"}]}} + +_error_response = {"data": None, "errors": [{"message": "Mock error message."}]} + +_parameter_tree = { + "data": { + "datasetClass": { + "attributes": [ + {"name": "ground_energies"}, + {"name": "ground_states"}, + {"name": "hamiltonians"}, + {"name": "num_phases"}, + {"name": "order_params"}, + {"name": "parameters"}, + {"name": "shadow_basis"}, + {"name": "shadow_meas"}, + {"name": "spin_system"}, + ], + "parameters": [ + {"name": "sysname"}, + {"name": "periodicity"}, + {"name": "lattice"}, + {"name": "layout"}, + ], + "parameterTree": { + "next": { + "Ising": { + "next": { + "open": { + "next": { + "chain": { + "next": { + "1x4": "transverse-field-ising-model-ising-open-chain-1x4", + "1x8": "transverse-field-ising-model-ising-open-chain-1x8", + "1x16": "transverse-field-ising-model-ising-open-chain-1x16", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "transverse-field-ising-model-ising-open-rectangular-2x2", + "2x4": "transverse-field-ising-model-ising-open-rectangular-2x4", + "2x8": "transverse-field-ising-model-ising-open-rectangular-2x8", + "4x4": "transverse-field-ising-model-ising-open-rectangular-4x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + "closed": { + "next": { + "chain": { + "next": { + "1x4": "transverse-field-ising-model-ising-closed-chain-1x4", + "1x8": "transverse-field-ising-model-ising-closed-chain-1x8", + "1x16": "transverse-field-ising-model-ising-closed-chain-1x16", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "transverse-field-ising-model-ising-closed-rectangular-2x2", + "2x4": "transverse-field-ising-model-ising-closed-rectangular-2x4", + "2x8": "transverse-field-ising-model-ising-closed-rectangular-2x8", + "4x4": "transverse-field-ising-model-ising-closed-rectangular-4x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + }, + "default": "open", + }, + "Heisenberg": { + "next": { + "open": { + "next": { + "chain": { + "next": { + "1x4": "xxz-heisenberg-model-heisenberg-open-chain-1x4", + "1x8": "xxz-heisenberg-model-heisenberg-open-chain-1x8", + "1x16": "xxz-heisenberg-model-heisenberg-open-chain-1x16", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "xxz-heisenberg-model-heisenberg-open-rectangular-2x2", + "2x4": "xxz-heisenberg-model-heisenberg-open-rectangular-2x4", + "2x8": "xxz-heisenberg-model-heisenberg-open-rectangular-2x8", + "4x4": "xxz-heisenberg-model-heisenberg-open-rectangular-4x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + "closed": { + "next": { + "chain": { + "next": { + "1x4": "xxz-heisenberg-model-heisenberg-closed-chain-1x4", + "1x8": "xxz-heisenberg-model-heisenberg-closed-chain-1x8", + "1x16": "xxz-heisenberg-model-heisenberg-closed-chain-1x16", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "xxz-heisenberg-model-heisenberg-closed-rectangular-2x2", + "2x4": "xxz-heisenberg-model-heisenberg-closed-rectangular-2x4", + "2x8": "xxz-heisenberg-model-heisenberg-closed-rectangular-2x8", + "4x4": "xxz-heisenberg-model-heisenberg-closed-rectangular-4x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + }, + "default": "open", + }, + "BoseHubbard": { + "next": { + "open": { + "next": { + "chain": { + "next": { + "1x4": "bose-hubbard-model-bosehubbard-open-chain-1x4", + "1x8": "bose-hubbard-model-bosehubbard-open-chain-1x8", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "bose-hubbard-model-bosehubbard-open-rectangular-2x2", + "2x4": "bose-hubbard-model-bosehubbard-open-rectangular-2x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + "closed": { + "next": { + "chain": { + "next": { + "1x4": "bose-hubbard-model-bosehubbard-closed-chain-1x4", + "1x8": "bose-hubbard-model-bosehubbard-closed-chain-1x8", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "bose-hubbard-model-bosehubbard-closed-rectangular-2x2", + "2x4": "bose-hubbard-model-bosehubbard-closed-rectangular-2x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + }, + "default": "open", + }, + "FermiHubbard": { + "next": { + "open": { + "next": { + "chain": { + "next": { + "1x4": "fermi-hubbard-model-fermihubbard-open-chain-1x4", + "1x8": "fermi-hubbard-model-fermihubbard-open-chain-1x8", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "fermi-hubbard-model-fermihubbard-open-rectangular-2x2", + "2x4": "fermi-hubbard-model-fermihubbard-open-rectangular-2x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + "closed": { + "next": { + "chain": { + "next": { + "1x4": "fermi-hubbard-model-fermihubbard-closed-chain-1x4", + "1x8": "fermi-hubbard-model-fermihubbard-closed-chain-1x8", + }, + "default": "1x4", + }, + "rectangular": { + "next": { + "2x2": "fermi-hubbard-model-fermihubbard-closed-rectangular-2x2", + "2x4": "fermi-hubbard-model-fermihubbard-closed-rectangular-2x4", + }, + "default": "2x2", + }, + }, + "default": "chain", + }, + }, + "default": "open", + }, + }, + "default": None, + }, + } + } +} + + +_qchem_parameter_tree = { + "data": { + "datasetClass": { + "attributes": [ + {"name": "basis_rot_groupings"}, + {"name": "basis_rot_samples"}, + {"name": "dipole_op"}, + {"name": "fci_energy"}, + {"name": "fci_spectrum"}, + {"name": "hamiltonian"}, + {"name": "hf_state"}, + {"name": "molecule"}, + {"name": "number_op"}, + {"name": "optimal_sector"}, + {"name": "paulix_ops"}, + {"name": "qwc_groupings"}, + {"name": "qwc_samples"}, + {"name": "sparse_hamiltonian"}, + {"name": "spin2_op"}, + {"name": "spinz_op"}, + {"name": "symmetries"}, + {"name": "tapered_dipole_op"}, + {"name": "tapered_hamiltonian"}, + {"name": "tapered_hf_state"}, + {"name": "tapered_num_op"}, + {"name": "tapered_spin2_op"}, + {"name": "tapered_spinz_op"}, + {"name": "vqe_energy"}, + {"name": "vqe_gates"}, + {"name": "vqe_params"}, + ], + "parameters": [ + {"name": "molname"}, + {"name": "basis"}, + {"name": "bondlength"}, + {"name": "bondangle"}, + {"name": "number_of_spin_orbitals"}, + ], + "parameterTree": { + "next": { + "C2": { + "next": { + "STO-3G": { + "next": { + "0.5": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-0.5-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "0.7": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-0.7-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "0.9": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-0.9-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.1": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-1.1-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.3": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-1.3-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.5": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-1.5-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.7": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-1.7-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.9": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-1.9-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "2.1": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-2.1-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "2.3": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-2.3-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "2.5": { + "next": { + "N\\A": { + "next": {"20": "c2-molecule-c2-sto-3g-2.5-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.246": { + "next": { + "N\\A": { + "next": { + "20": "c2-molecule-c2-sto-3g-1.246-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + }, + "default": "1.246", + } + }, + "default": "STO-3G", + }, + "CO": { + "next": { + "STO-3G": { + "next": { + "0.5": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-0.5-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "0.7": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-0.7-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "0.9": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-0.9-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.1": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-1.1-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.3": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-1.3-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.5": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-1.5-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.7": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-1.7-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.9": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-1.9-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "2.1": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-2.1-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "2.3": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-2.3-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "2.5": { + "next": { + "N\\A": { + "next": {"20": "co-molecule-co-sto-3g-2.5-n\\a-20"}, + "default": None, + } + }, + "default": None, + }, + "1.128": { + "next": { + "N\\A": { + "next": { + "20": "co-molecule-co-sto-3g-1.128-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + }, + "default": "1.128", + } + }, + "default": "STO-3G", + }, + "H2": { + "next": { + "6-31G": { + "next": { + "0.5": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.5-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.7": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.7-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.9": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.9-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.1": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.1-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.3": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.3-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.5": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.5-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.7": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.7-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.9": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.9-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "2.1": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-2.1-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.54": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.54-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.58": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.58-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.62": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.62-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.66": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.66-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.74": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.74-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.78": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.78-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.82": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.82-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.86": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.86-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.94": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.94-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.98": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.98-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.02": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.02-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.06": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.06-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.14": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.14-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.18": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.18-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.22": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.22-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.26": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.26-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.34": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.34-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.38": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.38-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.42": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.42-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.46": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.46-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.54": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.54-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.58": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.58-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.62": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.62-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.66": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.66-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.74": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.74-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.78": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.78-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.82": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.82-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.86": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.86-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.94": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.94-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "1.98": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-1.98-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "2.02": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-2.02-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "2.06": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-2.06-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + "0.742": { + "next": { + "N\\A": { + "next": {"8": "h2-molecule-h2-6-31g-0.742-n\\a-8"}, + "default": None, + } + }, + "default": None, + }, + }, + "default": "0.742", + }, + "STO-3G": { + "next": { + "0.5": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.5-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.7": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.7-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.9": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.9-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.1": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.1-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.3": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.3-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.5": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.5-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.7": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.7-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.9": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.9-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "2.1": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-2.1-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.54": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.54-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.58": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.58-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.62": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.62-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.66": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.66-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.74": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.74-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.78": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.78-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.82": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.82-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.86": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.86-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.94": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.94-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.98": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.98-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.02": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.02-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.06": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.06-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.14": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.14-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.18": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.18-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.22": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.22-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.26": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.26-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.34": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.34-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.38": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.38-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.42": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.42-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.46": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.46-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.54": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.54-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.58": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.58-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.62": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.62-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.66": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.66-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.74": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.74-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.78": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.78-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.82": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.82-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.86": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.86-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.94": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.94-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "1.98": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-1.98-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "2.02": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-2.02-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "2.06": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-2.06-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + "0.742": { + "next": { + "N\\A": { + "next": {"4": "h2-molecule-h2-sto-3g-0.742-n\\a-4"}, + "default": None, + } + }, + "default": None, + }, + }, + "default": "0.742", + }, + "CC-PVDZ": { + "next": { + "0.5": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-0.5-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "0.7": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-0.7-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "0.9": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-0.9-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "1.1": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-1.1-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "1.3": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-1.3-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "1.5": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-1.5-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "1.7": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-1.7-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "1.9": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-1.9-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "2.1": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-2.1-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "2.3": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-2.3-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "2.5": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-2.5-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + "0.742": { + "next": { + "N\\A": { + "next": { + "20": "h2-molecule-h2-cc-pvdz-0.742-n\\a-20" + }, + "default": None, + } + }, + "default": None, + }, + }, + "default": "0.742", + }, + }, + "default": "STO-3G", + }, + } + }, + } + } +} diff --git a/tests/data/data_manager/test_dataset_access.py b/tests/data/data_manager/test_dataset_access.py index 7157c0cd62a..b0b3fdcdc06 100644 --- a/tests/data/data_manager/test_dataset_access.py +++ b/tests/data/data_manager/test_dataset_access.py @@ -15,6 +15,7 @@ Unit tests for the :class:`pennylane.data.data_manager` functions. """ import os +import re from pathlib import Path, PosixPath from typing import NamedTuple from unittest.mock import MagicMock, call, patch @@ -25,7 +26,18 @@ import pennylane as qml import pennylane.data.data_manager from pennylane.data import Dataset -from pennylane.data.data_manager import S3_URL, _validate_attributes +from pennylane.data.data_manager import _validate_attributes +from pennylane.data.data_manager.graphql import GRAPHQL_URL + +from .support import ( + _dataclass_ids, + _error_response, + _get_urls_resp, + _list_attrs_resp, + _parameter_tree, + _qchem_parameter_tree, + _rydberggpt_url_resp, +) has_rich = False try: @@ -76,11 +88,6 @@ } -@pytest.fixture(scope="session") -def httpserver_listen_address(): - return ("localhost", 8888) - - # pylint:disable=unused-argument def get_mock(url, timeout=1.0): """Return the foldermap or data_struct according to URL""" @@ -89,18 +96,6 @@ def get_mock(url, timeout=1.0): return resp -def head_mock(url): - """Return a fake header stating content-length is 1.""" - return NamedTuple("Head", headers=dict)(headers={"Content-Length": 10000}) - - -@pytest.fixture -def mock_get_args(): - """A Mock object that tracks the arguments passed to ``mock_requests_get``.""" - - return MagicMock() - - @pytest.fixture(autouse=True) def mock_requests_get(request, monkeypatch, mock_get_args): """Patches `requests.get()` in the data_manager module so that @@ -136,6 +131,68 @@ def mock_iter_content(chunk_size: int): return mock_get +@pytest.fixture(scope="session") +def httpserver_listen_address(): + return ("localhost", 8888) + + +# pylint:disable=unused-argument +# pylint:disable=dangerous-default-value +def post_mock(url, json, timeout=1.0, headers={"content-type": "application/json"}): + """Return mocked get response depending on json content.""" + resp = MagicMock(ok=True) + if "ErrorQuery" in json["query"]: + resp.json.return_value = _error_response + elif "ListAttributes" in json["query"][0]: + resp.json.return_value = _list_attrs_resp + return resp + + +# pylint:disable=unused-argument +def graphql_mock(url, query, variables=None): + """Return the JSON according to the query.""" + if "ListAttributes" in query: + json_data = _list_attrs_resp + elif variables is not None and variables["datasetClassId"] == "rydberggpt": + json_data = _rydberggpt_url_resp + elif "GetDatasetsForDownload" in query: + json_data = _get_urls_resp + elif "GetParameterTree" in query: + json_data = _parameter_tree + elif "GetDatasetClasses" in query: + json_data = _dataclass_ids + return json_data + + +# pylint:disable=unused-argument +def graphql_mock_qchem(url, query, variables=None): + """Return the JSON according to the query.""" + if "ListAttributes" in query: + json_data = _list_attrs_resp + elif "GetParameterTree" in query: + json_data = _qchem_parameter_tree + elif "GetDatasetClasses" in query: + json_data = _dataclass_ids + return json_data + + +def get_dataset_urls_mock(class_id, parameters): + """Returns an empty response for the ``get_dataset_urls`` function.""" + return [] + + +def head_mock(url): + """Return a fake header stating content-length is 1.""" + return NamedTuple("Head", headers=dict)(headers={"Content-Length": 10000}) + + +@pytest.fixture +def mock_get_args(): + """A Mock object that tracks the arguments passed to ``mock_requests_get``.""" + + return MagicMock() + + def submit_download_mock(_self, _fetch_and_save, filename, dest_folder): """Patch to write a nonsense dataset rather than a downloaded one.""" # If filename == foo/bar/x_y_z_attr.dat, content == "x_y_z_attr" @@ -159,7 +216,6 @@ def mock_load(monkeypatch): return mock -@patch.object(requests, "get", get_mock) @patch.object(pennylane.data.data_manager, "head", head_mock) @patch("pennylane.data.data_manager.sleep") @patch("builtins.input") @@ -169,63 +225,83 @@ class TestLoadInteractive: [data name, *params, attributes, Force, Folder, continue] """ + @patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @pytest.mark.parametrize( - ("side_effect", "data_name", "kwargs", "sleep_call_count"), + ("side_effect"), [ ( - ["1", "1", "2", "", "", ""], - "qchem", - { - "attributes": ["hamiltonian"], - "folder_path": PosixPath(""), - "force": False, - "molname": "H2", - "basis": "6-31G", - "bondlength": "0.46", - }, - 2, + [ + "qspin", + "Heisenberg", + "open", + "chain", + "1x4", + "full", + True, + PosixPath("/my/path"), + "Y", + ] ), ( - ["2", "1, 4", "Y", "/my/path", "y"], - "qspin", - { - "attributes": ["parameters", "full"], - "folder_path": PosixPath("/my/path"), - "force": True, - "sysname": "Heisenberg", - "periodicity": "closed", - "lattice": "chain", - "layout": "1x4", - }, - 4, + [ + "qspin", + "Heisenberg", + "open", + "chain", + "1x4", + "[parameters, shadow_basis, shadow_meas]", + True, + PosixPath("/my/path"), + "Y", + ] ), ], ) def test_load_interactive_success( - self, mock_input, mock_sleep, mock_load, side_effect, data_name, kwargs, sleep_call_count + self, + mock_input, + mock_sleep, + mock_load, + side_effect, ): # pylint:disable=too-many-arguments, redefined-outer-name """Test that load_interactive succeeds.""" mock_input.side_effect = side_effect assert isinstance(qml.data.load_interactive(), qml.data.Dataset) - mock_load.assert_called_once_with(data_name, **kwargs) - assert mock_sleep.call_count == sleep_call_count + @patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) def test_load_interactive_without_confirm( self, mock_input, _mock_sleep, mock_load ): # pylint:disable=redefined-outer-name """Test that load_interactive returns None if the user doesn't confirm.""" - mock_input.side_effect = ["1", "1", "2", "", "", "n"] + mock_input.side_effect = [ + "qspin", + "Heisenberg", + "open", + "chain", + "1x4", + "full", + True, + PosixPath("/my/path"), + "n", + ] assert qml.data.load_interactive() is None mock_load.assert_not_called() + @patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @pytest.mark.parametrize( ("side_effect", "error_message"), [ - (["foo"], "Must enter an integer between 1 and 2"), - (["0"], "Must enter an integer between 1 and 2"), - (["3"], "Must enter an integer between 1 and 2"), - (["1", "1", "0"], "Must enter a list of integers between 1 and 5"), - (["1", "1", "1 2"], "Must enter a list of integers between 1 and 5"), + (["foo"], re.escape("Must select a single data name from ['other', 'qchem', 'qspin']")), + (["qspin", "foo"], "Must enter a valid sysname:"), + (["qspin", "Ising", "foo"], "Must enter a valid periodicity:"), + (["qspin", "Ising", "open", "foo"], "Must enter a valid lattice:"), + (["qspin", "Ising", "open", "chain", "foo"], "Must enter a valid layout:"), + ( + ["qspin", "Ising", "open", "chain", "1x4", "foo"], + re.escape( + "Must select a list of attributes from ['ground_energies', 'ground_states', 'hamiltonians', 'num_phases', 'order_params', 'parameters', 'shadow_basis', 'shadow_meas', 'spin_system']" + ), + ), ], ) def test_load_interactive_invalid_inputs( @@ -236,11 +312,45 @@ def test_load_interactive_invalid_inputs( with pytest.raises(ValueError, match=error_message): qml.data.load_interactive() + @patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock_qchem) + @pytest.mark.parametrize( + ("side_effect"), + [ + ( + [ + "qchem", + "H2", + "STO-3G", + "0.742", + "full", + True, + PosixPath("/my/path"), + "Y", + ] + ), + ], + ) + def test_load_interactive_qchem( + self, + mock_input, + mock_sleep, + mock_load, + side_effect, + ): + """Test that load_interactive succeeds.""" + mock_input.side_effect = side_effect + assert isinstance(qml.data.load_interactive(), qml.data.Dataset) + -@patch.object(requests, "get", get_mock) class TestMiscHelpers: """Test miscellaneous helper functions in data_manager.""" + @patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) + def test_list_data_names(self): + """Test list_data_names.""" + assert qml.data.list_data_names() == ["other", "qchem", "qspin"] + + @patch.object(requests, "get", get_mock) def test_list_datasets(self, tmp_path): """Test that list_datasets returns either the S3 foldermap, or the local tree.""" assert qml.data.list_datasets() == { @@ -248,11 +358,37 @@ def test_list_datasets(self, tmp_path): "qchem": {"H2": {"6-31G": ["0.46", "1.0", "1.16"]}}, } + @patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) def test_list_attributes(self): - """Test list_attributes""" - assert qml.data.list_attributes("qchem") == _data_struct["qchem"]["attributes"] - with pytest.raises(ValueError, match="Currently the hosted datasets are of types"): - qml.data.list_attributes("invalid_data_name") + """Test list_attributes.""" + assert qml.data.list_attributes("qchem") == [ + "basis_rot_groupings", + "basis_rot_samples", + "dipole_op", + "fci_energy", + "fci_spectrum", + "hamiltonian", + "hf_state", + "molecule", + "number_op", + "optimal_sector", + "paulix_ops", + "qwc_groupings", + "qwc_samples", + "sparse_hamiltonian", + "spin2_op", + "spinz_op", + "symmetries", + "tapered_dipole_op", + "tapered_hamiltonian", + "tapered_hf_state", + "tapered_num_op", + "tapered_spin2_op", + "tapered_spinz_op", + "vqe_energy", + "vqe_gates", + "vqe_params", + ] @pytest.fixture @@ -269,15 +405,17 @@ def mock(data_path, dest, attributes, force, block_size, pbar_task): # pylint: disable=too-many-arguments @patch.object(pennylane.data.data_manager, "head", head_mock) +@patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @pytest.mark.usefixtures("mock_download_dataset") @pytest.mark.parametrize( "data_name, params, expect_paths", [ ( "qchem", - {"molname": "H2", "basis": "6-31G", "bondlength": ["0.46", "1.16"]}, - ["qchem/H2/6-31G/0.46.h5", "qchem/H2/6-31G/1.16.h5"], - ) + {"molname": "H2", "basis": "STO-3G", "bondlength": ["1.0", "0.46", "1.16"]}, + ["qchem/h2_sto-3g_1.0.h5", "qchem/h2_sto-3g_0.46.h5", "qchem/h2_sto-3g_1.16.h5"], + ), + ("other", {"name": "rydberggpt"}, ["rydberggpt/rydberggpt.h5"]), ], ) @pytest.mark.parametrize("progress_bar", [True, False]) @@ -295,12 +433,21 @@ def test_load(tmp_path, data_name, params, expect_paths, progress_bar, attribute attributes=attributes, **params, ) - assert {Path(dset.bind.filename) for dset in dsets} == { Path(tmp_path, path) for path in expect_paths } +@patch.object(pennylane.data.data_manager, "get_dataset_urls", get_dataset_urls_mock) +def test_load_bad_config(): + msg = re.escape( + """No datasets exist for the provided configuration.\nPlease check the available datasets by using the ``qml.data.list_datasets()`` function.""" + ) + with pytest.raises(ValueError, match=msg): + pennylane.data.data_manager.load(data_name="qchem", molname="bad_name") + + +@patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @patch.object(pennylane.data.data_manager, "head", head_mock) def test_load_except(monkeypatch, tmp_path): """Test that an exception raised by _download_dataset is propagated.""" @@ -340,6 +487,7 @@ def test_download_dataset_full_or_partial( assert download_full.called is not called_partial +@patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @pytest.mark.parametrize("force", (True, False)) @patch("pennylane.data.data_manager._download_full") def test_download_dataset_full_call(download_full, force): @@ -351,7 +499,7 @@ def test_download_dataset_full_call(download_full, force): pbar_task = MagicMock() pennylane.data.data_manager._download_dataset( - f"{S3_URL}/dataset/path", + f"{GRAPHQL_URL}/dataset/path", attributes=None, dest=dest, force=force, @@ -360,10 +508,11 @@ def test_download_dataset_full_call(download_full, force): ) download_full.assert_called_once_with( - f"{S3_URL}/dataset/path", block_size=1, dest=dest, pbar_task=pbar_task + f"{GRAPHQL_URL}/dataset/path", block_size=1, dest=dest, pbar_task=pbar_task ) +@patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @pytest.mark.parametrize("attributes", [None, ["x"]]) @pytest.mark.parametrize("force", (True, False)) @patch("pennylane.data.data_manager._download_partial") @@ -376,7 +525,7 @@ def test_download_dataset_partial_call(download_partial, attributes, force): pbar_task = MagicMock() pennylane.data.data_manager._download_dataset( - f"{S3_URL}/dataset/path", + f"{GRAPHQL_URL}/dataset/path", attributes=attributes, dest=dest, force=force, @@ -385,7 +534,7 @@ def test_download_dataset_partial_call(download_partial, attributes, force): ) download_partial.assert_called_once_with( - f"{S3_URL}/dataset/path", + f"{GRAPHQL_URL}/dataset/path", dest=dest, attributes=attributes, overwrite=force, @@ -400,7 +549,7 @@ def test_download_full(tmp_path): """Tests that _download_dataset will fetch the dataset file at ``s3_url`` into ``dest``.""" pennylane.data.data_manager._download_full( - "dataset/path", tmp_path / "dataset", block_size=1, pbar_task=None + f"{GRAPHQL_URL}/dataset/path", tmp_path / "dataset", block_size=1, pbar_task=None ) with open(tmp_path / "dataset", "rb") as f: @@ -521,78 +670,17 @@ def test_download_partial_no_check_remote(open_hdf5_s3, tmp_path): open_hdf5_s3.assert_not_called() -@patch("builtins.open") -@patch.object(pennylane.data.data_manager, "head", head_mock) -@pytest.mark.parametrize( - "datapath, escaped", - [("data/NH3+/data.h5", "data/NH3%2B/data.h5"), ("data/CA$H/money.h5", "data/CA%24H/money.h5")], -) -def test_download_datasets_escapes_url(_, tmp_path, mock_get_args, datapath, escaped): - """Tests that _download_datasets escapes special characters in a URL when doing a full download.""" - - dest = MagicMock() - dest.exists.return_value = False - - pennylane.data.data_manager._download_datasets( - S3_URL, - folder_path=tmp_path, - data_paths=[datapath], - attributes=None, - force=True, - block_size=1, - num_threads=1, - pbar=MagicMock(), - ) - - mock_get_args.assert_called_once() - assert mock_get_args.call_args[0] == (f"{S3_URL}/{escaped}",) - - -@patch("pennylane.data.data_manager._download_partial") -@pytest.mark.parametrize( - "datapath, escaped", - [("data/NH3+/data.h5", "data/NH3%2B/data.h5"), ("data/CA$H/money.h5", "data/CA%24H/money.h5")], -) -def test_download_datasets_escapes_url_partial(download_partial, tmp_path, datapath, escaped): - """Tests that _download_datasets escapes special characters in a URL when doing a partial - download.""" - attributes = ["attr"] - force = False - pbar = MagicMock() - pbar_task = MagicMock() - pbar.add_task.return_value = pbar_task - - pennylane.data.data_manager._download_datasets( - S3_URL, - folder_path=tmp_path, - data_paths=[datapath], - attributes=attributes, - force=force, - block_size=1, - num_threads=1, - pbar=pbar, - ) - - download_partial.assert_called_once_with( - f"{S3_URL}/{escaped}", - dest=tmp_path / datapath, - attributes=attributes, - overwrite=force, - block_size=1, - pbar_task=pbar_task, - ) - - +@patch.object(pennylane.data.data_manager.graphql, "get_graphql", graphql_mock) @pytest.mark.parametrize( "attributes,msg", [ ( - ["x", "y", "z", "foo"], - r"'foo' is an invalid attribute for 'my_dataset'. Valid attributes are: \['x', 'y', 'z'\]", + ["basis_rot_groupings", "basis_rot_samples", "dipole_op", "fci_energy", "foo"], + r"'foo' is an invalid attribute for 'my_dataset'. Valid attributes are: \['basis_rot_groupings', 'basis_rot_samples', 'dipole_op', 'fci_energy', 'fci_spectrum', 'hamiltonian', 'hf_state', 'molecule', 'number_op', 'optimal_sector', 'paulix_ops', 'qwc_groupings', 'qwc_samples', 'sparse_hamiltonian', 'spin2_op', 'spinz_op', 'symmetries', 'tapered_dipole_op', 'tapered_hamiltonian', 'tapered_hf_state', 'tapered_num_op', 'tapered_spin2_op', 'tapered_spinz_op', 'vqe_energy', 'vqe_gates', 'vqe_params'\]", ), ( - ["x", "y", "z", "foo", "bar"], - r"\['foo', 'bar'\] are invalid attributes for 'my_dataset'. Valid attributes are: \['x', 'y', 'z'\]", + ["basis_rot_groupings", "basis_rot_samples", "dipole_op", "fci_energy", "foo", "bar"], + r"\['foo', 'bar'\] are invalid attributes for 'my_dataset'. Valid attributes are: \['basis_rot_groupings', 'basis_rot_samples', 'dipole_op', 'fci_energy', 'fci_spectrum', 'hamiltonian', 'hf_state', 'molecule', 'number_op', 'optimal_sector', 'paulix_ops', 'qwc_groupings', 'qwc_samples', 'sparse_hamiltonian', 'spin2_op', 'spinz_op', 'symmetries', 'tapered_dipole_op', 'tapered_hamiltonian', 'tapered_hf_state', 'tapered_num_op', 'tapered_spin2_op', 'tapered_spinz_op', 'vqe_energy', 'vqe_gates', 'vqe_params'\]", ), ], ) @@ -600,7 +688,54 @@ def test_validate_attributes_except(attributes, msg): """Test that ``_validate_attributes()`` raises a ValueError when passed invalid attributes.""" - data_struct = {"my_dataset": {"attributes": ["x", "y", "z"]}} - with pytest.raises(ValueError, match=msg): - _validate_attributes(data_struct, "my_dataset", attributes) + _validate_attributes("my_dataset", attributes) + + +class TestGetGraphql: + """Tests for the ``get_graphql()`` function.""" + + query = ( + """ + query ListAttributes($datasetClassId: String!) { + datasetClass($datasetClassId: String!) { + attributes { + name + } + } + } + """, + ) + inputs = {"input": {"datasetClassId": "qspin"}} + + @patch.object(pennylane.data.data_manager.graphql, "post", post_mock) + def test_return_json(self): + """Tests that an expected json response is returned for a valid query and url.""" + response = pennylane.data.data_manager.graphql.get_graphql( + GRAPHQL_URL, + self.query, + self.inputs, + ) + assert response == _list_attrs_resp + + @patch.object(pennylane.data.data_manager.graphql, "post", post_mock) + def test_error_response(self): + """Tests that GraphQLError is raised with error messages when + the returned json contains an error message. + """ + error_query = """ + query ErrorQuery { + errorQuery { + field + } + } + """ + + with pytest.raises( + pennylane.data.data_manager.graphql.GraphQLError, + match="Errors in request: Mock error message.", + ): + pennylane.data.data_manager.graphql.get_graphql( + GRAPHQL_URL, + error_query, + ) diff --git a/tests/data/data_manager/test_graphql.py b/tests/data/data_manager/test_graphql.py new file mode 100644 index 00000000000..da43d20930f --- /dev/null +++ b/tests/data/data_manager/test_graphql.py @@ -0,0 +1,55 @@ +"""Basic integration tests for the Datasets Service API""" + +import pytest +import requests + +import pennylane.data.data_manager +from pennylane.data.data_manager.graphql import GRAPHQL_URL + + +# pylint: disable=protected-access +class TestGetGraphql: + """Tests for the ``get_graphql()`` function.""" + + query = """ + query DatasetClass { + datasetClasses { + id + } + } + """ + + def test_return_json(self): + """Tests that a dictionary representation of a json response is returned for a + valid query and url.""" + response = pennylane.data.data_manager.graphql.get_graphql( + GRAPHQL_URL, + self.query, + ) + assert isinstance(response, dict) + + def test_bad_url(self): + """Tests that a ``ConnectionError`` is returned for a valid query and invalid url.""" + + with pytest.raises(requests.exceptions.ConnectionError): + pennylane.data.data_manager.graphql.get_graphql( + "https://bad/url/graphql", + self.query, + ) + + def test_bad_query(self): + """Tests that a ``HTTPError`` is returned for a invalid query and valid url.""" + + bad_query = """ + query BadQuery { + badQuery { + field + } + } + """ + + with pytest.raises(requests.exceptions.HTTPError): + pennylane.data.data_manager.graphql.get_graphql( + GRAPHQL_URL, + bad_query, + ) diff --git a/tests/data/data_manager/test_params.py b/tests/data/data_manager/test_params.py index 009e30dc292..9ca581ece66 100644 --- a/tests/data/data_manager/test_params.py +++ b/tests/data/data_manager/test_params.py @@ -23,6 +23,7 @@ ParamArg, format_param_args, format_params, + provide_defaults, ) pytestmark = pytest.mark.data @@ -106,12 +107,12 @@ def test_format_params(): """Test that format_params calls format_param_args with each parameter.""" assert format_params( layout=[1, 4], bondlength=["0.5", "0.6"], z="full", y=ParamArg.DEFAULT - ) == { - "bondlength": ["0.5", "0.6"], - "layout": ["1x4"], - "y": ParamArg.DEFAULT, - "z": ParamArg.FULL, - } + ) == [ + {"name": "layout", "values": ["1x4"]}, + {"name": "bondlength", "values": ["0.5", "0.6"]}, + {"name": "z", "values": ParamArg.FULL}, + {"name": "y", "values": ParamArg.DEFAULT}, + ] class TestDescription: @@ -126,3 +127,68 @@ def test_repr(self): """Test that __repr__ is equivalent to dict __repr__.""" params = {"foo": "bar", "x": "y"} assert repr(Description(params)) == f"Description({repr(params)})" + + +@pytest.mark.parametrize( + "data_name, params, expected_params", + [ + ( + "qchem", + [{"name": "molname", "values": ["H2"]}], + [ + {"name": "molname", "values": ["H2"]}, + {"default": True, "name": "basis"}, + {"default": True, "name": "bondlength"}, + ], + ), + ( + "qchem", + [{"name": "molname", "values": ["H2"]}, {"name": "bondlength", "values": ["0.82"]}], + [ + {"name": "molname", "values": ["H2"]}, + {"name": "bondlength", "values": ["0.82"]}, + {"default": True, "name": "basis"}, + ], + ), + ( + "qspin", + [{"name": "sysname", "values": ["BoseHubbard"]}], + [ + {"name": "sysname", "values": ["BoseHubbard"]}, + {"default": True, "name": "periodicity"}, + {"default": True, "name": "lattice"}, + {"default": True, "name": "layout"}, + ], + ), + ( + "qspin", + [ + {"name": "sysname", "values": ["BoseHubbard"]}, + {"name": "periodicity", "values": ["closed"]}, + ], + [ + {"name": "sysname", "values": ["BoseHubbard"]}, + {"name": "periodicity", "values": ["closed"]}, + {"default": True, "name": "lattice"}, + {"default": True, "name": "layout"}, + ], + ), + ( + "qspin", + [ + {"name": "sysname", "values": ["BoseHubbard"]}, + {"name": "periodicity", "values": ["closed"]}, + {"name": "lattice", "values": ["chain"]}, + ], + [ + {"name": "sysname", "values": ["BoseHubbard"]}, + {"name": "periodicity", "values": ["closed"]}, + {"name": "lattice", "values": ["chain"]}, + {"default": True, "name": "layout"}, + ], + ), + ("other", [], []), + ], +) +def test_provide_defaults(data_name, params, expected_params): + assert provide_defaults(data_name, params) == expected_params