Skip to content

Commit dfe7673

Browse files
authored
add pennylane tensor as return_format option (#24)
1 parent f9a5daf commit dfe7673

2 files changed

Lines changed: 52 additions & 9 deletions

File tree

pyqit/core/trainer/trainer.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,11 @@ def predict(
167167
Used for inference only; weights are not touched.
168168
datamodule : DataModule
169169
Test split if present, else validation, else train.
170-
return_format : {"auto", "numpy", "torch"}, default "auto"
171-
``"auto"`` follows whatever the model emits.
170+
return_format : {"auto", "numpy", "torch", "pennylane"}, default "auto"
171+
``"auto"`` follows whatever the model emits. ``"pennylane"``
172+
returns a ``pennylane.numpy.tensor`` rather than a bare
173+
``ndarray``, for callers composing the result into further
174+
pnp-based code (a custom cost function, ``utils.diagnostic``).
172175
173176
Returns
174177
-------
@@ -292,14 +295,30 @@ def _collect(all_preds: list, return_format: str):
292295
return torch.cat(
293296
[p if _is_torch(p) else torch.as_tensor(p) for p in all_preds], dim=0
294297
)
298+
elif target in ("numpy", "pennylane"):
299+
# Both share this merge: a batch can be a torch tensor, a pnp
300+
# tensor or a mid-trace ArrayBox depending on backend, and
301+
# "pennylane" only needs one extra wrap over the merged result
302+
# rather than repeating that per-batch normalisation itself.
303+
merged = np.concatenate(
304+
[
305+
p.detach().cpu().numpy() if _is_torch(p) else np.asarray(p)
306+
for p in all_preds
307+
],
308+
axis=0,
309+
)
295310

296-
return np.concatenate(
297-
[
298-
p.detach().cpu().numpy() if _is_torch(p) else np.asarray(p)
299-
for p in all_preds
300-
],
301-
axis=0,
302-
)
311+
if target == "pennylane":
312+
import pennylane.numpy as pnp
313+
314+
return pnp.array(merged, requires_grad=False)
315+
316+
return merged
317+
else:
318+
raise ValueError(
319+
f"Unknown return_format {return_format!r}; expected one of "
320+
"'auto', 'numpy', 'torch', 'pennylane'."
321+
)
303322

304323
def __repr__(self) -> str:
305324
return (

pyqit/tests/test_trainer.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,30 @@ def test_predict_torch_format_requires_torch(monkeypatch):
339339
Trainer(verbose=0).predict(_model(), _dm(), return_format="torch")
340340

341341

342+
@pytest.mark.parametrize("return_format", ["auto", "numpy", "torch", "pennylane"])
343+
def test_predict_return_format(return_format):
344+
"""Each explicit format is requestable regardless of the active backend.
345+
346+
``"auto"`` and ``"numpy"`` both collapse pennylane's native
347+
``pnp.tensor`` predictions down to a bare ``ndarray`` via ``np.asarray``;
348+
``"torch"`` and ``"pennylane"`` are opt-ins that instead preserve (or
349+
convert into) an autograd-carrying tensor type.
350+
"""
351+
import pennylane.numpy as pnp
352+
353+
expected_type = {
354+
"auto": np.ndarray,
355+
"numpy": np.ndarray,
356+
"torch": pytest.importorskip("torch").Tensor,
357+
"pennylane": pnp.tensor,
358+
}[return_format]
359+
360+
pyqit.set_backend("pennylane")
361+
preds = Trainer(verbose=0).predict(_model(), _dm(), return_format=return_format)
362+
363+
assert type(preds) is expected_type
364+
365+
342366
def _load(path):
343367
if path.endswith(".ckpt"):
344368
import torch

0 commit comments

Comments
 (0)