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