Repository: PennyLaneAI/pennylane (python)

Issue:
[BUG] `qml.data.load()` fails when using 'full' parameter value
### Expected behavior

`qml.data.load('qspin',sysname='Heisenberg',periodicity='open',lattice='full',layout='2x2')`
Should return the Heisenberg dataset corresponding to the `open` periodicity, `rectangular` lattice, and `2x2` layout.

### Actual behavior

`qml.data.load('qspin',sysname='Heisenberg',periodicity='open',lattice='full',layout='2x2')`

Raises `ValueError: layout value of '2x2' is not available. Available values are ['1x16', '1x4', '1x8']`

### Additional information

_No response_

### Source code

```shell
qml.data.load('qspin',sysname='Heisenberg',periodicity='open',lattice='full',layout='1x4') # works

qml.data.load('qspin',sysname='Heisenberg',periodicity='open',lattice='full',layout='2x2') # fails
```


### Tracebacks

_No response_

### System information

```shell
Name: PennyLane
Version: 0.31.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: .venv/lib/python3.8/site-packages
Editable project location: 
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           Linux-5.15.0-73-generic-x86_64-with-glibc2.29
Python version:          3.8.10
Numpy version:           1.23.5
Scipy version:           1.10.1
Installed devices:
- default.gaussian (PennyLane-0.31.0.dev0)
- default.mixed (PennyLane-0.31.0.dev0)
- default.qubit (PennyLane-0.31.0.dev0)
- default.qubit.autograd (PennyLane-0.31.0.dev0)
- default.qubit.jax (PennyLane-0.31.0.dev0)
- default.qubit.tf (PennyLane-0.31.0.dev0)
- default.qubit.torch (PennyLane-0.31.0.dev0)
- default.qutrit (PennyLane-0.31.0.dev0)
- null.qubit (PennyLane-0.31.0.dev0)
- lightning.qubit (PennyLane-Lightning-0.30.0)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.

Pull request:
Fix error in 'qml.data.load()` when using 'full' parameter value
**Context:**
`qml.data.load()` did not properly handle specified parameters that come after `'full'`.  For example, the following
call should return all qspin datasets matching `sysname='Heisenberg', periodicity='open', layout='2x2'`, for any value
of `lattice`:

```
qml.data.load('qspin',sysname='Heisenberg',periodicity='open',lattice='full',layout='2x2')
```
Instead, we get an exception, even though matching datasets do exist:
```
ValueError: layout value of '2x2' is not available. Available values are ['1x16', '1x4', '1x8']
```

This happens because the `find()` function fails as soon as it can't find a matching dataset for any of the parameters. When it checks the `'chain'` lattice, it can't find `'2x2'` and throws an exception, even though `'2x2'` is available for `'rectangular`'.

**Description of the Change:**
`Foldermap.find()` will only throw an exception if it cannot find any matching datasets for a given parameter. This does mean that the user can provide invalid parameters, as long as one of them matches. For example:

```
qml.data.load('qspin',sysname='Heisenberg',periodicity='open',lattice='full',layout=['2x2', '17x32'])
```
Will not result in an error.

**Benefits:**
- `qml.data.load()` can handle full parameters that come before specific parameters.

**Possible Drawbacks:**
- The user will not get immediate feedback for erroneous parameters
- Users are no logner guaranteed to get a dataset for every combination of (fully qualified) parameters. For example, `qml.data.load('qchem', molname='H2', basis=['STO-3G', 'CC-PVDZ'], bondlength=['0.5', '0.7'])` will return 4 datasets. But if one those is removed, 3 will be returned without throwing an error.

**Related GitHub Issues:**
Fixes #4270

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 1ac65dd07..b904b6e4a 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -210,6 +210,9 @@
   still a `_qfunc_output` property on `QNode` instances.
   [(#4651)](https://github.com/PennyLaneAI/pennylane/pull/4651)
 
+* `qml.data.load` properly handles parameters that come after `'full'`
+  [(#4663)](https://github.com/PennyLaneAI/pennylane/pull/4663)
+
 * The `qml.jordan_wigner` function has been modified to optionally remove the imaginary components
   of the computed qubit operator, if imaginary components are smaller than a threshold. 
   [(#4639)](https://github.com/PennyLaneAI/pennylane/pull/4639)
diff --git a/pennylane/data/data_manager/foldermap.py b/pennylane/data/data_manager/foldermap.py
index 9080535e8..bf4c0cde4 100644
--- a/pennylane/data/data_manager/foldermap.py
+++ b/pennylane/data/data_manager/foldermap.py
@@ -119,6 +119,7 @@ class FolderMapView(typing.Mapping[str, Union["FolderMapView", DataPath]]):
 
             while curr:
                 curr_description, curr_level = curr.pop()
+
                 if param_arg == ParamArg.FULL:
                     next_params = curr_level
                 elif param_arg == ParamArg.DEFAULT:
@@ -131,18 +132,29 @@ class FolderMapView(typing.Mapping[str, Union["FolderMapView", DataPath]]):
                 else:
                     next_params = param_arg
 
-                try:
-                    todo.extend(
+                for next_param in next_params:
+                    try:
+                        fmap_next = curr_level[next_param]
+                    except KeyError:
+                        continue
+
+                    todo.append(
                         (
                             Description((*curr_description.items(), (param_name, next_param))),
-                            curr_level[next_param],
+                            fmap_next,
                         )
-                        for next_param in next_params
                     )
-                except KeyError as exc:
-                    raise ValueError(
-                        f"{param_name} '{exc.args[0]}' is not available. Available values are: {list(curr_level)}"
-                    ) from exc
+
+            if len(todo) == 0:
+                # None of the parameters matched
+                param_arg_repr = (
+                    repr([param_arg])
+                    if isinstance(param_arg, (str, ParamArg))
+                    else repr(list(param_arg))
+                )
+                raise ValueError(
+                    f"{param_name} value(s) {param_arg_repr} are not available. Available values are: {list(curr_level)}"
+                )
 
             curr, todo = todo, curr
 


Test patch:
diff --git a/tests/data/data_manager/test_foldermap.py b/tests/data/data_manager/test_foldermap.py
index 2bfa56e60..8b5b21c88 100644
--- a/tests/data/data_manager/test_foldermap.py
+++ b/tests/data/data_manager/test_foldermap.py
@@ -15,6 +15,8 @@
 Tests for the :class:`pennylane.data.data_manger.FolderMapView` class.
 """
 
+import re
+
 import pytest
 
 from pennylane.data.data_manager import DEFAULT, FULL, DataPath
@@ -125,6 +127,38 @@ class TestFolderMapView:
                     ),
                 ],
             ),
+            (
+                {"missing_default": DEFAULT, "molname": "O2", "basis": FULL, "bondlength": ["0.6"]},
+                [
+                    (
+                        {"molname": "O2", "basis": "STO-3G", "bondlength": "0.6"},
+                        "qchem/O2/STO-3G/0.6.h5",
+                    ),
+                ],
+            ),
+            (
+                {
+                    "missing_default": DEFAULT,
+                    "molname": "O2",
+                    "basis": FULL,
+                    "bondlength": ["0.6", "200"],
+                },
+                [
+                    (
+                        {"molname": "O2", "basis": "STO-3G", "bondlength": "0.6"},
+                        "qchem/O2/STO-3G/0.6.h5",
+                    ),
+                ],
+            ),
+            (
+                {"missing_default": FULL, "molname": "O2", "bondlength": ["0.6"]},
+                [
+                    (
+                        {"molname": "O2", "basis": "STO-3G", "bondlength": "0.6"},
+                        "qchem/O2/STO-3G/0.6.h5",
+                    ),
+                ],
+            ),
         ],
     )
     def test_find(self, foldermap, kwds, expect):  # pylint: disable=redefined-outer-name
@@ -155,14 +189,36 @@ class TestFolderMapView:
         with pytest.raises(ValueError, match="No default available for parameter 'molname'"):
             FolderMapView(FOLDERMAP).find("qchem")
 
-    def test_find_invalid_parameter(self):
+    @pytest.mark.parametrize(
+        "arg, error_fmt", [("Z3", repr(["Z3"])), (("Z3", "Z4"), repr(["Z3", "Z4"]))]
+    )
+    def test_find_invalid_parameter(self, arg, error_fmt):
         """Test that a ValueError is raised when a parameter provided
         does not exist."""
 
         with pytest.raises(
-            ValueError, match=r"molname 'Z3' is not available. Available values are: \['O2', 'H2'\]"
+            ValueError,
+            match=re.escape(
+                f"molname value(s) {error_fmt} are not available. Available values are: ['O2', 'H2']"
+            ),
         ):
-            FolderMapView(FOLDERMAP).find("qchem", molname="Z3")
+            FolderMapView(FOLDERMAP).find("qchem", molname=arg)
+
+    @pytest.mark.parametrize("basis", [FULL, DEFAULT])
+    def test_find_invalid_parameters_after_full_default(self, basis):
+        """Test that a ValueError is raised when a parameter provided
+        does not exist, after a 'full' or 'default' parameter has been provided for a
+        higher-priority parameter."""
+
+        with pytest.raises(
+            ValueError,
+            match=(
+                r"bondlength value\(s\) \['0.20', '200'\] are not available. Available values are: \['0.5', '0.6'\]"
+            ),
+        ):
+            FolderMapView(FOLDERMAP).find(
+                "qchem", molname="O2", basis=basis, bondlength=["0.20", "200"]
+            )
 
     @pytest.mark.parametrize(
         "init, key, expect",