Coverage for tests/test_model.py: 98%
177 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-23 11:23 +0000
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-23 11:23 +0000
1from qml_essentials.model import Model
2from qml_essentials.ansaetze import Ansaetze, Circuit, Gates
3import pytest
4import logging
5import inspect
6import shutil
7import os
8import hashlib
9from typing import Optional
10import pennylane as qml
11import pennylane.numpy as np
13from typing import List, Callable
15logger = logging.getLogger(__name__)
18@pytest.mark.unittest
19def test_parameters() -> None:
20 test_cases = [
21 {
22 "shots": None,
23 "execution_type": "expval",
24 "output_qubit": 0,
25 "force_mean": False,
26 "exception": False,
27 },
28 {
29 "shots": None,
30 "execution_type": "expval",
31 "output_qubit": -1,
32 "force_mean": False,
33 "exception": False,
34 },
35 {
36 "shots": None,
37 "execution_type": "expval",
38 "output_qubit": -1,
39 "force_mean": True,
40 "exception": False,
41 },
42 {
43 "shots": None,
44 "execution_type": "density",
45 "output_qubit": 0,
46 "force_mean": False,
47 "exception": False,
48 },
49 {
50 "shots": 1024,
51 "execution_type": "probs",
52 "output_qubit": 0,
53 "force_mean": False,
54 "exception": False,
55 },
56 {
57 "shots": 1024,
58 "execution_type": "probs",
59 "output_qubit": 0,
60 "force_mean": True,
61 "exception": False,
62 },
63 {
64 "shots": 1024,
65 "execution_type": "expval",
66 "output_qubit": 0,
67 "force_mean": False,
68 "exception": False,
69 },
70 {
71 "shots": 1024,
72 "execution_type": "expval",
73 "output_qubit": 0,
74 "force_mean": True,
75 "exception": False,
76 },
77 {
78 "shots": 1024,
79 "execution_type": "density",
80 "output_qubit": 0,
81 "force_mean": False,
82 "exception": True,
83 },
84 ]
86 # Test the most minimal call
87 model = Model(
88 n_qubits=2,
89 n_layers=1,
90 circuit_type="Circuit_19",
91 )
92 assert (model() == model(model.params)).all()
94 for test_case in test_cases:
95 model = Model(
96 n_qubits=2,
97 n_layers=1,
98 circuit_type="Circuit_19",
99 output_qubit=test_case["output_qubit"],
100 shots=test_case["shots"],
101 )
103 if test_case["exception"]:
104 with pytest.warns(UserWarning):
105 _ = model(
106 model.params,
107 inputs=None,
108 execution_type=test_case["execution_type"],
109 force_mean=test_case["force_mean"],
110 )
111 else:
112 result = model(
113 model.params,
114 inputs=None,
115 execution_type=test_case["execution_type"],
116 force_mean=test_case["force_mean"],
117 )
119 if test_case["shots"] is None:
120 assert hasattr(
121 result, "requires_grad"
122 ), "No 'requires_grad' property available in output."
123 else:
124 # TODO: not supported by PennyLane yet
125 pass
126 if test_case["output_qubit"] == -1:
127 if test_case["force_mean"]:
128 assert (
129 result.size == 1 or result.shape[0] == 1
130 ), f"Shape of {test_case['output_qubit']} is not correct."
131 else:
132 # check for 2 because of n qubits
133 assert (
134 result.shape[0] == 2
135 ), f"Shape of {test_case['output_qubit']} is not correct."
136 str(model)
139@pytest.mark.smoketest
140def test_encoding() -> None:
141 test_cases = [
142 {"encoding_unitary": Gates.RX, "type": Callable, "input": [0]},
143 {"encoding_unitary": [Gates.RX, Gates.RY], "type": List, "input": [[0, 0]]},
144 {"encoding_unitary": "RX", "type": Callable, "input": [0]},
145 {"encoding_unitary": ["RX", "RY"], "type": List, "input": [[0, 0]]},
146 ]
148 for test_case in test_cases:
149 model = Model(
150 n_qubits=2,
151 n_layers=1,
152 circuit_type="Circuit_19",
153 encoding=test_case["encoding_unitary"],
154 )
155 _ = model(
156 model.params,
157 inputs=test_case["input"],
158 )
160 assert isinstance(model._enc, test_case["type"])
163@pytest.mark.unittest
164def test_cache() -> None:
165 # Stupid try removing caches
166 try:
167 shutil.rmtree(".cache")
168 except Exception as e:
169 logger.warning(e)
171 model = Model(
172 n_qubits=2,
173 n_layers=1,
174 circuit_type="Circuit_19",
175 )
177 result = model(
178 model.params,
179 inputs=None,
180 cache=True,
181 )
183 hs = hashlib.md5(
184 repr(
185 {
186 "n_qubits": model.n_qubits,
187 "n_layers": model.n_layers,
188 "pqc": model.pqc.__class__.__name__,
189 "dru": model.data_reupload,
190 "params": model.params,
191 "noise_params": model.noise_params,
192 "execution_type": model.execution_type,
193 "inputs": np.array([[0]]),
194 "output_qubit": model.output_qubit,
195 }
196 ).encode("utf-8")
197 ).hexdigest()
199 cache_folder: str = ".cache"
200 if not os.path.exists(cache_folder):
201 raise Exception("Cache folder does not exist.")
203 name: str = f"pqc_{hs}.npy"
204 file_path: str = os.path.join(cache_folder, name)
206 if os.path.isfile(file_path):
207 cached_result = np.load(file_path)
209 assert np.array_equal(
210 result, cached_result
211 ), "Cached result and calcualted result is not equal."
214@pytest.mark.expensive
215@pytest.mark.smoketest
216def test_lightning() -> None:
217 model = Model(
218 n_qubits=12, # model.lightning_threshold
219 n_layers=1,
220 circuit_type="Hardware_Efficient",
221 )
222 assert model.circuit.device.name == "lightning.qubit"
224 _ = model(
225 model.params,
226 inputs=None,
227 )
230@pytest.mark.smoketest
231def test_draw() -> None:
232 model = Model(
233 n_qubits=2,
234 n_layers=1,
235 circuit_type="Hardware_Efficient",
236 )
238 repr(model)
239 _ = model.draw(figure=True)
242@pytest.mark.smoketest
243def test_initialization() -> None:
244 test_cases = [
245 {
246 "initialization": "random",
247 },
248 {
249 "initialization": "zeros",
250 },
251 {
252 "initialization": "zero-controlled",
253 },
254 {
255 "initialization": "pi-controlled",
256 },
257 {
258 "initialization": "pi",
259 },
260 ]
262 for test_case in test_cases:
263 model = Model(
264 n_qubits=2,
265 n_layers=1,
266 circuit_type="Circuit_19",
267 data_reupload=True,
268 initialization=test_case["initialization"],
269 output_qubit=0,
270 shots=1024,
271 )
273 _ = model(
274 model.params,
275 inputs=None,
276 noise_params=None,
277 cache=False,
278 execution_type="expval",
279 )
282@pytest.mark.unittest
283def test_re_initialization() -> None:
284 model = Model(
285 n_qubits=2,
286 n_layers=1,
287 circuit_type="Circuit_19",
288 initialization_domain=[-2 * np.pi, 0],
289 random_seed=1000,
290 )
292 assert model.params.max() <= 0, "Parameters should be in [-2pi, 0]!"
294 temp_params = model.params.copy()
296 model.initialize_params(rng=np.random.default_rng(seed=1001))
298 assert not np.allclose(
299 model.params, temp_params, atol=1e-3
300 ), "Re-Initialization failed!"
303@pytest.mark.smoketest
304def test_ansaetze() -> None:
305 ansatz_cases = Ansaetze.get_available()
307 for ansatz in ansatz_cases:
308 # Skipping Circuit_15, as it is not yet correctly implemented (yet)
309 if ansatz.__name__ == "Circuit_15":
310 continue
312 logger.info(f"Testing Ansatz: {ansatz.__name__}")
313 model = Model(
314 n_qubits=4,
315 n_layers=1,
316 circuit_type=ansatz.__name__,
317 data_reupload=False,
318 initialization="random",
319 output_qubit=0,
320 shots=1024,
321 )
323 _ = model(
324 model.params,
325 inputs=None,
326 noise_params={
327 "BitFlip": 0.1,
328 "PhaseFlip": 0.2,
329 "AmplitudeDamping": 0.3,
330 "PhaseDamping": 0.4,
331 "DepolarizingChannel": 0.5,
332 },
333 cache=False,
334 execution_type="density",
335 )
337 class custom_ansatz(Circuit):
338 @staticmethod
339 def n_params_per_layer(n_qubits: int) -> int:
340 return n_qubits * 3
342 @staticmethod
343 def get_control_indices(n_qubits: int) -> Optional[np.ndarray]:
344 return None
346 @staticmethod
347 def build(w: np.ndarray, n_qubits: int, noise_params=None):
348 w_idx = 0
349 for q in range(n_qubits):
350 Gates.RY(w[w_idx], wires=q, noise_params=noise_params)
351 w_idx += 1
352 Gates.RZ(w[w_idx], wires=q, noise_params=noise_params)
353 w_idx += 1
355 if n_qubits > 1:
356 for q in range(n_qubits - 1):
357 Gates.CZ(wires=[q, q + 1], noise_params=noise_params)
359 model = Model(
360 n_qubits=2,
361 n_layers=1,
362 circuit_type=custom_ansatz,
363 data_reupload=True,
364 initialization="random",
365 output_qubit=0,
366 shots=1024,
367 )
368 logger.info(f"{str(model)}")
370 _ = model(
371 model.params,
372 inputs=None,
373 noise_params={
374 "BitFlip": 0.1,
375 "PhaseFlip": 0.2,
376 "AmplitudeDamping": 0.3,
377 "PhaseDamping": 0.4,
378 "DepolarizingChannel": 0.5,
379 },
380 cache=False,
381 execution_type="density",
382 )
385@pytest.mark.unittest
386def test_available_ansaetze() -> None:
387 ansatze = set(Ansaetze.get_available())
389 actual_ansaetze = set(
390 ansatz for ansatz in Ansaetze.__dict__.values() if inspect.isclass(ansatz)
391 )
392 # check that the classes are the ones returned by .__subclasses__
393 assert actual_ansaetze == ansatze
396@pytest.mark.unittest
397def test_multi_input() -> None:
398 input_cases = [
399 np.random.rand(1, 1),
400 np.random.rand(1, 2),
401 np.random.rand(1, 3),
402 np.random.rand(2, 1),
403 np.random.rand(20, 1),
404 ]
405 input_cases = [2 * np.pi * i for i in input_cases]
406 input_cases.append(None)
408 for inputs in input_cases:
409 logger.info(
410 f"Testing input with shape: "
411 f"{inputs.shape if inputs is not None else 'None'}"
412 )
413 encoding = (
414 Gates.RX if inputs is None else [Gates.RX for _ in range(inputs.shape[1])]
415 )
416 model = Model(
417 n_qubits=2,
418 n_layers=1,
419 circuit_type="Circuit_19",
420 data_reupload=True,
421 initialization="random",
422 encoding=encoding,
423 output_qubit=0,
424 shots=1024,
425 )
427 out = model(
428 model.params,
429 inputs=inputs,
430 noise_params=None,
431 cache=False,
432 execution_type="expval",
433 )
435 if inputs is not None:
436 if len(out.shape) > 0:
437 assert out.shape[0] == inputs.shape[0], (
438 f"batch dimension mismatch, expected {inputs.shape[0]} "
439 f"as an output dimension, but got {out.shape[0]}"
440 )
441 else:
442 assert (
443 inputs.shape[0] == 1
444 ), "expected one elemental input for zero dimensional output"
445 else:
446 assert len(out.shape) == 0, "expected one elemental output for empty input"
449@pytest.mark.smoketest
450def test_dru() -> None:
451 dru_cases = [False, True]
453 for dru in dru_cases:
454 model = Model(
455 n_qubits=2,
456 n_layers=1,
457 circuit_type="Circuit_19",
458 data_reupload=dru,
459 initialization="random",
460 output_qubit=0,
461 shots=1024,
462 )
464 _ = model(
465 model.params,
466 inputs=None,
467 noise_params=None,
468 cache=False,
469 execution_type="expval",
470 )
473@pytest.mark.unittest
474def test_local_state() -> None:
475 test_cases = [
476 {
477 "noise_params": None,
478 "execution_type": "density",
479 },
480 {
481 "noise_params": {
482 "BitFlip": 0.1,
483 "PhaseFlip": 0.2,
484 "AmplitudeDamping": 0.3,
485 "PhaseDamping": 0.4,
486 "DepolarizingChannel": 0.5,
487 },
488 "execution_type": "density",
489 },
490 {
491 "noise_params": None,
492 "execution_type": "expval",
493 },
494 ]
496 model = Model(
497 n_qubits=2,
498 n_layers=1,
499 circuit_type="Circuit_19",
500 data_reupload=True,
501 initialization="random",
502 output_qubit=0,
503 )
505 # Check default values
506 assert model.noise_params is None
507 assert model.execution_type == "expval"
509 for test_case in test_cases:
510 model = Model(
511 n_qubits=2,
512 n_layers=1,
513 circuit_type="Circuit_19",
514 data_reupload=True,
515 initialization="random",
516 output_qubit=0,
517 )
519 model.noise_params = test_case["noise_params"]
520 model.execution_type = test_case["execution_type"]
522 _ = model(
523 model.params,
524 inputs=None,
525 noise_params=None,
526 cache=False,
527 )
529 # check if setting "externally" is working
530 assert model.noise_params == test_case["noise_params"]
531 assert model.execution_type == test_case["execution_type"]
533 model = Model(
534 n_qubits=2,
535 n_layers=1,
536 circuit_type="Circuit_19",
537 data_reupload=True,
538 initialization="random",
539 output_qubit=0,
540 )
542 _ = model(
543 model.params,
544 inputs=None,
545 cache=False,
546 noise_params=test_case["noise_params"],
547 execution_type=test_case["execution_type"],
548 )
550 # check if setting in the forward call is working
551 assert model.noise_params == test_case["noise_params"]
552 assert model.execution_type == test_case["execution_type"]
555@pytest.mark.unittest
556def test_local_and_global_meas() -> None:
557 test_cases = [
558 {
559 "inputs": None,
560 "execution_type": "expval",
561 "output_qubit": -1,
562 "shots": None,
563 "out_shape": (2,),
564 "warning": False,
565 },
566 {
567 "inputs": np.array([0.1, 0.2, 0.3]),
568 "execution_type": "expval",
569 "output_qubit": -1,
570 "shots": None,
571 "out_shape": (2, 3),
572 "warning": False,
573 },
574 {
575 "inputs": np.array([0.1, 0.2, 0.3]),
576 "execution_type": "expval",
577 "output_qubit": 0,
578 "shots": None,
579 "out_shape": (3,),
580 "warning": False,
581 },
582 {
583 "inputs": np.array([0.1, 0.2, 0.3]),
584 "execution_type": "expval",
585 "output_qubit": [0, 1],
586 "shots": None,
587 "out_shape": (3,),
588 "warning": False,
589 },
590 {
591 "inputs": None,
592 "execution_type": "density",
593 "output_qubit": -1,
594 "shots": None,
595 "out_shape": (4, 4),
596 "warning": False,
597 },
598 {
599 "inputs": np.array([0.1, 0.2, 0.3]),
600 "execution_type": "density",
601 "output_qubit": -1,
602 "shots": None,
603 "out_shape": (3, 4, 4),
604 "warning": False,
605 },
606 {
607 "inputs": np.array([0.1, 0.2, 0.3]),
608 "execution_type": "density",
609 "output_qubit": 0,
610 "shots": None,
611 "out_shape": (3, 4, 4),
612 "warning": True,
613 },
614 {
615 "inputs": np.array([0.1, 0.2, 0.3]),
616 "execution_type": "probs",
617 "output_qubit": -1,
618 "shots": 1024,
619 "out_shape": (3, 4),
620 "warning": False,
621 },
622 {
623 "inputs": np.array([0.1, 0.2, 0.3]),
624 "execution_type": "probs",
625 "output_qubit": 0,
626 "shots": 1024,
627 "out_shape": (3, 2),
628 "warning": False,
629 },
630 {
631 "inputs": np.array([0.1, 0.2, 0.3]),
632 "execution_type": "probs",
633 "output_qubit": [0, 1],
634 "shots": 1024,
635 "out_shape": (3, 4),
636 "warning": False,
637 },
638 ]
640 for test_case in test_cases:
641 model = Model(
642 n_qubits=2,
643 n_layers=1,
644 circuit_type="Circuit_19",
645 data_reupload=True,
646 initialization="random",
647 output_qubit=test_case["output_qubit"],
648 shots=test_case["shots"],
649 )
650 if test_case["warning"]:
651 with pytest.warns(UserWarning):
652 out = model(
653 model.params,
654 inputs=test_case["inputs"],
655 noise_params=None,
656 cache=False,
657 execution_type=test_case["execution_type"],
658 )
659 else:
660 out = model(
661 model.params,
662 inputs=test_case["inputs"],
663 noise_params=None,
664 cache=False,
665 execution_type=test_case["execution_type"],
666 )
668 assert (
669 out.shape == test_case["out_shape"]
670 ), f"Expected {test_case['out_shape']}, got shape {out.shape}\
671 for test case {test_case}"
674@pytest.mark.unittest
675def test_parity() -> None:
676 model_a = Model(
677 n_qubits=2,
678 n_layers=1,
679 circuit_type="Circuit_1",
680 output_qubit=[0, 1], # parity
681 )
682 model_b = Model(
683 n_qubits=2,
684 n_layers=1,
685 circuit_type="Circuit_1",
686 output_qubit=-1, # individual
687 )
689 result_a = model_a(params=model_a.params, inputs=None, force_mean=True)
690 result_b = model_b(
691 params=model_a.params, inputs=None, force_mean=True
692 ) # use same params!
694 assert not np.allclose(
695 result_a, result_b
696 ), f"Models should be different! Got {result_a} and {result_b}"
699@pytest.mark.smoketest
700def test_params_store() -> None:
701 model = Model(
702 n_qubits=2,
703 n_layers=1,
704 circuit_type="Circuit_1",
705 )
706 opt = qml.AdamOptimizer(stepsize=0.01)
708 def cost(params):
709 return model(params=params, inputs=np.array([0])).mean()._value
711 params, cost = opt.step_and_cost(cost, model.params)